手把手教你为Cursor撸一个自定义的MCP服务(对接wiki.js)
2026/5/9 0:51:09 网站建设 项目流程

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)

基本调用(有序参数)

{
"jsonrpc": "2.0",
"method": "subtract",
"params": [42, 23],
"id": 1
}

{
"jsonrpc": "2.0",
"result": 19,
"id": 1
}

基本调用(命名参数)

{
"jsonrpc": "2.0",
"method": "subtract",
"params": {"minuend": 42, "subtrahend": 23},
"id": 3
}

{
"jsonrpc": "2.0",
"result": 19,
"id": 3
}

通知(Notification)
(客户端不期待响应)

{
"jsonrpc": "2.0",
"method": "update",
"params": [1,2,3,4,5]
}
注意:没有id字段

服务端不返回任何响应

调用错误
(例如,方法不存在)

{
"jsonrpc": "2.0",
"method": "nonExistentMethod",
"id": "1"
}

{
"jsonrpc": "2.0",
"error": {
"code": -32601,
"message": "Method not found"
},
"id": "1"
}

无效请求
(例如,method不是字符串)

{
"jsonrpc": "2.0",
"method": 1,
"params": "bar"
}

{
"jsonrpc": "2.0",
"error": {
"code": -32600,
"message": "Invalid Request"
},
"id": null
}

批处理调用 (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才可以起飞。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询