标签 :
MCPModel Context ProtocolJSON-RPCAgent工具调用协议
适合人群:希望理解 MCP 底层通信机制、或需要自行实现 MCP 客户端的开发者
一、MCP 是什么协议
MCP(Model Context Protocol)是 Anthropic 于 2024 年发布的开放协议,定义了 AI 模型与外部工具、资源之间的标准通信方式。它的底层是 JSON-RPC 2.0,支持三种传输方式:
| 传输方式 | 适用场景 |
|---|---|
| Stdio | 本地进程间通信,如 npx 启动的 MCP 服务器 |
| SSE | 基于 HTTP 的长连接,服务端推送事件 |
| HTTP | 标准 HTTP 请求/响应,无状态或带 session |
无论选择哪种传输方式,上层的通信协议和握手流程完全一致。本文以 SSE 为主线介绍,Stdio 和 HTTP 的差异仅在连接建立阶段。
二、完整交互流程
一次完整的 MCP 通信分为四个阶段:
客户端 服务器
│ │
│──── ① 建立连接 ─────────────────────> │
│<─── 连接确认(endpoint / 进程就绪)─── │
│ │
│──── ② initialize 请求 ──────────────> │
│<─── initialize 响应 ───────────────── │
│──── initialized 通知 ───────────────> │ ← 必须发送,否则服务器不接受后续请求
│ │
│ ✅ 握手完成 │
│ │
│──── ③ tools/list ───────────────────> │
│<─── 工具列表 ──────────────────────── │
│ │
│──── ③ tools/call ───────────────────> │
│<─── 工具结果 ──────────────────────── │
│ │
│──── ④ 关闭连接 ─────────────────────> │
三、第一阶段:建立连接
Stdio 方式
本地启动 MCP 服务器进程,通过标准输入输出通信:
bash
# 启动 MCP 服务器进程
uv --directory /path/to/mcp run mcp-server
# 通信方向
stdin → 发送 JSON-RPC 请求
stdout ← 接收 JSON-RPC 响应
stderr ← 接收服务器日志
SSE 方式
先建立长连接获取消息端点,后续通过该端点收发消息:
bash
# 第一步:连接 SSE 端点,保持长连接
GET http://127.0.0.1:8000/sse
Accept: text/event-stream
# 服务器推送 endpoint 事件,告知后续请求的地址
event: endpoint
data: /messages/?session_id=abc123
# 第二步:通过消息端点发送所有后续请求
POST http://127.0.0.1:8000/messages/?session_id=abc123
HTTP 方式
无需预先建立长连接,直接向固定端点发送请求:
bash
POST http://127.0.0.1:8000/mcp
Accept: application/json, text/event-stream
⚠️ HTTP 方式的响应可能是 SSE 格式(
data:前缀的行),需要额外处理。
四、第二阶段:握手(必须完成三步)
这是整个流程中最容易出错的部分。握手由三个子步骤构成,缺少任何一步,服务器都不会接受后续的工具调用请求。
步骤 2.1:客户端发送 initialize 请求
json
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": {
"name": "my-mcp-client",
"version": "1.0.0"
}
}
}
protocolVersion 固定为 "2024-11-05",capabilities 可以是空对象。
步骤 2.2:服务器响应 initialize
json
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2024-11-05",
"capabilities": {
"tools": { "listChanged": false },
"prompts": { "listChanged": false },
"resources": { "subscribe": false, "listChanged": false }
},
"serverInfo": {
"name": "Cognee",
"version": "1.16.0"
}
}
}
capabilities 字段说明服务器支持哪些功能,客户端据此判断后续可以调用哪些方法。
步骤 2.3:客户端发送 initialized 通知
json
{
"jsonrpc": "2.0",
"method": "notifications/initialized",
"params": {}
}
这一步有两个细节容易忽略:
- 没有
id字段:这是"通知",不是"请求",服务器不会返回响应 - 发送时机 :必须在收到 initialize 响应之后立即发送,只有发送此通知后,服务器才开始接受
tools/list、tools/call等请求
五、第三阶段:工具调用
握手完成后进入正常通信阶段。最常用的两个方法是列出工具和调用工具。
列出可用工具
json
// 请求
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/list",
"params": {}
}
// 响应
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"tools": [
{
"name": "search",
"description": "Search for information",
"inputSchema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Search query"
}
},
"required": ["query"]
}
}
]
}
}
inputSchema 是标准 JSON Schema 格式,可以直接转换成 Function Calling 所需的工具描述,交给支持工具调用的大模型使用。
调用工具
json
// 请求
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "search",
"arguments": {
"query": "MCP protocol"
}
}
}
// 响应
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [
{
"type": "text",
"text": "MCP (Model Context Protocol) is..."
}
],
"isError": false
}
}
content 数组支持多态,type 字段可以是 text、image 或 resource,需根据类型分别处理。
其他可用方法
| 方法 | 说明 | 关键参数 |
|---|---|---|
prompts/list |
列出服务器预定义的提示词模板 | 无 |
prompts/get |
获取并渲染某个提示词 | name、arguments(可选) |
resources/list |
列出服务器暴露的资源 | 无 |
resources/read |
读取某个资源的内容 | uri |
resources/subscribe |
订阅资源变更通知 | uri |
logging/setLevel |
设置服务器日志级别 | level |
六、错误处理
服务器遇到错误时,响应体中会有 error 字段而非 result:
json
{
"jsonrpc": "2.0",
"id": 3,
"error": {
"code": -32602,
"message": "Invalid params",
"data": {
"details": "Missing required field: query"
}
}
}
JSON-RPC 2.0 标准错误码:
| 错误码 | 含义 | 常见原因 |
|---|---|---|
-32700 |
Parse error | 请求不是合法 JSON |
-32600 |
Invalid Request | 缺少 jsonrpc 或 method 字段 |
-32601 |
Method not found | 调用了服务器不支持的方法 |
-32602 |
Invalid params | 参数类型错误或缺少必填字段 |
-32603 |
Internal error | 服务器内部异常 |
七、第四阶段:关闭连接
Stdio 方式
java
process.destroy();
SSE 方式
java
eventSource.cancel(); // 关闭长连接
HTTP 方式
bash
# 发送 DELETE 请求清理 session 资源
DELETE http://127.0.0.1:8000/mcp
mcp-session-id: abc123
八、Java 客户端代码示例
如果使用 j-langchain,上面所有步骤(连接建立、三步握手、工具列举、工具调用、连接关闭)都封装在 McpServerConnection 内部,不需要手动处理 JSON-RPC 报文:
java
public void mcpClientFlow() throws Exception {
// 1. 配置连接方式(SSE 示例)
ServerConfig config = new ServerConfig();
config.type = "sse";
config.url = "http://127.0.0.1:8000/sse";
// 2. 建立连接,connect() 内部自动完成三步握手
McpConnection conn = McpConnectionFactory.createConnection("cognee", config);
conn.connect();
// 3. 列出工具
List<ToolDesc> tools = conn.listTools();
System.out.println("可用工具数量:" + tools.size());
// 4. 调用工具
Map<String, Object> args = Map.of("query", "MCP protocol");
ToolResult result = conn.callTool("search", args);
System.out.println("工具返回:" + result.getFirstText());
// 5. 关闭连接
conn.close();
}
如果需要对接非标准 MCP 服务器或自行实现客户端,按照本文的协议流程逐步构造 JSON-RPC 报文即可。
九、常见问题速查
Q:为什么 tools/call 一直返回 "Method not found"?
A:最可能的原因是没有发送 initialized 通知,服务器还处于握手未完成状态,拒绝所有工具调用请求。
Q:SSE 连接建立后,请求发到哪个地址?
A:不是 /sse 端点,而是服务器推送的 endpoint 事件中的地址(通常是 /messages/?session_id=xxx)。
Q:请求的 id 字段有什么要求?
A:必须是数字或字符串,且在同一会话内唯一。通常用递增整数:initialize 用 1,后续依次 2、3......服务器响应会携带相同的 id,用于匹配请求和响应。
Q:通知和请求有什么区别?
A:请求有 id 字段,服务器会返回对应响应;通知没有 id,服务器不返回任何内容。initialized 是唯一必须发送的通知。
📎 相关资源
- MCP 官方规范:https://modelcontextprotocol.io
- JSON-RPC 2.0 规范:https://www.jsonrpc.org/specification
- j-langchain GitHub:https://github.com/flower-trees/j-langchain
- j-langchain Gitee 镜像:https://gitee.com/flower-trees-z/j-langchain