JSON-RPC 2.0:MCP 的通信底座
作者:程序员马丁
Ragent AI —— 从 0 到 1 纯手工打造企业级 Agentic RAG,拒绝 Demo 玩具!AI 时代,助你拿个offer。
为什么需要 JSON-RPC?
对于 Java 开发者来说,远程调用并不是新鲜事。
在业务系统中,我们已经很熟悉 REST、Feign、Dubbo、gRPC 这 些调用方式,它们解决的是“服务与服务之间如何通信”的问题。
但 MCP 要解决的,不完全是同一个层面的问题。
在 MCP 中,重点不在于某个业务接口是用 Spring MVC 还是 Dubbo 实现的,而在于:当一个 MCP Client 想调用某个工具能力时,客户端和服务端是否有一套统一、标准、可互操作的消息格式。
比如,一次调用中:
- 方法名怎么表示?
- 参数放在哪?
- 调用成功返回什么结构?
- 调用失败如何返回错误码和错误信息?
- 不需要响应的通知消息又该怎么表示?
如果这些内容没有统一规范,不同 MCP 实现之间就很难兼容,大模型也无法稳定地以相同方式调用外部工具。
因此,MCP 选择 JSON-RPC 2.0,并不是为了替代 Java 领域已有的微服务调用框架,而是为了在协议层统一“请求—响应—通知”的表达方式。
它定义的是一套消息结构标准,让不同语言、不同框架、不同运行环境下的 MCP Client 和 MCP Server,都能按照同一套规则通信。
所以可以把它理解为:
- REST / Dubbo / gRPC:偏业务接口调用方式
- JSON-RPC 2.0:偏协议消息封装规范
- MCP:在 JSON-RPC 2.0 之上,进一步约定工具、资源、提示词等能力模型
它本身并不绑定具体传输层,既可以跑在 HTTP 之上,也可以跑在 WebSocket、stdio 等通道之上。只要客户端和服务端都遵守 JSON-RPC 2.0 的约定,就能够以一致的方式完成请求、响应和通知。
但它也有明确的边界:JSON-RPC 只定义消息格式与处理规则,并不负责传输层细节,也不包含鉴权、服务发现等工程能力。这些需要在网关、框架或系统规范里另外补齐。
用一句话概括 JSON-RPC 2.0 是什么
JSON-RPC 是一种轻量的远程过程调用(RPC)协议,使用 JSON 作为消息格式。它的目标很简单:用统一的 JSON 结构描述一次远程方法调用。其设计哲学是“简单至上”(It is designed to be simple)。
需要注意版本:本文讨论的是 JSON-RPC 2.0。JSON-RPC 2.0 发布于 2010 年 3 月,并且与 JSON-RPC 1.0 不兼容。区分方式很简单:
- JSON-RPC 2.0 的请求和响应对象都必须包含
"jsonrpc": "2.0" - JSON-RPC 1.0 没有这个字段
JSON-RPC 2.0 的核心对象
从 Java 开发者的视角看,JSON-RPC 2.0 其实不复杂,核心无非就是几种固定的消息结构:
-
Request Object:发起一次调用,请求服务端执行某个方法
-
Notification:也是请求,但因为没有
id,所以不要求服务端回包 -
Response Object:服务端对请求的响应,里面要么是结果,要么是错误
-
Batch:把多个请求一起发出去,减少多次交互开销
除了消息结构,协议里还定义了两个交互角色:
- Client:发送请求的一方
- Server:接收请求并返回结果的一方
注意:同一个程序可以同时扮演 Client 和 Server。例如在微服务调用链中:
- 服务 A 调用服务 B 时,A 是 Client
- B 调用服务 C 时,B 又成为 Client
核心对象:Request、Notification、Response、Error
1. Request Object(请求对象)
一次 RPC 调用通过向服务端发送 Request Object 来表示。Request 对象包含以下字段。
jsonrpc(必需)
- 类型:String
- 值:必须精确等于
"2.0" - 作用:标识协议版本
method(必需)
- 类型:String
- 含义:要调用的方法名
- 约束:以
rpc.开头的方法名是保留方法,用于 RPC 内部或扩展,不应作为业务方法名
例如(合法方法名示例):
"getUserInfo""order.create""calculateSum"
params(可选)
- 类型:Object 或 Array
- 含义:方法调用参数
- 可以省略,表示无参数调用
两种参数形式:
- 按位置传参:
params为 Array,例如[42, 23] - 按名称传参:
params为 Object,例如{"minuend": 42, "subtrahend": 23}使用命名参数时,字段名必须与服务端期望的参数名完全匹配(包括大小写)。
id(条件必需)
- 类型:String、Number 或 Null
- 作用:用于关联请求和响应
规则:
- 如果 不存在
id字段,该请求被视为 Notification - 服务端必须在 Response 中返回相同的 id
规范层面的建议:
- 避免使用 Null 作为 id
- Number 类型 不应使用小数
Request 示例:
{
"jsonrpc": "2.0",
"method": "getUserInfo",
"params": {"userId": "12345"},
"id": 1
}
2. Notification(通知)
Notification 是一种没有 id 字段的 Request。它表示客户端不期望收到任何响应。
规范要求:
- 服务端 不得返回 JSON-RPC Response Object
- 即使发生错误,也不会返回错误对象(客户端无法感知错误)
Notification 示例:
{
"jsonrpc": "2.0",
"method": "logEvent",
"params": {
"level": "info",
"message": "User logged in"
}
}
如果使用 HTTP 作为传输层:服务器仍然需要返回 HTTP 响应(例如 204 No Content),但 不会返回 JSON-RPC Response 对象。
什么时候发送 Notification 请求?
当调用方不关心结果/错误,且不希望为这次调用付出一次响应的成本(等待、解析、重试、幂等等)时,用 Notification。比如:日志/埋点/旁路异步触发等,调用方只要“发出去”即可,不管任务结果或可以在后台处理。
3. Response Object(响应对象)
当服务端处理 Request 时,必须返回一个 Response Object,除非该请求是 Notification。Response 包含以下字段。
jsonrpc(必需)
- 值:必须精确等于
"2.0"
result(成功时必需)
- 含义:调用 成功的返回值
- 规则:成功时必须存在;失败时不得存在
error(失败时必需)
- 含义:调用失败的错误信息
- 规则:失败时必须存在;成功时不得存在
- 值:必须是 Error Object
id(必需)
- 含义:用于关联请求和响应
- 规则:
- 正常情况下必须与 Request 中的
id相同 - 如果服务端无法识别请求 ID(例如解析错误、无效请求),
id必须为null
- 正常情况下必须与 Request 中的
关键规则:result 和 error 必须有且只有一个存在。
成功响应示例:
{
"jsonrpc": "2.0",
"result": {
"userId": "12345",
"name": "张三",
"role": "admin"
},
"id": 1
}
错误响应示例:
{
"jsonrpc": "2.0",
"error": {
"code": -32602,
"message": "Invalid params",
"data": "userId is required"
},
"id": 1
}
4. Error Object(错误对象)
当 RPC 调用失败时,Response 必须包含 error 对象。Error Object 包含三个字段:
code(必需)
- 类型:Number
- 必须是整数
- 含义:错误类型
message(必需)
- 类型:String
- 含义:简短的人类可读错误描述
data(可选)
- 类型:任意 JSON 值
- 含义:额外错误信息(调试/上下文 等)
规范预定义错误码
JSON-RPC 规范保留了 -32768 到 -32000 的错误码区间,其中部分被定义为标准错误:
| code | message | 含义 |
|---|---|---|
| -32700 | Parse error | JSON 解析错误 |
| -32600 | Invalid Request | 请求不是有效的 Request 对象 |
| -32601 | Method not found | 方法不存在 |
| -32602 | Invalid params | 参数错误 |
| -32603 | Internal error | JSON-RPC 内部错误 |
| -32000 ~ -32099 | Server error | 保留给实现定义 |
其余错误码空间可用于应用自定义错误。
Error 示例:
{
"code": -32601,
"message": "Method not found",
"data": "Method 'getUserInfo' is not registered"
}
Batch(批处理)到底怎么用?
Batch 允许客户端一次发送多个 Request 对象,服务端批量处理后返回多个 Response。这样可以减少网络往返次数,提高吞吐量。但在实际实现中,Batch 也是 JSON-RPC 2.0 里最容易被实现错误的一部分。
1. Batch 的基本规则
请求格式
- 客户端可以发送一个 Array,其中包含多个 Request 对象
- Array 中可以混合 普通 Request 和 Notification
响应格式
- 服务端应返回一个 Array,包含对应的 Response 对象
- 每个 Request 应对应一个 Response
- Notification 不应该返回 Response
- Response 的顺序 不要求与请求顺序一致
- 客户端必须通过
id字段来匹配请求与响应 - 服务端可以并发处理 Batch 中的请求
特殊情况
- 如果 Batch 不是有效 JSON,服务端必须返回 单个错误 Response(不是 Array)
- 如果 Batch 是一个 空 Array,这也是无效请求,应返回单个
Invalid Request - 如果 Batch 中 没有任何需要返回 Response 的请求(例如全部是 Notification),服务端 不应返回 JSON-RPC 响应
2. 示例:混合 Request 和 Notification
请求
[
{"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", "method": "get_data", "id": "9"}
]
响应
[
{"jsonrpc": "2.0", "result": 7, "id": "1"},
{"jsonrpc": "2.0", "result": 19, "id": "2"},
{"jsonrpc": "2.0", "result": ["hello", 5], "id": "9"}
]
要点
- 请求中有 4 个对象,其中
notify_hello是 Notification(没有id) - 因此响应只有 3 个对象
- 响应顺序 不保证与请求一致
- 客户端通过
id进行匹配
3. 示例:全部是 Notification
请求
[
{"jsonrpc": "2.0", "method": "notify_sum", "params": [1, 2, 4]},
{"jsonrpc": "2.0", "method": "notify_hello", "params": [7]}
]
响应
无 JSON-RPC 响应。
在 HTTP 传输场景下,服务器通常返回:
204 No Content- 或
200 OK且 body 为空
关键点是:不返回 JSON-RPC Response 对象。
4. 错误示例:空 Array
请求
[]
响应
{
"jsonrpc": "2.0",
"error": {"code": -32600, "message": "Invalid Request"},
"id": null
}
原因
规范要求 Batch 必须包含至少一个元素。空数组不是有效请求。
5. 错误示例:Array 中不是 Request 对象
请求
[1, 2, 3]
响应
[
{"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null},
{"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null},
{"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null}
]
原因
Batch 中的每个元素都必须是 Request Object。
1、2、3 都是无效请求,因此分别返回错误。
6. 错误示例:Batch 不是合法 JSON
请求
[
{"jsonrpc": "2.0", "method": "sum", "params": [1, 2, 4], "id": "1"},
{"jsonrpc": "2.0", "method"
]
响应
{
"jsonrpc": "2.0",
"error": {"code": -32700, "message": "Parse error"},
"id": null
}
原因
JSON 本身无法解析,因此返回 Parse error。
常见实现错误(实践中最容易踩的坑)
1. 忘记包含 jsonrpc: "2.0"
错误示例
{
"method": "getUserInfo",
"params": {"userId": "12345"},
"id": 1
}
原因:规范要求 Request 和 Response 必须包含 jsonrpc 字段。
正确写法
{
"jsonrpc": "2.0",
"method": "getUserInfo",
"params": {"userId": "12345"},
"id": 1
}
2. Notification 返回了响应
错误示例
客户端请求
{
"jsonrpc": "2.0",
"method": "logEvent",
"params": {"level": "info", "message": "User logged in"}
}
服务端响应
{
"jsonrpc": "2.0",
"result": "OK",
"id": null
}
原因:Notification 不允许返回 JSON-RPC Response。
正确做法:不返回 JSON-RPC 响应(HTTP 场景可返回 204)。
3. result 和 error 同时存在
错误示例
{
"jsonrpc": "2.0",
"result": {"userId": "12345"},
"error": {"code": -32603, "message": "Internal error"},
"id": 1
}
原因:规范要求 两者只能存在一个。
4. method 不是字符串
错误示例
{
"jsonrpc": "2.0",
"method": 123,
"params": [1, 2],
"id": 1
}
原因:method 必须是字符串类型。
5. params 不是 Object 或 Array
错误示例
{
"jsonrpc": "2.0",
"method": "echo",
"params": "hello",
"id": 1
}
原因:params 必须是 Object 或 Array。
正确示例
{
"jsonrpc": "2.0",
"method": "echo",
"params": ["hello"],
"id": 1
}
6. 命名参数大小写不匹配
错误示例
{
"jsonrpc": "2.0",
"method": "getUserInfo",
"params": {"userid": "12345"},
"id": 1
}
如果服务端期望参数名是 userId,则会返回 Invalid params。
JSON 字段名 大小写敏感。
7. Batch 全是 Notification 却返回 []
错误示例
[]
原因:如果 Batch 中 没有需要返回的 Response,规范要求 不返回 JSON-RPC 响应。
正确做法:
- HTTP 返回
204 No Content - 或返回空 body
8. 业务错误使用了协议错误码
错误示例
{
"jsonrpc": "2.0",
"error": {"code": -32603, "message": "User not found"},
"id": 1
}
-32603 是 JSON-RPC 内部错误,不应该用于业务逻辑。
正确示例
{
"jsonrpc": "2.0",
"error": {"code": 1001, "message": "User not found"},
"id": 1
}
建议:
-32768 ~ -32000用于 协议层错误- 业务错误使用 自定义错误码(例如 1000+)