1 MCP服务开发
1.1 MCP服务如何开发?
MCP协议的核心思想是解耦与标准化。它通过定义一套清晰的规范,使LLM能够以统一的方式访问外部工具、数据源和服务,而无需为每个工具编写特定的适配代码。
清晰的规范,到底是什么规范?
| 规范维度 | 核心内容 |
|---|---|
| 核心目标 | 解决AI集成碎片化问题,提供统一、安全的连接标准 |
| 架构模式 | 客户端-服务器 (C/S) 架构,包含Host(宿主应用)、Client(客户端)、Server(服务端) |
| 通信协议 | 应用层基于JSON-RPC 2.0;传输层支持Stdio(标准输入输出) 和基于HTTP的SSE/Streamable HTTP |
| 核心能力 | Server提供三大能力:工具(Tools,可执行函数)、资源(Resources,可读数据)、提示模板(Prompts,预定义提示) |
了解了规范有什么后,先具体看看细节,这里着重讲一下JSON-RPC 2.0格式,为后面撸代码做些铺垫
JSON-RPC 2.0输入输出示例
输入
一个 JSON-RPC 2.0 请求数据是一个单一的 JSON 对象,可以包含以下成员:
- jsonrpc:字符串,指定 JSON-RPC 的版本号,对于 2.0 规范来说,这个值必须是 2.0。
- method:字符串,指定要调用的远程方法的名称。
- params:结构化值,可以是数组或者对象,传递给远程方法的参数。如果方法不需要参数可以省略。
- id:唯一标识符,可以是字符串或数字,用于关联请求和响应,服务端必须返回相同的值。如果请求是一个通知类型,则此参数可以被省略。
@Data public class McpRequest { @JsonProperty("jsonrpc") private String jsonrpc = "2.0"; // JSON-RPC 2.0 中 id 可以是 String、Number 或 null private Object id; private String method; private Map<String, Object> params; }输出
一个 JSON-RPC 2.0 响应数据也是一个单一的 JSON 对象,可以包含以下成员:
- jsonrpc:字符串,指定 JSON-RPC 的版本号,对于 2.0 规范来说,这个值必须是2.0。
- result:当请求成功时,包含由远程方法返回的结果。如果请求失败,则不包含此成员。
- error:当请求失败时,包含一个错误对象。如果请求成功,则不包含此成员。
- id:与请求中的 id 相同,用于识别哪个请求对应的响应。
@Data @NoArgsConstructor @AllArgsConstructor @JsonPropertyOrder({"jsonrpc", "id", "result", "error"}) @JsonInclude(JsonInclude.Include.NON_NULL) public class McpResponse { @JsonProperty("jsonrpc") private String jsonrpc = "2.0"; // JSON-RPC 2.0 中 id 可以是 String、Number 或 null,必须与请求中的 id 保持一致 private Object id; private Object result; private McpError error; @Data @NoArgsConstructor @AllArgsConstructor public static class McpError { private int code; private String message; } }请求与响应示例
下表展示了不同场景下的消息示例。
场景描述 | 请求 (Client -> Server) | 响应 (Server -> Client) |
|---|---|---|
基本调用(有序参数) |
|
|
基本调用(命名参数) |
|
|
通知(Notification) |
| 服务端不返回任何响应 |
调用错误 |
|
|
无效请求 |
|
|
批处理调用 (Batch)
客户端可以发送一个请求对象数组来进行批量调用。服务器处理完后,应返回一个响应对象数组,其中的响应顺序不一定与请求顺序一致,客户端需根据id进行匹配
批量请求示例:
[
{"jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": "1"},
{"jsonrpc": "2.0", "method": "notify_hello", "params": [7]}, // 这是一个通知
{"jsonrpc": "2.0", "method": "subtract", "params": [42,23], "id": "2"}
]
批量响应示例:
[
{"jsonrpc": "2.0", "result": 7, "id": "1"},
{"jsonrpc": "2.0", "result": 19, "id": "2"}
]
1.2 Cursor对MCP服务的要求是什么?
没有去找官方文档,也没去找其他博客,实践出真知,我是直接接口打断点看看Cursor到底怎么请求的
1.2.1 按照JSON-RPC 2.0协议规范,开发了一个接口
@PostMapping public Mono<ResponseEntity<McpResponse>> handleMcpRequest( @RequestBody McpRequest request, HttpServletRequest httpRequest) { return Mono.just(ResponseEntity.ok().build()); }1.2.2 在Cursor中配置这个接口
"wikijs": { "type": "http", "url": "http://127.0.0.1:8080/mcp" } }1.2.3 打断点看看Cursor做了哪些调用,报文是什么
第一次请求(initialize), 初始化,带了一些Cursor的参数
第二次请求(notifications/initialized),通知初始化完成
第三次请求(tools/list),请求工具,这个请求距离initialized有一定时间间隔,断点发现tools/list总共请求了2次并且有周期性的请求(间隔时间较长,推断是定时拉取更新)
总结就是
initialize -> notifications/initialized -> tools/list
1.3 撸个真实的MCP服务
有了以上的基础,现在实现一个真实的MCP服务
@PostMapping public Mono<ResponseEntity<McpResponse>> handleMcpRequest( @RequestBody McpRequest request, HttpServletRequest httpRequest) { String token = (String) httpRequest.getAttribute("wikijs_token"); // initialize 和 initialized 方法不需要 token if ("initialize".equals(request.getMethod())) { return handleInitialize(request); } if ("initialized".equals(request.getMethod())) { // initialized 是一个通知(notification),客户端在收到 initialize 响应后发送 // 根据 JSON-RPC 2.0 规范,通知没有 id,不需要响应 // 但为了兼容性,如果客户端发送了带 id 的请求,我们也返回成功响应 if (request.getId() != null) { // 如果客户端发送了带 id 的请求,返回成功响应 return Mono.just(ResponseEntity.ok() .contentType(org.springframework.http.MediaType.APPLICATION_JSON) .body(McpResponse.success(request.getId(), Map.of()))); } else { // 通知不需要响应,返回 200 OK 但无响应体 return Mono.just(ResponseEntity.ok().build()); } } // tools/list 不需要 token(只是获取工具列表) if ("tools/list".equals(request.getMethod())) { return handleToolsList(request); } // 其他方法需要 token(如 tools/call) if (token == null) { return Mono.just(ResponseEntity.ok( McpResponse.error(request.getId(), -32000, "未授权:必须提供Wiki.js API token") )); } return switch (request.getMethod()) { case "tools/call" -> handleToolsCall(request, token); default -> Mono.just(ResponseEntity.ok( McpResponse.error(request.getId(), -32601, "方法不存在: " + request.getMethod()) )); }; }这是我使用wiki.js搭建私有知识库做的一个MCP服务,如果你感兴趣可以查看全部的源码,地址见:
https://gitee.com/huang_yang/ai-developed-scaffolding
相信有了以上基础,你可以开发任意你想要的MCP服务,有了这些MCP服务的加持,你的Cursor才可以起飞。