关注更多AI编程资讯请去AI Coding专区:https://juejin.cn/aicoding
什么是 MCP
❓
引用一些官方的介绍吧:
❝
Model Context Protocol
(MCP
) 是一个开放协议,它使LLM
应用与外部数据源和工具之间的无缝集成成为可能。无论你是构建 AI 驱动的 IDE、改善 chat 交互,还是构建自定义的 AI 工作流,MCP 提供了一种标准化的方式,将 LLM 与它们所需的上下文连接起来。❞
大白话就是一个数据通信的应用协议,约定了应用和大模型之间如何传递数据进行无缝连接。
「本文主要讲的是 MCP
的 SSE+HTTP
方式的使用。」
先举个荔枝吧:)
当下背景
-
服务器通过 Ollama
部署了一些乱七八糟的模型,用于提供给公司内部的朋友们使用。 -
另一台服务器上有一个公司内部的 ERP
系统,管理着公司大量的数据信息。 -
你从隔壁社区听到了 「MCP」 的概念。
那我们能在这个背景下玩一些什么事情呢?
先看截图:

❝
我们使用的客户端是 「CherryStudio」,左边是我们的
ERP
系统,右边是Ollama
跑的一个小 「7B」 的通义千问开源模型。❞
我们直接通过 「CherryStudio」 的 MCP
协议接入功能,直接和 ERP
系统进行通信,实现 ERP
系统的数据查询和操作。
如果我们把 「CherryStudio」 换成手机上的 「Siri」,身边的小爱同学呢?
❝
Siri 可以通过快捷指令来完成,小爱同学可以通过小爱技能来完成,当然,体验肯定没有直接内置 MCP 来得快体验好。
❞
着手分析
首先,我们先了解一下 「MCP」 的架构设计时序图:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
sequenceDiagram
participant User as User
participant CherryStudio as CherryStudio
participant Server as Server
participant Ollama as Ollama
User->>CherryStudio: 打开软件
CherryStudio-->>Server: **SSE** 兄弟,我们聊会
Server--)CherryStudio: **SSE** 好,你有事的话 POST 这个地址(endpoint)
CherryStudio-->>Server: **POST** 兄弟,自我介绍一下(initalize)
Server--)CherryStudio: **SSE** 好,这是我的基本信息(serverInfo)
CherryStudio-->>Server: **POST** 兄弟,我收到了,我准备好了(initialized)
CherryStudio-->>Server: POST: 兄弟,你有MCP的工具吗(tools/list)
Server--)CherryStudio: **SSE** 我提供了几个工具(tools)
User->>CherryStudio: 输入: 禁用张三的账号
CherryStudio->>Ollama: POST: 带工具调用 `禁用张三的账号`
Ollama-)CherryStudio: 意图识别: {工具:禁用账号,参数:张三}
CherryStudio-->>Server: **POST** 请求发送 {工具:禁用账号,参数:张三}
Server-->>CherryStudio: 执行工具并 **SSE** 推送结果
CherryStudio->>Ollama: 整理下收到的结果
Ollama-)CherryStudio: 返回处理后的结果
CherryStudio-)User: 显示给用户看
开始开发
有了架构图了,那开发起来倒是没有什么难事了:
当然,你可以使用官网提供的一些 「SDK」 来做,不过吧,很多问题,你可以先试试了来评论区讨论~。。。
我们就不考虑上 「SDK」 啦,直接在项目里生撸!
「项目技术栈」
-
运行时:「Java17」 -
框架: 「SpringBoot」 -
ORM: 「JPA」
来吧,直接开始。
「MCP」 的基础数据结构
基础结构
ounter(lineounter(lineounter(lineounter(line
{
"id": 0,
"jsonrpc": "2.0"
}
请求结构 extends 基础结构
所有发送给 「MCP」 服务器的请求都是这个结构:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
interface Request {
// 请求的ID
id: number
// 请求的协议 固定2.0
jsonrpc: "2.0";
// 请求的方法
method: string;
// 请求的参数
params?: { ... };
}
例如 方法 initalize
的请求结构:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
{
"id": 0,
"jsonrpc": "2.0",
"method": "initalize",
"params": {
// 客户端的一些能力
"capabilities": {},
"clientInfo": {
// 一些客户端信息,比如名称、版本等
}
}
}
又例如 函数调用的 请求结构
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
{
"id": 1,
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "disableUserByName",
"arguments": {
"name": "张三"
}
}
}
响应结构 extends 基础结构
所有通过 「SSE」 推送给客户端的响应都是这个结构:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
interface Response {
id: 0;
jsonrpc: "2.0";
result: {
// 一些数据信息
};
error: {
// 一些错误信息
};
}
SSE 服务
SpringBoot 下开启一个 「SSE」 服务简单得不要不要的:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
public final static ConcurrentHashMap<String, SseEmitter> EMITTERS = new ConcurrentHashMap<>();
public SseEmitter connect() throws IOException {
String uuid = UUID.randomUUID().toString();
SseEmitter emitter = new SseEmitter();
sseEmitter.send(SseEmitter.event()
.name("endpoint")
.data("/mcp/messages?sessionId=" + uuid)
.build()
);
EMITTERS.put(uuid, emitter);
// 可以加点心跳
emitter.onCompletion(() -> EMITTERS.remove(uuid));
emitter.onTimeout(() -> EMITTERS.remove(uuid));
return emitter;
return sseEmitter;
}
❝
这里需要注意的是,「MCP」 要求连接上后必须发送一次消息,内容是 「MCP」 服务用于接受 「POST」 请求的 URL。
❞
好,这个服务有了,客户端就可以通过这个服务来收我们要下发的消息了。
Message POST API
接下来,我们来实现这个复杂一点的 「POST」 请求:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
"messages") (
public Json messages(HttpServletRequest request, McpRequest mcpRequest) {
String uuid = request.getParameter("sessionId");
if (Objects.isNull(uuid)) {
return Json.error("sessionId is required");
}
String method = mcpRequest.getMethod();
switch(method){
case "initalize":
// 这个请求是初始化请求,需要返回一些服务器信息给客户端
break;
case "tools/call":
// 这个请求是工具调用请求,需要返回执行结果给客户端
break;
case "tools/list":
// 这个请求是工具列表请求,需要返回一些工具列表给客户端
break;
default:
}
}
❝
请注意,所有请求都不是 HTTP 直接响应,而是通过刚才的 「SSE」 通道推送回去。
❞
1、initalize 初始化
初始化请求需要响应给客户端的是服务器的一些基本信息:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
{
id: id,
jsonrpc: "2.0",
result: {
// 一些服务能力
capabilities: {},
serverInfo: {
name: "服务器名称",
version: "1.0.0"
}
}
}
这时候,客户端已经可以显示服务器的基本信息了。
2、请求工具列表
「SSE」 服务器收到到请求后,需要响应给客户端的是工具列表:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
{
"id": 0,
"jsonrpc": "2.0",
"result": {
"tools": [
{
"name": "disableUserByName",
"description": "禁用一个用户的账号",
"inputSchema": {
"type": "object",
"properties": {
"nickname": {
"type": "string",
"description": "名称"
}
},
"required": ["nickname"]
}
}
]
}
}
3、执行工具
「SSE」 服务器需要执行工具时,会得到这个结构体:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
{
"id": 1,
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "disableUserByName",
"arguments": {
"name": "张三"
}
}
}
你可以在执行一些代码后,返回下面的结构体:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
{
"id": 1,
"jsonrpc": "2.0",
"result": {
"content": [
{
"type": "text",
"text": "好,张三被我干掉了"
}
]
}
}
到这里,几乎完成了整个流程。
基于注解的封装
我们因为使用的 「Java」 和 「SpringBoot」, 所以我们使用了 @McpMethod
注解配合 Reflections
来实现自动注册工具。
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
"modifyEmailByName") (
"modify user new email by name") (
public String modifyEmailByName(
"the name of user, e.g. 凌小云") (
String name,
"the new email of user, e.g. example@domain.com") (
String email
) {
List<UserEntity> userList = filter(new UserEntity().setNickname(name));
DATA_NOT_FOUND.when(userList.isEmpty(), "没有叫 " + name + " 的用户");
userList.forEach(user -> {
updateToDatabase(get(user.getId()).setEmail(email));
});
return "已经将 " + userList.size() + " 个叫 " + name + " 的用户邮箱修改为 " + email;
}
只要标记了 @McpMethod
注解, MCP
服务器就会自动注册这个方法。
然后你就可以通过 「CherryStudio」 等工具来调用这个方法了。
❝
动动嘴的事情~
❞
总结
我们通过上述的方式完成了一个的 「MCP」 服务, 并且也可以为我们的一些其他系统进行扩展,用大模型来改造这些系统的使用方式,美滋滋。
当然,这里还有很多问题需要我们解决,比如权限控制。
完整的代码我们放在了我们的 「SPMS_Server」 项目以及 「AirPower4J」 基础库里了:
-
SPMS-Server: https://github.com/s-pms/SPMS-Server/tree/hamm -
AirPower4J: https://github.com/HammCn/AirPower4J/tree/hamm
展望
我倒是很悲观,现在满脑子都是 「小爱同学,把张三的辞职报告审核通过一下」。
等各种终端设备都支持 「MCP」 协议了,我们再来玩更多的事情吧。