引用一些官方的介绍吧:

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 的架构设计时序图:

bytemd-mermaid-1742475232037-0{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#bytemd-mermaid-1742475232037-0 .error-icon{fill:#552222;}#bytemd-mermaid-1742475232037-0 .error-text{fill:#552222;stroke:#552222;}#bytemd-mermaid-1742475232037-0 .edge-thickness-normal{stroke-width:2px;}#bytemd-mermaid-1742475232037-0 .edge-thickness-thick{stroke-width:3.5px;}#bytemd-mermaid-1742475232037-0 .edge-pattern-solid{stroke-dasharray:0;}#bytemd-mermaid-1742475232037-0 .edge-pattern-dashed{stroke-dasharray:3;}#bytemd-mermaid-1742475232037-0 .edge-pattern-dotted{stroke-dasharray:2;}#bytemd-mermaid-1742475232037-0 .marker{fill:#333333;stroke:#333333;}#bytemd-mermaid-1742475232037-0 .marker.cross{stroke:#333333;}#bytemd-mermaid-1742475232037-0 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#bytemd-mermaid-1742475232037-0 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#bytemd-mermaid-1742475232037-0 text.actor>tspan{fill:black;stroke:none;}#bytemd-mermaid-1742475232037-0 .actor-line{stroke:grey;}#bytemd-mermaid-1742475232037-0 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#bytemd-mermaid-1742475232037-0 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#bytemd-mermaid-1742475232037-0 #arrowhead path{fill:#333;stroke:#333;}#bytemd-mermaid-1742475232037-0 .sequenceNumber{fill:white;}#bytemd-mermaid-1742475232037-0 #sequencenumber{fill:#333;}#bytemd-mermaid-1742475232037-0 #crosshead path{fill:#333;stroke:#333;}#bytemd-mermaid-1742475232037-0 .messageText{fill:#333;stroke:none;}#bytemd-mermaid-1742475232037-0 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#bytemd-mermaid-1742475232037-0 .labelText,#bytemd-mermaid-1742475232037-0 .labelText>tspan{fill:black;stroke:none;}#bytemd-mermaid-1742475232037-0 .loopText,#bytemd-mermaid-1742475232037-0 .loopText>tspan{fill:black;stroke:none;}#bytemd-mermaid-1742475232037-0 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#bytemd-mermaid-1742475232037-0 .note{stroke:#aaaa33;fill:#fff5ad;}#bytemd-mermaid-1742475232037-0 .noteText,#bytemd-mermaid-1742475232037-0 .noteText>tspan{fill:black;stroke:none;}#bytemd-mermaid-1742475232037-0 .activation0{fill:#f4f4f4;stroke:#666;}#bytemd-mermaid-1742475232037-0 .activation1{fill:#f4f4f4;stroke:#666;}#bytemd-mermaid-1742475232037-0 .activation2{fill:#f4f4f4;stroke:#666;}#bytemd-mermaid-1742475232037-0 .actorPopupMenu{position:absolute;}#bytemd-mermaid-1742475232037-0 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#bytemd-mermaid-1742475232037-0 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#bytemd-mermaid-1742475232037-0 .actor-man circle,#bytemd-mermaid-1742475232037-0 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#bytemd-mermaid-1742475232037-0 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}UserCherryStudioServerOllama打开软件SSE 兄弟,我们聊会SSE 好,你有事的话 POST 这个地址(endpoint)POST 兄弟,自我介绍一下(initalize)SSE 好,这是我的基本信息(serverInfo)POST 兄弟,我收到了,我准备好了(initialized)POST: 兄弟,你有MCP的工具吗(tools/list)SSE 我提供了几个工具(tools)输入: 禁用张三的账号POST: 带工具调用 禁用张三的账号意图识别: {工具:禁用账号,参数:张三}POST 请求发送 {工具:禁用账号,参数:张三}执行工具并 SSE 推送结果整理下收到的结果返回处理后的结果显示给用户看UserCherryStudioServerOllama

开始开发
有了架构图了,那开发起来倒是没有什么难事了:
当然,你可以使用官网提供的一些 SDK 来做,不过吧,很多问题,你可以先试试了来评论区讨论~。。。
我们就不考虑上 SDK 啦,直接在项目里生撸!
项目技术栈

运行时:Java17
框架: SpringBoot
ORM: JPA

来吧,直接开始。
MCP 的基础数据结构
基础结构
json 代码解读复制代码{
"id": 0,
"jsonrpc": "2.0"
}

请求结构 extends 基础结构
所有发送给 MCP 服务器的请求都是这个结构:
ts 代码解读复制代码interface Request {
// 请求的ID
id: number

// 请求的协议 固定2.0
jsonrpc: "2.0";

// 请求的方法
method: string;

// 请求的参数
params?: { ... };
}

例如 方法 initalize 的请求结构:
json 代码解读复制代码{
"id": 0,
"jsonrpc": "2.0",
"method": "initalize",
"params": {

// 客户端的一些能力
"capabilities": {},
"clientInfo": {
  // 一些客户端信息,比如名称、版本等
}

}
}

又例如 函数调用的 请求结构
json 代码解读复制代码{
"id": 1,
"jsonrpc": "2.0",
"method": "tools/call",
"params": {

"name": "disableUserByName",
"arguments": {
  "name": "张三"
}

}
}

响应结构 extends 基础结构
所有通过 SSE 推送给客户端的响应都是这个结构:
ts 代码解读复制代码interface Response {
id: 0;
jsonrpc: "2.0";
result: {

// 一些数据信息

};
error: {

// 一些错误信息

};
}

SSE 服务
SpringBoot 下开启一个 SSE 服务简单得不要不要的:
java 代码解读复制代码public final static ConcurrentHashMap<String, SseEmitter> EMITTERS = new ConcurrentHashMap<>();

@GetMapping(value = "sse", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
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 请求:
java 代码解读复制代码@PostMapping("messages")
public Json messages(HttpServletRequest request, @RequestBody 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 通道推送回去。

initalize 初始化
初始化请求需要响应给客户端的是服务器的一些基本信息:
json 代码解读复制代码{
id: id,
jsonrpc: "2.0",
result: {

// 一些服务能力
capabilities: {},
serverInfo: {
  name: "服务器名称",
  version: "1.0.0"
}

}
}

这时候,客户端已经可以显示服务器的基本信息了。
请求工具列表
SSE 服务器收到到请求后,需要响应给客户端的是工具列表:
json 代码解读复制代码{
"id": 0,
"jsonrpc": "2.0",
"result": {

"tools": [
  {
    "name": "disableUserByName",
    "description": "禁用一个用户的账号",
    "inputSchema": {
      "type": "object",
      "properties": {
        "nickname": {
          "type": "string",
          "description": "名称"
        }
      },
      "required": ["nickname"]
    }
  }
]

}
}

执行工具
SSE 服务器需要执行工具时,会得到这个结构体:
json 代码解读复制代码{
"id": 1,
"jsonrpc": "2.0",
"method": "tools/call",
"params": {

"name": "disableUserByName",
"arguments": {
  "name": "张三"
}

}
}

你可以在执行一些代码后,返回下面的结构体:
json 代码解读复制代码{
"id": 1,
"jsonrpc": "2.0",
"result": {

"content": [
  {
    "type": "text",
    "text": "好,张三被我干掉了"
  }
]

}
}

到这里,几乎完成了整个流程。
基于注解的封装
我们因为使用的 Java 和 SpringBoot, 所以我们使用了 @McpMethod 注解配合 Reflections 来实现自动注册工具。
java 代码解读复制代码@McpMethod("modifyEmailByName")
@Description("modify user new email by name")
public String modifyEmailByName(

    @Description("the name of user, e.g. 凌小云")
    String name,
    @Description("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: github.com/s-pms/SPMS-…
AirPower4J: github.com/HammCn/AirP…

weibo.com/ttarticle/p/show?id=2309405146374575620319
weibo.com/ttarticle/p/show?id=2309405146374525289464
weibo.com/ttarticle/p/show?id=2309405146374479151172
weibo.com/ttarticle/p/show?id=2309405146374428557345
weibo.com/ttarticle/p/show?id=2309405146374378488066
weibo.com/ttarticle/p/show?id=2309405146374327894127
weibo.com/ttarticle/p/show?id=2309405146374277824758
weibo.com/ttarticle/p/show?id=2309405146374227493124
weibo.com/ttarticle/p/show?id=2309405146374177161511
weibo.com/ttarticle/p/show?id=2309405146374126567671
weibo.com/ttarticle/p/show?id=2309405146374080692574
weibo.com/ttarticle/p/show?id=2309405146374030360759
weibo.com/ttarticle/p/show?id=2309405146373980029598
weibo.com/ttarticle/p/show?id=2309405146373929697591
weibo.com/ttarticle/p/show?id=2309405146373883559971
weibo.com/ttarticle/p/show?id=2309405146373833228778
weibo.com/ttarticle/p/show?id=2309405146373787090961
weibo.com/ttarticle/p/show?id=2309405146373732565216
weibo.com/ttarticle/p/show?id=2309405146373682233706
weibo.com/ttarticle/p/show?id=2309405146373631902129
weibo.com/ttarticle/p/show?id=2309405146373585764483
weibo.com/ttarticle/p/show?id=2309405146373535170602
weibo.com/ttarticle/p/show?id=2309405146373485101944
weibo.com/ttarticle/p/show?id=2309405146373438963863
weibo.com/ttarticle/p/show?id=2309405146373388632203
weibo.com/ttarticle/p/show?id=2309405146373338301628
weibo.com/ttarticle/p/show?id=2309405146373287706762
weibo.com/ttarticle/p/show?id=2309405146373229249338
weibo.com/ttarticle/p/show?id=2309405146373170528732
weibo.com/ttarticle/p/show?id=2309405146373120196674
https://weibo.com/ttarticle/p/show?id=2309405146374575620319
https://weibo.com/ttarticle/p/show?id=2309405146374525289464
https://weibo.com/ttarticle/p/show?id=2309405146374479151172
https://weibo.com/ttarticle/p/show?id=2309405146374428557345
https://weibo.com/ttarticle/p/show?id=2309405146374378488066
https://weibo.com/ttarticle/p/show?id=2309405146374327894127
https://weibo.com/ttarticle/p/show?id=2309405146374277824758
https://weibo.com/ttarticle/p/show?id=2309405146374227493124
https://weibo.com/ttarticle/p/show?id=2309405146374177161511
https://weibo.com/ttarticle/p/show?id=2309405146374126567671
https://weibo.com/ttarticle/p/show?id=2309405146374080692574
https://weibo.com/ttarticle/p/show?id=2309405146374030360759
https://weibo.com/ttarticle/p/show?id=2309405146373980029598
https://weibo.com/ttarticle/p/show?id=2309405146373929697591
https://weibo.com/ttarticle/p/show?id=2309405146373883559971
https://weibo.com/ttarticle/p/show?id=2309405146373833228778
https://weibo.com/ttarticle/p/show?id=2309405146373787090961
https://weibo.com/ttarticle/p/show?id=2309405146373732565216
https://weibo.com/ttarticle/p/show?id=2309405146373682233706
https://weibo.com/ttarticle/p/show?id=2309405146373631902129
https://weibo.com/ttarticle/p/show?id=2309405146373585764483
https://weibo.com/ttarticle/p/show?id=2309405146373535170602
https://weibo.com/ttarticle/p/show?id=2309405146373485101944
https://weibo.com/ttarticle/p/show?id=2309405146373438963863
https://weibo.com/ttarticle/p/show?id=2309405146373388632203
https://weibo.com/ttarticle/p/show?id=2309405146373338301628
https://weibo.com/ttarticle/p/show?id=2309405146373287706762
https://weibo.com/ttarticle/p/show?id=2309405146373229249338
https://weibo.com/ttarticle/p/show?id=2309405146373170528732
https://weibo.com/ttarticle/p/show?id=2309405146373120196674
https://weibo.com/ttarticle/x/m/show/id/2309405146374575620319
https://weibo.com/ttarticle/x/m/show/id/2309405146374525289464
https://weibo.com/ttarticle/x/m/show/id/2309405146374479151172
https://weibo.com/ttarticle/x/m/show/id/2309405146374428557345
https://weibo.com/ttarticle/x/m/show/id/2309405146374378488066
https://weibo.com/ttarticle/x/m/show/id/2309405146374327894127
https://weibo.com/ttarticle/x/m/show/id/2309405146374277824758
https://weibo.com/ttarticle/x/m/show/id/2309405146374227493124
https://weibo.com/ttarticle/x/m/show/id/2309405146374177161511
https://weibo.com/ttarticle/x/m/show/id/2309405146374126567671
https://weibo.com/ttarticle/x/m/show/id/2309405146374080692574
https://weibo.com/ttarticle/x/m/show/id/2309405146374030360759
https://weibo.com/ttarticle/x/m/show/id/2309405146373980029598
https://weibo.com/ttarticle/x/m/show/id/2309405146373929697591
https://weibo.com/ttarticle/x/m/show/id/2309405146373883559971
https://weibo.com/ttarticle/x/m/show/id/2309405146373833228778
https://weibo.com/ttarticle/x/m/show/id/2309405146373787090961
https://weibo.com/ttarticle/x/m/show/id/2309405146373732565216
https://weibo.com/ttarticle/x/m/show/id/2309405146373682233706
https://weibo.com/ttarticle/x/m/show/id/2309405146373631902129
https://weibo.com/ttarticle/x/m/show/id/2309405146373585764483
https://weibo.com/ttarticle/x/m/show/id/2309405146373535170602
https://weibo.com/ttarticle/x/m/show/id/2309405146373485101944
https://weibo.com/ttarticle/x/m/show/id/2309405146373438963863
https://weibo.com/ttarticle/x/m/show/id/2309405146373388632203
https://weibo.com/ttarticle/x/m/show/id/2309405146373338301628
https://weibo.com/ttarticle/x/m/show/id/2309405146373287706762
https://weibo.com/ttarticle/x/m/show/id/2309405146373229249338
https://weibo.com/ttarticle/x/m/show/id/2309405146373170528732
https://weibo.com/ttarticle/x/m/show/id/2309405146373120196674


睿智的篮球_lHiLC
1 声望0 粉丝