首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >从零玩转系列之 MCP AI 理论+项目实战开发你的MCP Server

从零玩转系列之 MCP AI 理论+项目实战开发你的MCP Server

原创
作者头像
杨不易呀
修改2025-08-29 13:47:01
修改2025-08-29 13:47:01
2.7K18
举报

前言

halo,我是不易, 继ChatGPT发布已经过去了快三年了, 随着 AI 人工智能的不断发展给我们开发人员甚至UI设计人员带来了巨大的变化.

24年横空出世Claude 3.5 模型给我震撼到了, 力压 ChatGPT

我的小程序以及UI设计差不多都是它完成, 这个团队的强大让我不得不去关注.

24年10月2日,Anthropic 发布 MCP 协议,旨在为 AI 模型提供一个开放、通用且有共识的协议标准。

它允许开发者将 AI 模型连接到各种数据源和工具,从而实现更强大的 AI 应用。

你可以把 MCP 想象成 AI 世界的“USB-C 接口”——就像 USB-C 让各种设备轻松互联,MCP 也为 AI 模型连接不同数据源和工具提供了标准化方式。

这又是AI 再次迎来新突破, 新的概念, 新的协议, 新的生态.

那时 MCP 还没现在这么火,也没有这么多 MCP Server, 大部分的MCP Server 都是基于Nodejs 开发, 没有Java 的生态, 随着 Spring 的生态支持,Java 也迎来了“春天”——只需简单配置,就能实现自己的 MCP Server。

对于刚接触的朋友来说,MCP 和 MCP Server 可能有些陌生。别担心,接下来我们会一步步探索它们的玩法:从认识概念,到简单操作,再到实战开发。流程图如下:

⚠️ 本文纯手敲, 涵盖了理论知识+实战知识, 如果您觉得帮助到了您,请给我点个👍 如果本文有错别字,或者有不对的地方请指出谢谢啦! 🛫 本期内容涉及较多, 本次的理论知识较多,如果你已经掌握了理论那么可以直接前往项目实战开发 MCP Server 开发技术选型: Java + Spring Ai 来完成我们的腾讯云开发者社区 MCP Server 另外接入支付宝 MCP 来完成支付场景, 带着大家结合起来情景演绎, AI 助手让AI调用多个MCP Server 比如有人找我写论文我 AI 生成完毕需要让他交钱我们就可以生成链接让他去支付 最后会统一将这些 MCP 串联起来形成 MCP Agent 变成一个 AI 小助手 如下图的结合

什么是 MCP? 为什么一定要用 MCP?

  • MCP 是一个标准协议,就像给 AI 大模型装了一个 “万能接口”,让 AI 模型能够与不同的数据源和工具进行无缝交互。它就像 USB-C 接口一样,提供了一种标准化的方法,将 AI 模型连接到各种数据源和工具。
  • MCP 旨在替换碎片化的 Agent 代码集成,从而使 AI 系统更可靠,更有效。通过建立通用标准,服务商可以基于协议来推出它们自己服务的 AI 能力,从而支持开发者更快的构建更强大的 AI 应用。开发者也不需要重复造轮子,通过开源项目可以建立强大的 AI Agent 生态。
  • MCP 可以在不同的应用 / 服务之间保持上下文,增强整体自主执行任务的能力。

下面图展现了 MCP 在实际应用中的整体架构和数据流转过程:

  1. MCP Client(如 Claude、IDE、工具等):这是你本地的“指挥中心”,负责发起各种 AI 能力请求。无论你用的是 AI 助手、开发工具还是自动化脚本,只要支持 MCP 协议,都可以作为 MCP Client。
  2. MCP 协议:它就像一根“万能数据线”,把 MCP Client 和不同的 MCP Server 连接起来。所有的请求和响应都通过 MCP 协议进行标准化传递,保证了兼容性和可扩展性。
  3. MCP Server A/B/C:每个 MCP Server 都可以对接不同的数据源或服务。例如:
    • MCP Server A 连接本地数据源 A(如本地数据库、文件等)
    • MCP Server B 连接本地数据源 B
    • MCP Server C 通过 Web API 连接远程服务 C(如云端 API、第三方平台等)
  4. 数据流转:当 MCP Client 发起请求时,会根据需求选择合适的 MCP Server。Server 再去访问对应的数据源或服务,获取结果后通过 MCP 协议返回给 Client。
  5. 本地与远程资源整合:无论数据源在本地还是远程,MCP 都能实现统一调用和管理。比如你既可以让 AI 读取本地文件,也能让它调用云端服务,全部通过同一套 MCP 协议完成。

这下懂了吧, 是不是觉得很强大, 赋予一个 "USB" 就可以访问你的数据库, 并且进行让他 CRUD、操作本地文件等等.

MCP Stdio 协议 & JSON-RPC 传输格式

在前面的架构图中,我们已经看到 MCP 如何像"USB-C"一样,把本地和远程的各种数据源、工具统一接入到 AI 系统中。这种统一接入极大提升了开发效率和系统扩展性。

这就很像 function call 让模型可以按需调用预设函数,自动获取数据或执行等等操作, 确实带来了便利,但也引入了新的问题:平台依赖强、API 不兼容,每次更换模型或平台都要重写集成代码,既费时又容易出错,还存在安全和交互上的局限。

MCP 正是为了解决这些"碎片化对接"的难题而生。它最大的特点就是"开箱即用"——你无需编写任何代码,只需通过简单的配置,就能直接接入和调用各种工具和服务。

接下来,我们重点介绍 MCP 的接入协议

MCP 支持很多协议: Stdio/SSE/Str/eamable HTTP/同进程调用, 其余的本文内容较多将另外单开一个专题详细讲解

我这里就重点介绍 MCP 协议之一:Stdio

标准输入/输出(Stdio)

Stdio 协议通过标准输入和输出流实现通信,非常适合本地集成和命令行工具开发。常见的使用场景包括:

  • 构建命令行工具
  • 实现本地系统集成
  • 简单的进程间通信
  • 使用 shell 脚本自动化

传输格式与配置示例

MCP 的配置也非常直观。你只需在 mcpServers 中定义好需要接入的 MCP Server(比如 mcp-gitee、支付宝mcp、mysql mcp 等),然后通过 MCP 客户端统一调用即可

例如下面的配置 JSON-RPC 传输格式 :

代码语言:json
复制
{
  "mcpServers": {
    "gitee": {
      "command": "npx",
      "args": [
        "-y",
        "@gitee/mcp-gitee@latest"
      ],
      "env": {
        "GITEE_API_BASE": "https://gitee.com/api/v5",
        "GITEE_ACCESS_TOKEN": "<your personal access token>"
      }
    }
  }
}

在任何支持MCP协议的客户端使用, 比如 Claude Desktop 、腾讯云代码助手、Cursor 等等

通过这种方式,无论你需要接入什么工具或服务,只需简单配置即可,无需关心底层实现细节,极大提升了开发效率和系统可维护性。

实操 MCP

那么接下来我们来看看如何使用, 以 腾讯云代码助手为例(非常好用强烈推荐), 接入 mcp-gitee 实现创建仓库、查询仓库、提交代码、推送代码等操作

前往 GITEE 仓库搜索 mcp-gitee 地址: https://gitee.com/oschina/mcp-gitee

往下滑可以看到 npx 启动方式 这个方式就需要你安装 Node.js 还有一种就是可执行文件启动这个就是要你自己去源码构建了, 我这边就直接使用 Node.js 方式集成, 如果你还没安装 Node.js 那么打开腾讯云 ai 对话问它如何安装即可非常全: https://copilot.tencent.com/chat/

代码语言:markdown
复制
{
  "mcpServers": {
    "gitee": {
      "command": "npx",
      "args": [
        "-y",
        "@gitee/mcp-gitee@latest"
      ],
      "env": {
        "GITEE_API_BASE": "https://gitee.com/api/v5",
        "GITEE_ACCESS_TOKEN": "<your personal access token>"
      }
    }
  }
}

继续往下滑可以看到这个 mcp server 支持的功能

有很多功能工具, 也有环境变量、也有命令行配置信息

我就列举几个教大家如何简单使用, 其他的同学们感兴趣就多玩玩

可用工具

服务器提供了各种与 Gitee 交互的工具:

工具

类别

描述

list_user_repos

仓库

列出用户授权的仓库

get_file_content

仓库

获取仓库中文件的内容

create_user_repo

仓库

创建用户仓库

create_org_repo

仓库

创建组织仓库

create_enter_repo

仓库

创建企业仓库

fork_repository

仓库

Fork 仓库

create_release

仓库

为仓库创建发行版

list_releases

仓库

列出仓库发行版

search_open_source_repositories

仓库

搜索开源仓库

list_repo_pulls

Pull Request

列出仓库中的拉取请求

merge_pull

Pull Request

合并拉取请求

create_pull

Pull Request

创建拉取请求

update_pull

Pull Request

更新拉取请求

get_pull_detail

Pull Request

获取拉取请求的详细信息

comment_pull

Pull Request

评论拉取请求

list_pull_comments

Pull Request

列出拉取请求的所有评论

create_issue

Issue

创建 Issue

update_issue

Issue

更新 Issue

get_repo_issue_detail

Issue

获取仓库 Issue 的详细信息

list_repo_issues

Issue

列出仓库 Issue

comment_issue

Issue

评论 Issue

list_issue_comments

Issue

列出 Issue 的评论

get_user_info

用户

获取当前认证用户信息

search_users

用户

搜索用户

list_user_notifications

通知

列出用户通知

获取 MCP-GITEE 配置信息

在上面可以看到 mcp server 配置当中需要填入 GITEE_ACCESS_TOKEN 点击下面链接前往获取

填写好你的令牌名称、过期时间

⚠️ 令牌可千万别泄漏了, 别人是可以拿这个访问可以访问的操作权限.

使用 MCP-GITEE

前往下载腾讯云代码助手 https://copilot.tencent.com/#install

我就使用 Cursor 来下载腾讯云代码助手进行使用, 我这里已经下载过了, 同学们没下载的速度下载.

在侧边栏点击助手图标就可以使用了, 点击到 Craft 模式它支持 MCP 服务的使用

点击小花花图标, 可以看到 MCP 市场 我们搜索 MCP-GITEE 搜索不到, 因为还没收录进来

我们就手动添加即可, 复制上面的 mcp-gitee server json

点击已经安装, 配置 MCP Server , 会弹出编辑 mcp 配置文件

复制进去之后,在配置好 gitee 的令牌 token 即可看到旁边的 gitee 显示出这么多的工具

⚠️ 注意: 复制上面的npx命令,如果你是windows用户,需要做如下的修改:command修改为cmd ,下面的args数组增加 "/c"和 "npx",注意顺序不要乱undefined"服务": { "command": "cmd", "args": "/c", "npx", "-y", "@modelcontextprotocol/xxxxxxxxxxx" undefined }

尝鲜 MCP Server 接入客户端使用

继续点击旁边的播放图标, 让 CodeBuddy 进行测试它是否可以正常使用

那么可以看到已经是输出了, 并且调用了 查询工具 去查询 gitee 上面叫 ruanyf 的技术博主, 接下来我们来搜索自己

可以看到也是可以正常的搜索出来的, 看到返回的信息没什么问题都是正确的

那么继续完成前面说的的任务吧

  • 获取当前登录用户的信息, 说实话很快嗷, 眼睛没眨眼就输出出来了, 腾讯云助手这么快吗
  • 创建用户仓库
  • 帮我创建一个仓库名称为: mcp-study 为公开仓库, 默认生成readme.md 里面的内容是 从零玩转MCP

不得不夸一下腾讯云部署的模型了,这么快的吗? 我用别的模型都要等 30 秒

可以看到调用了三个 mco 工具 , 但是仓库不默认给我公开, 可能是安全原因吧, 下面也提示我要我主动去设置一下.

1、创建仓库

2、获取仓库文件修改仓库文件为 我们指定的内容

3、创建了 issue

来一一认证

可以看到成功创建仓库 并且创建了 readme 文件 内容也更新了 还创建了 issue 来提醒你干什么操作了, 可以看到 MCP Server 的强大、便捷了, 只需要配置一下 3 秒就完成

拥有 MCP 协议大模型可以做任何事情, 操作系统文件也不例外, 来我们继续玩玩

操作电脑文件 MCP Server

打开腾讯云开发者社区 MCP 广场 我们找找操作系统文件的 MCP Server

https://cloud.tencent.com/developer/mcp

直接 ctrl + f 搜索即可 可以看到有一个点进去

可以看到支持这些功能操作, 具体的我们看都不用看 都理解了 它是干嘛的,直接配置, 下滑找到 npx

CodeBuddy 集成文件系统 MCP Server

代码语言:markdown
复制
  "filesystem": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "/Users/yangbuyi/Desktop",
        "/Users/yangbuyi/Desktop"
      ]
    },

⚠️ 注意: 复制上面的npx命令,如果你是windows用户,需要做如下的修改:command修改为cmd ,下面的args数组增加 "/c"和 "npx",注意顺序不要乱undefined"服务": { "command": "cmd", "args": "/c", "npx", "-y", "@modelcontextprotocol/xxxxxxxxxxx" undefined }

和前面一样点击播放图标让 CodeBuddy 进行测试这个 MCP Server

可以看到上面列举了这么多功能操作, 我这边就简单演示几个感兴趣的同学可以多研究玩玩

MCP Server - 操作电脑文件

  • 帮我在桌面创建 从零玩转 MCP 的文件 .txt 格式 内容帮我填入 腾讯云开发者社区杨不易呀

默认像是这种存在操作系统相关的会进行询问一下,你可以点击‘每次询问’ 换成自动, 这里呢我们直接修改即可

然后继续询问已经创建成功了, 是否应用这个操作? 我们直接接受即可

可以看到桌面已经创建好了,并且内容也写入进来了

到这里是不是已经充分认识到 MCP Server 的作用了和强大了呢?

接下来我带着大家手把手使用 Java 语言制作自己的 MCP Server

项目实战 - 自定义 MCP Server

在上面我们已经简单玩了下 MCP Server , 接下来我带着大家动手做一个 MCP Server

我们要做的是腾讯云文章发布 MCP Server , 这个功能的目的是指可以将文章发布各个平台, 但是目前只是集成了腾讯云开发者社区后续会慢慢的集成其他平台

其实这个功能因为之前就想做 无奈 JS 逆向技术有限, 无法突破旧版本的文章发布里面的 content 加密 (有大佬感兴趣可以去试试看,提醒一下应该有 2 层加密其中有 base64), 然而腾讯云发布了新版的文章编辑器嘿嘿我进去一看没加密, 至此腾讯云文章发布 MCP Server 诞生了, 也是一个很好的锻炼开发 MCP Server 的机会(官方看到了不会把加密加上了吧) , 那么开始吧, 先看看流程图, 在来效果演示

效果演示

我们先来看一下效果图

可以看到生成了文章 并且发布到腾讯云开发者社区了 点进去看,可以看到已经发布成功

我还提前发布了一点, 测试文章发布稳定状态

开发前置工作

前置工作

  • 需要有腾讯云开发者社区帐号 - 后续需要拿 Cookie
  • 开发者工具去获取请求接口以及参数

接下来我们先来获取接口

随便输入一点东西, 达到能够发布文章的校验, 然后打开 F12

分别勾选这两个 开启录制和保留请求接口日志

点击发送, 观察接口 addArticle 然后对接口进行右击获取请求 CUrl 格式

点击 以 cUrl 格式复制, 然后顺便打开你的 api 调试工具 这里我使用 apifox

点击导入, 复制进去即可, 就可以看到接口请求信息了

此时直接请求是不能请求的, 没有 cookie 也就是权限认证, 我们手动的去复制即可

回到刚刚的接口, 复制下面的 Cookie , 然后到 api 调试工具新增 请求头 key 为 cookie 参数就是这个复制过来

填写好后我们请求一下, 查看是否请求成功

可以看到返回了一个文章的 ID 和状态码 为零的 表示成功了, 我们看看社区创作中心看看是否存在

可以看到, 成功请求成功了, 那么就先删除这几个测试文章, 接下来创建项目开发服务

实战 MCP Server

创建 tencent-add-article-mcp-server 项目

然后接着一直下一步就行, 无需配置任何东西

使用CodeBuddy开发发布文章功能

前面我们已经将腾讯云社区发布文章到API复制到apifox, 接下来我带着大家用腾讯云发布的产品

CodeBuddy 来为我编写发布文章接口, 我这里录制了一个演示视频让 CodeBuddy 帮我写

下面是生成出来的代码, 不想用代码伙伴的话(强烈建议用一下很舒服)

发布文章代码结构

出入参实体类

代码语言:markdown
复制
package com.yby6.mcp.server.tencent.api.dto;

import lombok.Data;

import java.util.List;
import java.util.Map;

/**
 * 腾讯云开发者社区发布文章请求DTO
 *
 * 该DTO类用于封装发布文章到腾讯云开发者社区时所需的所有参数。
 * 包含了文章的基本信息、分类信息、标签信息、展示设置等多个维度的数据。
 * 使用Lombok的@Data注解自动生成getter、setter等方法。
 *
 * 主要功能:
 * 1. 封装文章发布所需的所有参数
 * 2. 支持Markdown到ProseMirror格式的自动转换
 * 3. 提供文章分类、标签、专栏等管理功能
 *
 * @author yby6
 * @version 1.0.0
 */
@Data
public class AddArticleRequest {
    /**
     * 文章标题
     * 必填字段,用于显示文章的主标题
     */
    private String title;

    /**
     * 文章内容(JSON格式)
     * 包含富文本编辑器的完整内容结构
     * 使用ProseMirror格式存储,支持复杂的富文本编辑功能
     */
    private String content;

    /**
     * 文章纯文本内容
     * 用于SEO和摘要展示
     * 当content为空时,会自动将plain转换为ProseMirror格式
     */
    private String plain;

    /**
     * 文章来源类型
     * 1: 原创
     * 用于标识文章的创作类型
     */
    private Integer sourceType;

    /**
     * 文章分类ID列表
     * 用于文章分类管理
     * 支持多分类,每个分类对应一个ID
     */
    private List<Integer> classifyIds;

    /**
     * 文章标签ID列表
     * 用于文章标签管理
     * 支持多标签,每个标签对应一个ID
     */
    private List<Integer> tagIds;

    /**
     * 长尾标签列表
     * 用于SEO优化
     * 支持自定义长尾关键词,提升文章搜索可见性
     */
    private List<String> longtailTag;

    /**
     * 专栏ID列表
     * 用于文章专栏管理
     * 支持将文章添加到多个专栏中
     */
    private List<Integer> columnIds;

    /**
     * 是否开启评论
     * 1: 开启
     * 0: 关闭
     * 控制文章是否允许读者评论
     */
    private Integer openComment;

    /**
     * 是否关闭文本链接
     * 1: 关闭
     * 0: 开启
     * 控制文章中的文本链接是否可点击
     */
    private Integer closeTextLink;

    /**
     * 用户摘要
     * 用于文章预览展示
     * 提供文章内容的简短描述
     */
    private String userSummary;

    /**
     * 文章封面图片URL
     * 用于文章列表和详情页的展示
     * 支持自定义封面图片
     */
    private String pic;

    /**
     * 来源详情
     * 用于记录文章来源的详细信息
     * 包含来源类型、URL等额外信息
     */
    private Map<String, Object> sourceDetail;

    /**
     * 专区名称
     * 用于指定文章所属的专区
     * 支持文章分类到特定专区
     */
    private String zoneName;

    /**
     * 草稿ID
     * 如果是编辑已有草稿,需要提供此ID
     * 用于支持文章的草稿编辑功能
     */
    private Long draftId;
    
    /**
     * 获取ProseMirror格式的内容
     * <p>
     * 该方法用于获取文章内容的ProseMirror格式。
     * 如果content为空,则自动将plain转换为ProseMirror格式。
     * 确保文章内容始终以正确的格式提供给编辑器。
     *
     * @return ProseMirror格式的内容
     */
    public String getContent() {
        return "";
    }
    
}
代码语言:markdown
复制
package com.yby6.mcp.server.tencent.api.dto;

import lombok.Data;

/**
 * 腾讯云开发者社区发布文章响应DTO
 *
 * 该DTO类用于封装发布文章到腾讯云开发者社区后的返回结果。
 * 包含了文章发布的状态信息和文章的唯一标识符。
 * 使用Lombok的@Data注解自动生成getter、setter等方法。
 *
 * 主要功能:
 * 1. 提供文章发布结果的反馈
 * 2. 返回新创建文章的ID
 * 3. 指示发布操作的成功或失败状态
 *
 * @author yby6
 * @version 1.0.0
 */
@Data
public class AddArticleResponse {
    /**
     * 文章ID
     * 发布成功后返回的文章唯一标识
     * 用于后续对文章进行编辑、删除等操作
     * 在发布失败时可能为null
     */
    private Long articleId;

    /**
     * 发布状态
     * 0: 成功
     * 非0: 失败
     * 用于快速判断文章发布是否成功
     * 具体的错误码需要参考腾讯云开发者社区的API文档
     */
    private Integer status;
}

Retrofit2 RestFul 接口 api

代码语言:markdown
复制
package com.yby6.mcp.server.tencent.api;

import com.yby6.mcp.server.tencent.api.dto.AddArticleRequest;
import com.yby6.mcp.server.tencent.api.dto.AddArticleResponse;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.Header;
import retrofit2.http.Headers;
import retrofit2.http.POST;

/**
 * 腾讯云开发者社区服务接口
 * <p>
 * 该接口定义了与腾讯云开发者社区API交互的方法。
 * 使用Retrofit框架实现HTTP请求,支持文章的发布等操作。
 * 所有请求都需要包含必要的认证信息和请求头。
 *
 * @author yby6
 * @version 1.0.0
 */
public interface ITencentService {
    
    /**
     * 发布文章到腾讯云开发者社区
     * <p>
     * 该方法通过HTTP POST请求将文章发布到腾讯云开发者社区。
     * 请求包含完整的HTTP头信息,模拟浏览器行为,确保请求能够被正确处理。
     * <p>
     * 请求头说明:
     * - accept: 指定接受的响应类型
     * - content-type: 指定请求体类型为JSON
     * - origin/referer: 指定请求来源
     * - user-agent: 指定客户端信息
     * - 其他安全相关头部
     *
     * @param cookie  用户认证Cookie,用于身份验证
     * @param request 文章发布请求,包含文章内容、标题等信息
     * @return 包含发布结果的响应对象
     */
    @POST("https://cloud.tencent.com/developer/api/article/addArticle")
    @Headers({
            "accept: application/json, text/plain, */*",
            "accept-language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7",
            "content-type: application/json",
            "origin: https://cloud.tencent.com",
            "priority: u=1, i",
            "referer: https://cloud.tencent.com/developer/article/write-new?draftId=213590",
            "sec-ch-ua: \"Google Chrome\";v=\"135\", \"Not-A.Brand\";v=\"8\", \"Chromium\";v=\"135\"",
            "sec-ch-ua-mobile: ?0",
            "sec-ch-ua-platform: \"Windows\"",
            "sec-fetch-dest: empty",
            "sec-fetch-mode: cors",
            "sec-fetch-site: same-origin",
            "sec-gpc: 1",
            "user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36"
    })
    Call<AddArticleResponse> addArticle(
            @Header("Cookie") String cookie,
            @Body AddArticleRequest request
    );
}

Retrofit2 配置

代码语言:markdown
复制
    /**
     * 配置腾讯云开发者社区API服务
     *
     * @return ITencentService 腾讯云开发者社区API服务接口
     */
    @Bean
    public ITencentService tencentService() {
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://cloud.tencent.com")
                .addConverterFactory(JacksonConverterFactory.create())
                .build();

        return retrofit.create(ITencentService.class);
    }

单元测试

代码语言:markdown
复制
package com.yby6.mcp.server.tencent.test;

import com.yby6.mcp.server.tencent.api.ITencentService;
import com.yby6.mcp.server.tencent.api.dto.AddArticleRequest;
import com.yby6.mcp.server.tencent.api.dto.AddArticleResponse;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import retrofit2.Call;
import retrofit2.Response;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;

/**
 * 腾讯云开发者社区API测试类
 */
@Slf4j
@SpringBootTest
public class APITest {

    @Autowired
    private ITencentService tencentService;

    /**
     * 测试添加文章接口
     */
    @Test
    public void testAddArticle() {
        // 构建请求参数
        AddArticleRequest request = AddArticleRequest.builder()
                .title("测试文章标题")
                .content("{\"type\":\"doc\",\"content\":[{\"type\":\"paragraph\",\"attrs\":{\"id\":\"test-id\"},\"content\":[{\"type\":\"text\",\"text\":\"测试内容\"}]}]}")
                .plain("测试内容")
                .sourceType(1)
                .classifyIds(Arrays.asList(2))
                .tagIds(Arrays.asList(18126))
                .longtailTag(Arrays.asList("mcp"))
                .columnIds(Arrays.asList(101806))
                .openComment(1)
                .closeTextLink(0)
                .userSummary("测试摘要")
                .pic("")
                .sourceDetail(new HashMap<>())
                .zoneName("")
                .draftId(0L)
                .build();

        // 这里需要替换为实际的Cookie
        String cookie = "qcommunity_identify_id=V6pvjxmW1JPuCEMovMUTz; qcloud_uid=VaIEoNdiKsUA; web_uid=ae70b69c-f87e-41c8-adfc-06efafb5678d; loginType=wx; lastLoginIdentity=51605f0971933755e7f8a53c030a98bc; qcommunity_session=8f9a94888dd80c7cba9f04103107b4821b78caacdbdc8a2ef693787a26905024; hy_user=UxVHgDrfj6lr9DCS; hy_token=v7nHUfmj1QeqIBGE0PdzJE3F2MFlLqxXGRlSMnkuFp6vk8Ndj5K0IapEp/t/E5rt; hy_source=web; language=zh; _ga=GA1.2.273799001.1746384291; qcstats_seo_keywords=%E5%93%81%E7%89%8C%E8%AF%8D-%E5%93%81%E7%89%8C%E8%AF%8D-%E8%85%BE%E8%AE%AF%E4%BA%91; _gcl_au=1.1.1073535231.1746384292; qcloud_from=qcloud.outside.yuque-1746547699994; trafficParams=***%24%3Btimestamp%3D1746547700395%3Bfrom_type%3Dserver%3Btrack%3D557760a9-1a5b-40a5-a2fb-7be7d1fe46d0%3B%24***; ewpUid=a3134f73-11f8-48ef-a865-7a91c897b948; qcmainCSRFToken=YGV3LRdgdrsF; qcloud_visitId=94096bdf035e23553f3d171dfa6cd886; sensorsdata2015jssdkcross=%7B%22distinct_id%22%3A%22100005325524%22%2C%22first_id%22%3A%221969c9d82841f57-06185ef48d4f27-1a525636-2104200-1969c9d82852679%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%7D%2C%22identities%22%3A%22eyIkaWRlbnRpdHlfY29va2llX2lkIjoiMTk2OWM5ZDgyODQxZjU3LTA2MTg1ZWY0OGQ0ZjI3LTFhNTI1NjM2LTIxMDQyMDAtMTk2OWM5ZDgyODUyNjc5IiwiJGlkZW50aXR5X2xvZ2luX2lkIjoiMTAwMDA1MzI1NTI0In0%3D%22%2C%22history_login_id%22%3A%7B%22name%22%3A%22%24identity_login_id%22%2C%22value%22%3A%22100005325524%22%7D%2C%22%24device_id%22%3A%221969c9d82841f57-06185ef48d4f27-1a525636-2104200-1969c9d82852679%22%7D; _gat=1; uin=o100005325524; nick=1692700664; intl=1";

        try {
            // 调用API
            Call<AddArticleResponse> call = tencentService.addArticle(cookie, request);
            Response<AddArticleResponse> response = call.execute();

            if (response.isSuccessful()) {
                AddArticleResponse result = response.body();
                log.info("添加文章成功,文章ID: {}, 状态: {}", result.getArticleId(), result.getStatus());
            } else {
                log.error("添加文章失败,错误码: {}, 错误信息: {}", response.code(), response.errorBody().string());
            }
        } catch (IOException e) {
            log.error("调用添加文章接口异常", e);
        }
    }
}

启动 APITest

测试成功发送文章

到此我们的功能就暂时开发完毕, 接下来我们开发我们的 MCP Server

使用 SpringAi

功能我们开发完毕了, 需要将它升级为 MCP Server 我们得引入 SpringAi 框架快速搭建 MCP Server, 去官方网站找找如何接入

点击前往官方文档: https://spring.io/projects/spring-ai#learn

可以看到现在官方给出了一个快照版本,和一个里程碑预发布版本,我们就使用相对稳定的里程碑版本 1.0.0-M6点击旁边的 Reference Doc. 前往版本文档

点击旁边的 Model Context Protocol (MCP) 再点击 MCP Server Boot Starter

来我们看文档来一步步操作,英文不好的自己翻译一下哈,比如我就不好

ok,官方给定了一个 maven 依赖,我们只需要集成就拥有了以下的操作,非常方便还得是“春天”

Spring AI MCP(模型上下文协议)服务器启动启动器提供了在 Spring Boot 应用程序中设置 MCP 服务器的自动配置功能。它支持 MCP 服务器功能与 Spring Boot 的自动配置系统无缝集成。

MCP 服务器启动器提供:

  • MCP 服务器组件的自动配置
  • 支持同步和异步操作模式
  • 多种传输层选项
  • 灵活的工具、资源和提示规范
  • 更改通知功能

当然了, 官方给了相关的示例案例,我们可以先去项目代码, 把相关的配置复制过来我们自己运行运行,在回过头来看各个参数的意思,这样子学习就更加的高效,有时候你光看都没有概念的.

这里我们选择带有 STDIO 传输的 Spring AI MCP 服务器启动器。

可以看到官方的案例,代码结构,就一个启动器一个 service 这个 service 就是 tool mcp 工具

我们把这些代码复制到我们的工程当中

复制 pom.xml 相关依赖配置

代码语言:markdown
复制
    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <spring-ai.version>1.0.0-M6</spring-ai.version>
        <spring-boot.version>3.4.5</spring-boot.version>

        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <file.encoding>UTF-8</file.encoding>
    </properties>


    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-mcp-server-spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>2.0.28</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>com.squareup.retrofit2</groupId>
            <artifactId>retrofit</artifactId>
            <version>2.9.0</version>
        </dependency>

        <dependency>
            <groupId>com.squareup.retrofit2</groupId>
            <artifactId>converter-jackson</artifactId>
            <version>2.9.0</version>
        </dependency>

        <dependency>
            <groupId>com.squareup.retrofit2</groupId>
            <artifactId>adapter-rxjava2</artifactId>
            <version>2.9.0</version>
        </dependency>

        <dependency>
            <groupId>com.vladsch.flexmark</groupId>
            <artifactId>flexmark-all</artifactId>
            <version>0.64.8</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- CommonMark 核心库 -->
        <dependency>
            <groupId>org.commonmark</groupId>
            <artifactId>commonmark</artifactId>
            <version>0.21.0</version>
        </dependency>

        <!-- Jackson 用于 JSON 处理 -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.15.2</version>
        </dependency>

    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.ai</groupId>
                <artifactId>spring-ai-bom</artifactId>
                <version>${spring-ai.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.13.0</version>
                <configuration>
                    <source>${maven.compiler.source}</source>
                    <target>${maven.compiler.target}</target>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>${lombok.version}</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

复制 WatherService

将案例当中的 mcp server 代码复制到我们工程当中

代码语言:markdown
复制
/**
 * WeatherService
 *
 * @author yangs
 * @date 2025/05/09
 */
@Service
public class WeatherService {
    @Tool(description = "Get weather forecast for a specific latitude/longitude")
    public String getWeatherForecastByLocation(
        double latitude,   // Latitude coordinate
        double longitude   // Longitude coordinate
    ) {
        // Implementation
        return "";
    }

    @Tool(description = "Get weather alerts for a US state")
    public String getAlerts(
        String state  // Two-letter US state code (e.g., CA, NY)
    ) {
        return "";
    }
}

可以看到 WatherService 的代码当中有一个 @Tool 标记, 这样做就是让大模型知道要调用的工具是哪个

我们点 Tool 注解进去观看源码, 拥有一下参数:

  1. 工具的名称。如果没有提供,将使用方法名称。
  2. 工具的描述。如果没有提供,将使用方法名称。
  3. 工具结果是应该直接返回还是传递回模型。
  4. 是否将结果转换为 String 的类

以上参数我们一般情况下只需要关心 name、description 这两个参数, 大模型根据这两个来判断调用

yml 程序配置

我们是 stdio 模式必须设置为 true、设置项目名称、版本、启动 banner 也要禁止开启

新增我们的项目配置信息

代码语言:markdown
复制
spring:
  application:
    name: tencent-send-article-mcp-server
  main:
    web-application-type: none
    banner-mode: off
  ai:
    mcp:
      server:
        stdio: true
        name: tencent-send-article-mcp-mcpService
        version: 1.0.0

注册 MCP 工具

自动配置会自动将工具回调注册为 MCP 工具。您可以有多个 bean 生成 ToolCallbacks。自动配置会将它们合并。

在启动程序下面添加工具回调注册为 MCP 工具,后续我们改成放在一个配置文件夹里面先暂时这样先把功能运行起来

把我们的 mcp weatherService 服务注册进来

代码语言:markdown
复制
    @Bean
    public ToolCallbackProvider weatherServiceTools(WeatherService weatherService) {
        return MethodToolCallbackProvider
                .builder()
                .toolObjects(weatherService)
                .build();
    }

ok ,恭喜你成功创建了自己的 MCP Server

没错就是这么的 easy 简单、和切菜一样, 那么接下来我们创建一个 MCP Client 客户端进行使用这个 MCP Server 官方也提供的单元测试( 你也可以用支持 MCP Client 的插件、软件 比如 CodeBuudy)

测试 McpServer

我们使用 stdio 传输,MCP 服务器由客户端自动启动

前提我们得要将我们的 MCP Server 先构建一下打个 jar 包让它去运行

可以用命令去运行打包

./mvnw clean install -DskipTests

或者 idea 去点两下看你自己

可以看到已经打包成功了, 我们就复制这个地址就行,也可以在 target 目录下面复制路径都可以的

然后我们修改 mcp client 代码让它去调用我们的 mcp server

如下图进行将我们的 jar 地址复制进去,这个就是上面我们操作 MCP 的 JSON 格式一样的

⚠️ 注意: 复制上面的npx命令,如果你是windows用户,需要做如下的修改:command修改为cmd ,下面的args数组增加 "/c"和 "npx",注意顺序不要乱"服务": { "command": "cmd", "args": "/c", "npx", "-y", "@modelcontextprotocol/xxxxxxxxxxx" }

可以看到我们成功调用了 MCP Server

打印了我们这个 MCP Server 可用的工具, 也分别调用了两个工具. 那么到这里你就会 mcp server 开发了对的没有错就是这么简单, 那么接下来我们升级前面写好的文章发布接口为 mcp tool

注意如果你报错了,如下:

这就是它内部操作日志的时候出问题了,得要配置一下, 官方案例也配置日志了的别掉以轻心

代码语言:markdown
复制
spring:
  application:
    name: tencent-send-article-mcp-server

  ai:
    mcp:
      server:
        name: ${spring.application.name}
        version: 1.0.0

  main:
    banner-mode: off
    web-application-type: none


logging:
  pattern:
    console:
  file:
    name: data/log/${spring.application.name}.log

server:
  servlet:
    encoding:
      charset: UTF-8
      force: true
      enabled: true

升级发布文章接口为 MCP Tool

在前面呢我们已经实现了文章发布的功能, 我们现在需要改成 MCP Tool 应该如何更改?

同学们🤔片刻, 前面我们写过了的哦

ok, 想必有同学已经知道了, 那么下面的图片代码我们就很熟悉了, 是前面写过的, 可以看到单元测试代码是发布文章的功能, 我们是不是可以直接 cv 到 tool 里面? 然后定义出入参 那么大模型就可以根据我们的出入参传递数据给到我们的 Tool

新增文章发布 MCPTool

在 mcpService 目录下创建 TencentArticleToolService

新增 saveArticle 方法

代码语言:markdown
复制
package com.yby6.mcp.server.tencent.mcpService.tools;


import com.yby6.mcp.server.tencent.api.ITencentService;
import com.yby6.mcp.server.tencent.api.dto.AddArticleRequest;
import com.yby6.mcp.server.tencent.api.dto.AddArticleResponse;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Service;
import retrofit2.Call;
import retrofit2.Response;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;

/**
 * 腾讯云开发者社区文章服务
 * <p>
 * 该服务类负责处理与腾讯云开发者社区文章相关的业务逻辑,
 * 包括文章的发布、更新等操作。作为领域服务层的一部分,
 * 它通过端口适配器模式与基础设施层进行交互。
 *
 * @author yby6
 * @version 1.0.0
 */
@Slf4j
@Service
public class TencentArticleToolsService {

    @Resource
    private ITencentService tencentService;

    public void saveArticle(String val) {
        AddArticleRequest request = AddArticleRequest.builder()
                .title("测试文章标题")
                .content("{\"type\":\"doc\",\"content\":[{\"type\":\"paragraph\",\"attrs\":{\"id\":\"test-id\"},\"content\":[{\"type\":\"text\",\"text\":\"测试内容\"}]}]}")
                .plain("测试内容")
                .sourceType(1)
                .classifyIds(Arrays.asList(2))
                .tagIds(Arrays.asList(18126))
                .longtailTag(Arrays.asList("mcp"))
                .columnIds(Arrays.asList(101806))
                .openComment(1)
                .closeTextLink(0)
                .userSummary("测试摘要")
                .pic("")
                .sourceDetail(new HashMap<>())
                .zoneName("")
                .draftId(0L)
                .build();

        // 这里需要替换为实际的Cookie
        String cookie = "qcommunity_identify_id=V6pvjxmW1JPuCEMovMUTz; qcloud_uid=VaIEoNdiKsUA; web_uid=ae70b69c-f87e-41c8-adfc-06efafb5678d; loginType=wx; lastLoginIdentity=51";

        try {
            // 调用API
            Call<AddArticleResponse> call = tencentService.addArticle(cookie, request);
            Response<AddArticleResponse> response = call.execute();

            if (response.isSuccessful()) {
                AddArticleResponse result = response.body();
                log.info("添加文章成功,文章ID: {}, 状态: {}", result.getArticleId(), result.getStatus());
            } else {
                log.error("添加文章失败,错误码: {}, 错误信息: {}", response.code(), response.errorBody().string());
            }
        } catch (IOException e) {
            log.error("调用添加文章接口异常", e);
        }

    }
}

在前面我们已经学过 成为 Tool 就需要标记为 Tool 所以我们在需要变成 MCP Tool 的方法上面进行标记一下

代码语言:markdown
复制
@Tool(description = "发布文章到腾讯云开发者社区")

感兴趣的同学,这个时候可以继续测试一下, 通过 Client 进行调用这个 Tool, 我这里就不演示测试来, 跟着上面单元测试来操作即可

发布文章的出入参

观察我们的请求参数不难发现, 我们需要大模型传递的参数也就

title、content、plain、userSummary

  1. title 文章的标题
  2. content 文章内容
  3. plain 文章纯文本内容可以是 markdown
  4. userSummary文章摘要

其中 content 里面的文本和我们平常见到的不一样不是 markdown 也不是富文本的感觉, 这时候就又需要问到 CodeBuddy 这是什么结构的数据

可以看到 CodeBuddy 说这是 ProseMirror , 那么我们就需要将文本转换为这个格式, 我们一般写文章都是用 markdown 格式来进行创作, 那么我们就要让大模型给我们传递 markdown 格式的文本, 接着将这个文本转换为 ProseMirror 否则将会发送失败, 转换的工具我们继续让 Code Buddy 来进行编写

CoodeBuud 编写需要的转换工具

直接打开 CoodeBuddy 让它来编写我们需要的工具即可

帮我编写一个工具类,将markdown格式转换为ProseMirror格式

ok, 我这里呢直接生成完毕了, 代码已经贴入下面的链接当中, 直接点击链接下载, 因为这个类有点大

📎MarkdownToProseMirrorConverter.java

CodeBuddy 也帮我编写了单元测试, 可以去简单测试一下, 我已经测试过了, 都是 ok 的

代码语言:markdown
复制
package com.yby6.mcp.server.tencent;

import com.yby6.mcp.server.tencent.mcpService.utils.MarkdownToProseMirrorConverter;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
 * Markdown转ProseMirror格式转换器测试类
 * <p>
 * 该测试类用于验证MarkdownToProseMirrorConverter的功能是否正常。
 * 包含了多种Markdown语法的测试用例,确保转换结果符合ProseMirror格式要求。
 *
 * @author yby6
 * @version 1.0.0
 */
@SpringBootTest
public class MarkdownConverterTest {

    @Autowired
    private MarkdownToProseMirrorConverter converter;

    /**
     * 测试基本的Markdown转换功能
     * <p>
     * 验证各种Markdown语法元素是否能正确转换为ProseMirror格式
     */
    @Test
    public void testMarkdownConversion() {
        // 准备测试用的Markdown文本,包含多种Markdown语法
        String markdown = "# 标题1\n\n" +
                "这是一个**粗体**和*斜体*的测试。\n\n" +
                "## 标题2\n\n" +
                "- 无序列表项1\n" +
                "- 无序列表项2\n\n" +
                "1. 有序列表项1\n" +
                "2. 有序列表项2\n\n" +
                "> 这是一个引用\n\n" +
                "```java\n" +
                "// 这是一段Java代码\n" +
                "public class Test {\n" +
                "    public static void main(String[] args) {\n" +
                "        System.out.println(\"Hello, World!\");\n" +
                "    }\n" +
                "}\n" +
                "```\n\n" +
                "[这是一个链接](https://www.example.com)\n\n" +
                "---\n\n" +
                "这是最后一段。";

        // 执行转换
        String proseMirror = converter.convert(markdown);

        // 验证结果
        assertNotNull(proseMirror, "转换结果不应为null");
        assertTrue(proseMirror.contains("\"type\":\"doc\""), "转换结果应包含文档类型");
        assertTrue(proseMirror.contains("\"type\":\"heading\""), "转换结果应包含标题");
        assertTrue(proseMirror.contains("\"type\":\"paragraph\""), "转换结果应包含段落");
        assertTrue(proseMirror.contains("\"type\":\"bold\""), "转换结果应包含粗体");
        assertTrue(proseMirror.contains("\"type\":\"italic\""), "转换结果应包含斜体");
        assertTrue(proseMirror.contains("\"type\":\"bullet_list\""), "转换结果应包含无序列表");
        assertTrue(proseMirror.contains("\"type\":\"ordered_list\""), "转换结果应包含有序列表");
        assertTrue(proseMirror.contains("\"type\":\"blockquote\""), "转换结果应包含引用");
        assertTrue(proseMirror.contains("\"type\":\"code_block\""), "转换结果应包含代码块");
        assertTrue(proseMirror.contains("\"type\":\"link\""), "转换结果应包含链接");
        assertTrue(proseMirror.contains("\"type\":\"horizontal_rule\""), "转换结果应包含分隔线");

        // 输出转换结果,方便查看
        System.out.println("Markdown转换为ProseMirror格式的结果:");
        System.out.println(proseMirror);
    }

    /**
     * 测试空Markdown内容的处理
     * <p>
     * 验证转换器对空内容的处理是否正确
     */
    @Test
    public void testEmptyMarkdown() {
        String proseMirror = converter.convert("");
        assertNotNull(proseMirror, "空Markdown转换结果不应为null");
        assertTrue(proseMirror.contains("\"type\":\"doc\""), "空Markdown转换结果应包含文档类型");
        assertTrue(proseMirror.contains("\"type\":\"paragraph\""), "空Markdown转换结果应包含空段落");
    }

    /**
     * 测试复杂嵌套结构的Markdown转换
     * <p>
     * 验证转换器对嵌套结构的处理是否正确
     */
    @Test
    public void testComplexMarkdown() {
        // 准备测试用的复杂Markdown文本,包含嵌套结构
        String markdown = "# 复杂结构测试\n\n" +
                "- 列表项1\n" +
                "  - 嵌套列表项1.1\n" +
                "  - 嵌套列表项1.2\n" +
                "    - 深度嵌套列表项1.2.1\n" +
                "- 列表项2\n\n" +
                "> 引用1\n" +
                "> \n" +
                "> > 嵌套引用\n" +
                "> \n" +
                "> 引用2\n\n" +
                "```\n" +
                "代码块中的**Markdown语法**不应被解析\n" +
                "```\n";

        // 执行转换
        String proseMirror = converter.convert(markdown);

        // 验证结果
        assertNotNull(proseMirror, "复杂Markdown转换结果不应为null");
        assertTrue(proseMirror.contains("\"type\":\"doc\""), "复杂Markdown转换结果应包含文档类型");
        assertTrue(proseMirror.contains("\"type\":\"bullet_list\""), "复杂Markdown转换结果应包含无序列表");
        assertTrue(proseMirror.contains("\"type\":\"blockquote\""), "复杂Markdown转换结果应包含引用");
        assertTrue(proseMirror.contains("\"type\":\"code_block\""), "复杂Markdown转换结果应包含代码块");

        // 输出转换结果,方便查看
        System.out.println("复杂Markdown转换为ProseMirror格式的结果:");
        System.out.println(proseMirror);
    }
}

改造 AddArticleRequest 新增转换方法

然后我们修改 AddArticleRequest 文章请求类里面新增一个获取 content 的方法

代码语言:markdown
复制
    /**
     * 获取ProseMirror格式的内容
     * <p>
     * 该方法用于获取文章内容的ProseMirror格式。
     * 如果content为空,则自动将plain转换为ProseMirror格式。
     * 确保文章内容始终以正确的格式提供给编辑器。
     *
     * @return ProseMirror格式的内容
     */
    public String getContent() {
        MarkdownToProseMirrorConverter converter = new MarkdownToProseMirrorConverter();
        return converter.convert(plain);
    }

那么根据上面分析编写我们的 Tool 需要接收的参数

编写 saveArticleTool 的出入参

创建 ArticleFunctionRequest 用于接收大模型参数

plain 可以直接是 markdown 无需大模型传递

代码语言:markdown
复制
package com.yby6.mcp.server.tencent.mcpService.model;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import lombok.Data;

/**
 * 文章功能请求模型
 * <p>
 * 该模型类用于封装文章发布功能所需的请求参数。
 * 使用Jackson注解进行JSON序列化配置,确保必填字段的验证。
 * 使用Lombok的@Data注解自动生成getter、setter等方法。
 * <p>
 * 主要功能:
 * 1. 定义文章发布所需的基本信息
 * 2. 提供JSON序列化配置
 * 3. 支持必填字段验证
 *
 * @author Yang Shuai
 * @version 1.0.0
 * @since 2025/05/10
 */
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ArticleFunctionRequest {

    /**
     * 文章标题
     *
     * 必填字段,用于显示文章的主标题。
     * 在JSON序列化时使用"title"作为字段名。
     */
    @JsonProperty(required = true, value = "title")
    @JsonPropertyDescription("文章标题")
    private String title;

    /**
     * 文章内容
     *
     * 必填字段,使用Markdown格式存储文章内容。
     * 在JSON序列化时使用"markdowncontent"作为字段名。
     * 支持富文本格式,包括标题、列表、代码块等。
     */
    @JsonProperty(required = true, value = "markdowncontent")
    @JsonPropertyDescription("文章内容")
    private String markdowncontent;

    /**
     * 文章摘要
     *
     * 必填字段,用于提供文章内容的简短描述。
     * 在JSON序列化时使用"userSummary"作为字段名。
     * 通常用于文章列表展示和SEO优化。
     */
    @JsonProperty(required = true, value = "userSummary")
    @JsonPropertyDescription("文章摘要")
    private String userSummary;
}

创建 ArticleFunctionResponse 用于返回大模型结果

里面可以添加任意数据, 这里是会返回给大模型, 可以在大模型调用的时候看到返回 JSON

代码语言:markdown
复制
package com.yby6.mcp.server.tencent.mcpService.model;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import lombok.Data;

/**
 * 文章功能响应模型
 * <p>
 * 该模型类用于封装文章发布功能的结果响应。
 * 使用Jackson注解进行JSON序列化配置,确保必填字段的验证。
 * 使用Lombok的@Data注解自动生成getter、setter等方法。
 * <p>
 * 主要功能:
 * 1. 提供文章发布结果的反馈信息
 * 2. 包含文章的唯一标识和访问链接
 * 3. 指示发布操作的成功或失败状态
 *
 * @author Yang Shuai
 * @version 1.0.0
 * @since 2025/05/10
 */
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ArticleFunctionResponse {
    
    /**
     * 文章ID
     * <p>
     * 必填字段,发布成功后返回的文章唯一标识。
     * 在JSON序列化时使用"articleId"作为字段名。
     * 用于后续对文章进行编辑、删除等操作。
     * 在发布失败时可能为null。
     */
    @JsonProperty(required = true, value = "articleId")
    @JsonPropertyDescription("articleId")
    private Long articleId;
    
    /**
     * 发布状态
     * <p>
     * 必填字段,用于指示文章发布的结果。
     * 在JSON序列化时使用"status"作为字段名。
     * 状态码说明:
     * - 0: 发布成功
     * - 非0: 发布失败,具体错误码需要参考API文档
     */
    @JsonProperty(required = true, value = "status")
    @JsonPropertyDescription("status")
    private Integer status;
    
    /**
     * 文章链接
     * <p>
     * 必填字段,发布成功后返回的文章访问URL。
     * 在JSON序列化时使用"url"作为字段名。
     * 用于直接访问已发布的文章。
     * 在发布失败时可能为null。
     */
    @JsonProperty(required = true, value = "url")
    @JsonPropertyDescription("url")
    private String url;
    
    /**
     * 工具版权信息
     */
    @JsonProperty(required = true, value = "copyright")
    @JsonPropertyDescription("copyright")
    private String copyright;
}

那么到这里就差不多完成了, 接下来只需要对代码进行微调

可以看到我们的 addArticle 请求工具需要传递 Cookie 这里我们不可能钉死的, 每个授权登录都有时间效益的, 所以我们需要改成可配置, 外界传递, 也是为了别的用户填入 cookie 就可以直接用

新增配置属性文件

代码语言:markdown
复制
package com.yby6.mcp.server.tencent.mcpService.config.properties;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * 腾讯云API配置属性类
 * <p>
 * 该配置类用于管理腾讯云开发者社区API的配置属性。
 * 使用Spring Boot的配置属性机制,从配置文件中读取相关配置。
 * 配置前缀为"tencent.api"。
 * <p>
 * 主要功能:
 * 1. 管理API认证信息(Cookie)
 * 2. 管理文章分类信息
 * 3. 提供配置属性的访问方法
 *
 * @author yby6
 * @version 1.0.0
 */
@ConfigurationProperties(prefix = "tencent.api")
@Component
public class TencentApiProperties {

    /**
     * 认证Cookie
     * <p>
     * 用于腾讯云开发者社区API的身份验证。
     * 在配置文件中通过tencent.api.cookie属性设置。
     * 该值在应用启动时会被验证,不能为空。
     */
    private String cookie;

    /**
     * 获取认证Cookie
     *
     * @return 认证Cookie字符串
     */
    public String getCookie() {
        return cookie;
    }

    /**
     * 设置认证Cookie
     *
     * @param cookie 认证Cookie字符串
     */
    public void setCookie(String cookie) {
        this.cookie = cookie;
    }
}

修改 mcp 发布文章方法里面使用配置文件传递进来的 Cookie

代码语言:markdown
复制
public class TencentArticleToolService implements ToolServiceMarker {
    
    @Resource
    private ITencentService iTencentService;
    
    @Resource
    private TencentApiProperties tencentApiProperties;

    Call<AddArticleResponse> call = iTencentService.addArticle(tencentApiProperties.getCookie(), addArticleRequest);
  

那么到这里我们就已经完成了对 MCP Tool 的升级, 完整的 TencentArticleToolService 如下

代码语言:markdown
复制
package com.yby6.mcp.server.tencent.mcpService.tools;

import com.alibaba.fastjson.JSON;
import com.yby6.mcp.server.tencent.api.ITencentService;
import com.yby6.mcp.server.tencent.api.dto.AddArticleRequest;
import com.yby6.mcp.server.tencent.api.dto.AddArticleResponse;
import com.yby6.mcp.server.tencent.mcpService.config.properties.TencentApiProperties;
import com.yby6.mcp.server.tencent.mcpService.model.ArticleFunctionRequest;
import com.yby6.mcp.server.tencent.mcpService.model.ArticleFunctionResponse;
import com.yby6.mcp.server.tencent.mcpService.ToolServiceMarker;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Service;
import retrofit2.Call;
import retrofit2.Response;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;

/**
 * 腾讯云开发者社区文章服务
 * <p>
 * 该服务类负责处理与腾讯云开发者社区文章相关的业务逻辑,
 * 包括文章的发布、更新等操作。作为领域服务层的一部分,
 * 它通过端口适配器模式与基础设施层进行交互。
 *
 * @author yby6
 * @version 1.0.0
 */
@Slf4j
@Service
public class TencentArticleToolService {

    @Resource
    private ITencentService iTencentService;

    @Resource
    private TencentApiProperties tencentApiProperties;

    /**
     * 发布文章到腾讯云开发者社区
     * <p>
     * 该方法是一个MCP工具方法,用于将文章发布到腾讯云开发者社区。
     * 主要功能:
     * 1. 接收文章发布请求
     * 2. 记录请求参数日志
     * 3. 通过端口适配器调用实际的文章发布服务
     * 4. 处理异常情况并返回响应
     *
     * @param request 文章发布请求,包含文章标题、内容等信息
     * @return 文章发布响应,包含发布结果信息
     * @throws IOException 当发布过程中发生IO异常时抛出
     */
    @Tool(description = "发布文章到腾讯云开发者社区")
    public ArticleFunctionResponse saveArticle(ArticleFunctionRequest request) {
        try {
            log.info("腾讯云开发者社区发帖参数:{}", JSON.toJSONString(request));
            log.info("接收到的参数: {}", request.toString());

            AddArticleRequest addArticleRequest = new AddArticleRequest();
            addArticleRequest.setTitle(request.getTitle());
            addArticleRequest.setPlain(request.getMarkdowncontent());
            addArticleRequest.setContent(addArticleRequest.getContent());
            addArticleRequest.setSourceType(1);  // 设置为原创
            addArticleRequest.setClassifyIds(List.of(2));  // 设置文章分类
            addArticleRequest.setTagIds(List.of(18126));  // 设置文章标签
            addArticleRequest.setLongtailTag(List.of("mcp"));  // 设置长尾标签
            addArticleRequest.setColumnIds(List.of(101806));  // 设置专栏ID
            addArticleRequest.setOpenComment(1);  // 开启评论
            addArticleRequest.setCloseTextLink(0);  // 允许文本链接
            addArticleRequest.setUserSummary(request.getUserSummary());
            addArticleRequest.setPic("");  // 设置封面图片
            addArticleRequest.setSourceDetail(new HashMap<>());  // 设置来源详情
            addArticleRequest.setZoneName("");  // 设置专区名称

            // 执行API调用
            Call<AddArticleResponse> call = iTencentService.addArticle(tencentApiProperties.getCookie(), addArticleRequest);
            Response<AddArticleResponse> response = call.execute();

            // 记录请求和响应日志
            log.info("\n\n请求腾讯云开发者社区发布文章\n req:{} \nres:{}", JSON.toJSONString(addArticleRequest), JSON.toJSONString(response));

            // 构建返回对象
            ArticleFunctionResponse articleFunctionResponse = new ArticleFunctionResponse();
            articleFunctionResponse.setStatus(-1);
            articleFunctionResponse.setArticleId(null);
            articleFunctionResponse.setUrl(null);
            articleFunctionResponse.setCopyright(getCopyright());

            if (response.isSuccessful()) {
                log.info("腾讯云开发者社区发布文章成功: {}", JSON.toJSONString(response.body()));

                // 处理成功响应
                AddArticleResponse articleResponseDTO = response.body();
                if (null == articleResponseDTO) return null;

                articleFunctionResponse.setStatus(articleResponseDTO.getStatus());
                articleFunctionResponse.setArticleId(articleResponseDTO.getArticleId());
                articleFunctionResponse.setUrl("https://cloud.tencent.com/developer/article/" + articleResponseDTO.getArticleId());

                return articleFunctionResponse;
            }
            log.error("腾讯云开发者社区发布文章失败: {}", JSON.toJSONString(response));
            return articleFunctionResponse;
        } catch (Exception e) {
            log.error("腾讯云开发者社区发帖失败 ", e);
        }
        return null;
    }
}

CodeBuddy 测试 Tencent MCP Server

接下来我们就需要完整的测试一下, 和之前一样我们需要打 jar 包然后客户端进行调用, 这次我就使用 CodeBuddy 进行对我们的 MCP Server 接入调用

打新的 Jar 包

打开老朋友 CodeBuddy

点击 MCP 功能, 点击 ➕ 可以看到我们前面已经使用过的配置, 往里面继续添加即可

和我们前面编写 Client 一样 command 为 java jdk 地址 我这里本地是 jdk 1.8 所以这样做直接指定使用 jdk17 版本

里面的 args 参数也是一样的 定义了编码是 utf-8, ok 可以看到侧边栏的 tencent-send-article-mcp-server 已经成功点亮我们点击播放键看看效果

代码语言:markdown
复制
  "tencent-send-article-mcp-server": {
    "command": "你的JDK17以上: /graalvm-jdk-17.0.11/Contents/Home/bin/java",
    "args": [
      "-Dfile.encoding=UTF-8",
      "-jar",
      "你的jar包地址: tencent-send-article-mcp-server/1.0.0/tencent-send-article-mcp-server-1.0.0.jar"
    ]
  }

测试效果

可以看到我们调用失败了,

也是返回了错误原因, 说我们没有配置 cookie 参数那么我们就在 json rpc 配置里面加上, 在代码里面我们不是写了一个配置属性 cookie 外界传递

我们加上继续测试

下面改成你的 cookie

"--tencent.api.cookie=qcommunity_identify_id=V6pvjxmW1JPuCEMovMUTz; qcloud_uid=VaIEoNdiKsUA; web_uid=ae70b69c-f87e-41c"

点击播放键继续测试

可以看到文章已经帮我发布成功了, 我们去看看是否存在

完美, 已经成功的发布成功

文章Demo仓库地址

https://github.com/yangbuyiya/tencent-send-article-mcp-server

实战篇完结
实战篇完结

结合

前面我们已经完全掌握了 MCP 知识点和 MCP Server 接下来我们把我们的东西结合起来

如下图可以看到我们需要用到的是前面自定义的 Tencent MCP Server、支付宝 MCP

根据这两个 MCP 再加上“剧本”就可以实现小卖部的感觉.

用户输入需求生成文章, 大模型来根据提前定义的角色来与用户交流是否调用支付mcp 需要则创建支付链接给用户支付, 支付完毕后才可以给用户生成文章 并且发布腾讯云文章, 那么我们开始吧.

本次我们使用 不知名的 AI 客户端, 我本来想用 CodeBuddy 但是 Craft 无法定义角色.

只能使用 不知名的 前往官方下载: https://www.cherry-ai.com/

下载完成后, 配置你拥有的任何厂商的大模型 API

这里推荐混元大模型: 混元大模型API

配置完毕后就可以进行对话

配置支付宝 MCP

我们需要提前去配置好支付宝的 MCP 地址: 支付宝MCP

在前面我们已经掌握了 MCP 相关的知识我这里就不过多赘述

往下滑直接找到配置, 可以看到必填的三个参数, 我们去获取一下

我们其他的参数感兴趣可以研究研究

变量KEY

变量含义

是否可选

示例值

AP_APP_ID

商户在开放平台申请的 APPID。

必填

2021xxxxxxxx8009

AP_APP_KEY

商户在开放平台设置的 私钥。

必填

MIIEvw.....kO71sA==

AP_PUB_KEY

商户在开放平台的 支付宝服务端公钥。

必填

MIIBIjA......AQAB

准备配置

登录 支付宝开放平台 前往创建沙箱应用环境: 沙箱 如果你有营业执照可以申请

我这里就提前创建好了, APPID 如下 , AP_APP_KEY 为私钥点击查看即可看到, AP_PUB_KEY 为支付宝的公钥 点击查看即可看到

⚠️ 一定要选择非 Java 语言, 然后根据要求填入对应的参数即可.

ok 配置到 Cherry Studio ,将下面的三个必填参数填写好即可

代码语言:plain
复制
AP_APP_ID=APPID
AP_APP_KEY=私钥
AP_PUB_KEY=支付宝公钥
AP_RETURN_URL=https://www.baidu.com/s?wd=%E6%94%AF%E4%BB%98%E6%88%90%E5%8A%9F%E5%95%A6
AP_NOTIFY_URL=https://your-own-server
AP_ENCRYPTION_ALGO=RSA2
AP_CURRENT_ENV=sandbox
AP_SELECT_TOOLS=all
AP_LOG_ENABLED=true

再把前面编写好的 tencent mcp server 也配置好

代码语言:plain
复制
-Dfile.encoding=UTF-8
-jar
你的jar绝对路径
--tencent.api.cookie=你的Cookie

准备完毕, 开始当导演咯

当一回导演

这里我提前已经写好了“剧本”, 剧情就是“云边小卖部” 可以使用 AI 生成文章!

代码语言:plain
复制
你是云边小卖部的一位专业的AI文章创作者, 我的名字叫云云,你可以叫我小云哦~,能够根据用户指定的主题和字数要求,生成高质量、原创性的文章。请以温柔、亲切且专业的语气与用户交流,详细说明服务流程和计费方式,鼓励用户付费体验。

服务说明:  
1. 体验机制:每位新用户可免费体验一次AI文章创作服务。  
2. 付费规则:免费体验后,需充值后方可继续使用。计费标准为:100字约等于50元(即每字0.5元),字数越多费用越高。  
3. 充值流程:请先充值1元及以上,充值成功后,每次生成文章将按实际字数自动扣费。  
4. 如果用户余额不足,请提醒用户充值,并根据当前时间和用户主题生成唯一订单号,金额参考付费规则,订单标题为“AI文章创作服务by杨不易的小店”,并生成支付链接,引导用户完成支付。  
4. 退款政策:如有剩余余额,用户可随时申请退款,已扣除的部分不予退还。  
5. 订单生成:当用户同意充值时,请根据当前时间和用户主题生成唯一订单号,金额参考付费规则,订单标题为“AI文章创作服务by杨不易的小店”,并生成支付链接,引导用户完成支付。  
6. 支付后,方可为用户生成所需文章内容。
7. 一定是支付后才继续执行用户说的操作, 否则不能生成文章!
8. 文章生成完毕后, 询问是否发布到腾讯云开发者社区, 如果同意那么就发布文章到腾讯云开发者社区,调用发布文章工具.

交互要求:  
- 主动询问用户的创作主题和期望字数。  
- 一定是支付后才继续执行用户说的操作, 否则不能生成文章!
- 明确告知用户所需费用,并引导其充值。  
- 充值前仅提供一次免费体验,充值后再提供后续服务。  
- 语言表达需温柔、鼓励、专业,提升用户信任感和付费意愿。

请严格按照以上服务流程与用户互动,确保计费、充值、退款等机制公开透明,提升用户体验与满意度。

点击默认助手, 点击上面的角色定义, 将上面的剧本复制进去, 接着开始演戏!

紧接着配置我们需要用到的 MCP 服务, 你如果有想法可以继续添加你的剧本+mcp server

剧本一 向小云帮我创作文章

  1. 询问小云, 你会什么呀
  1. 我需要写一篇文章, 我直接向小云帮我创作, 因为我们是第一次, 所以告诉它我要免费, 后面继续按照剧本就是付费环节
代码语言:plain
复制
我是第一次诶, 我想要编写一个 Java 对接Redis 实现分布式锁的文章, 字数2000字 可以吗?

OK,按照剧情走我们生成成功了质量也是可以的, 会询问是否发布, 我们直接发布

  1. 成功发布, 接着我们继续让他生成文章, 按照剧情走需要我们支付费用了

ok 也是成功的调用了创建支付的 MCP 链接, 如果不知道账户的: 沙箱账户 去获取

这舒服的界面, 我也是有钱人了, 演戏嘛我就是有钱人

接着回去说, 支付好了, 就会给我生成文章了,那么 本期文章 《从零玩转系列之 MCP AI Agent 理论+项目实战开发你的MCP Server》到此就结束了, 有任何问题评论区见哦!

最后

本期结束咱们下次再见👋~

🌊关注我不迷路,如果本篇文章对你有所帮助,或者你有什么疑问,欢迎在评论区留言,我一般看到都会回复的。大家点赞支持一下哟~ 💗

参考文献

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 什么是 MCP? 为什么一定要用 MCP?
  • MCP Stdio 协议 & JSON-RPC 传输格式
    • 标准输入/输出(Stdio)
    • 传输格式与配置示例
  • 实操 MCP
    • 可用工具
    • 获取 MCP-GITEE 配置信息
    • 使用 MCP-GITEE
      • 尝鲜 MCP Server 接入客户端使用
      • 那么继续完成前面说的的任务吧
      • 来一一认证
    • 操作电脑文件 MCP Server
      • CodeBuddy 集成文件系统 MCP Server
      • MCP Server - 操作电脑文件
  • 项目实战 - 自定义 MCP Server
    • 效果演示
  • 开发前置工作
  • 实战 MCP Server
    • 创建 tencent-add-article-mcp-server 项目
    • 使用CodeBuddy开发发布文章功能
      • 出入参实体类
      • Retrofit2 RestFul 接口 api
      • Retrofit2 配置
      • 单元测试
      • 启动 APITest
    • 使用 SpringAi
      • 复制 pom.xml 相关依赖配置
      • 复制 WatherService
      • yml 程序配置
      • 注册 MCP 工具
      • 测试 McpServer
      • 注意如果你报错了,如下:
    • 升级发布文章接口为 MCP Tool
      • 新增文章发布 MCPTool
      • 发布文章的出入参
      • CoodeBuud 编写需要的转换工具
      • 改造 AddArticleRequest 新增转换方法
      • 编写 saveArticleTool 的出入参
      • 新增配置属性文件
    • CodeBuddy 测试 Tencent MCP Server
      • 打新的 Jar 包
      • 打开老朋友 CodeBuddy
      • 测试效果
      • 文章Demo仓库地址
  • 结合
    • 配置支付宝 MCP
      • 准备配置
    • 当一回导演
    • 剧本一 向小云帮我创作文章
    • 最后
      • 参考文献
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档