Skip to content

2.3 JSON-RPC 与消息流——协议的底层语言

小周理解了三种原语,现在想深入一层:Client 和 Server 之间到底怎么通信?消息长什么样? 他在 Inspector 里看到了一些 JSON 消息,但看不懂格式。


JSON-RPC 2.0

MCP 的所有通信都基于 JSON-RPC 2.0——一个轻量级的远程过程调用协议。

为什么选 JSON-RPC 2.0 而不是 REST?

  • REST 是请求-响应模式,单向
  • JSON-RPC 2.0 支持双向通信——Server 也可以主动发请求给 Client
  • JSON-RPC 2.0 支持通知——一方发送消息,不需要回复

四种消息类型

1. 请求(Request)

Client 发送请求,期待 Server 的响应。每条请求有唯一的 id

json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "search-files",
    "arguments": { "pattern": "*.ts" }
  }
}

2. 响应(Response)

Server 收到请求后返回结果,id 与请求匹配。

json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      { "type": "text", "text": "Found 3 files: a.ts, b.ts, c.ts" }
    ]
  }
}

3. 通知(Notification)

没有 id,不期待响应。用于推送事件。

json
{
  "jsonrpc": "2.0",
  "method": "notifications/tools/list_changed"
}

常见通知:

通知说明
notifications/tools/list_changed工具列表变了,Client 应重新获取
notifications/resources/updated某个资源内容变了
notifications/resources/list_changed资源列表变了
notifications/prompts/list_changed模板列表变了
notifications/messageServer 发送日志
notifications/cancelled取消正在进行的请求
notifications/progress工具执行进度

4. 错误(Error)

请求处理失败时返回错误。

json
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32602,
    "message": "Unknown tool: invalid_tool_name"
  }
}

标准错误码:

代码含义典型场景
-32700Parse errorstdout 被非协议数据污染
-32600Invalid Request请求格式不符合规范
-32601Method not found调用了不支持的方法
-32602Invalid params参数格式错误
-32603Internal errorServer 内部异常

两种错误,两种处理方式

MCP 有两种不同的错误报告机制,理解它们的区别非常重要:

Protocol Error(协议错误)
触发:请求本身有问题——未知工具、格式错误、缺少参数
格式:JSON-RPC error 对象
LLM 能修正吗:不能——请求格式不对,LLM 重试也没用
❌ LLM 无法自我修正
Tool Execution Error(工具执行错误)
触发:工具运行了但失败——API 报错、验证失败
格式:`isError: true` 的正常响应
LLM 能修正吗:——看到错误描述后可以调整参数重试
✅ LLM 可以自我修正

v4 的关键变更:输入验证错误(如日期格式错误)应作为 Tool Execution Error 返回,而不是 Protocol Error。这样 LLM 能看到错误描述并修正参数。

json
// Tool Execution Error 示例——LLM 看到后可以修正日期格式
{
  "result": {
    "content": [
      { "type": "text", "text": "Invalid date: must be in YYYY-MM-DD format. Got: 08/08/2025" }
    ],
    "isError": true
  }
}

完整通信流程

小周在 Claude Desktop 中输入「搜索项目中的 TypeScript 文件」,完整的消息流如下:

Client
1initialize
1capabilities
2tools/list
2工具列表 + Schema
4tools/call
4执行结果
Server
1初始化——Client 发送 initialize,携带 {tools: {}, resources: {}}。Server 回复自己的能力。Client 发送 initialized 通知。
2发现工具——Client 发送 tools/list,Server 返回所有可用工具的列表和 Schema。
3AI 决策——Claude 模型看到工具列表,决定调用 search-files,参数 {"pattern": "*.ts"}
4工具调用——Client 发送 tools/call,Server 执行搜索并返回结果。
5结果回传——Client 把工具结果作为 tool_result 附加到对话中,再次调用 Claude 模型。
6最终回复——Claude 模型基于工具结果,生成最终的自然语言回复给用户。

分页

当 Server 返回大量数据时,使用 cursor-based 分页:

场景:一个 GitHub Server 可能暴露 200+ 个工具(每个 API 端点一个)。一次性返回全部会浪费带宽、拖慢初始化。分页让 Client 按需加载。

json
// 请求(获取第一页)
{ "method": "tools/list", "params": {} }

// 响应(有更多数据)
{ "result": { "tools": [...], "nextCursor": "page-2-token" } }

// 请求(获取下一页)
{ "method": "tools/list", "params": { "cursor": "page-2-token" } }

// 响应(最后一页)
{ "result": { "tools": [...] } }

Client 应循环获取直到 nextCursor 为空。


取消

任一方可以发送 notifications/cancelled 通知取消正在进行的请求:

json
{
  "method": "notifications/cancelled",
  "params": {
    "requestId": "123",
    "reason": "用户请求取消"
  }
}

收到取消通知后,接收方应停止处理、释放资源、不发送响应。


本节核心要点

  • MCP 基于 JSON-RPC 2.0,支持请求-响应、通知、错误四种消息
  • 选择 JSON-RPC 而非 REST,因为需要双向通信能力
  • 两种错误机制:Protocol Error(LLM 无法修正)vs Tool Execution Error(LLM 可自我修正)
  • v4 起,输入验证错误应作为 Tool Execution Error 返回
  • 支持分页(cursor-based)和取消机制

思考题:为什么 Tool Execution Error 要设计成 isError: true 的正常响应,而不是直接返回 JSON-RPC 错误?

参考思路

关键在于"LLM 能否自我修正":Tool Execution Error 是正常响应的一部分,LLM 可以看到错误描述(如"日期格式需要 YYYY-MM-DD")并修正参数重试。如果返回 JSON-RPC Error,LLM 看到的是协议层面的失败,无法理解具体哪里出错。


进入实战前的自测

在继续之前,确认你能回答以下问题:

  1. MCP 的三层架构是什么?Client 和 Server 是几对几关系?
  2. 三种原语分别由谁控制?
  3. 为什么 MCP 选择 JSON-RPC 而不是 REST?
  4. stdio 传输中,日志应该写到哪里?

← 上一节:三大原语 | 目录 | 下一节:第一个 MCP Server →