news 2026/8/14 15:17:00

JSON-RPC 2.0:MCP通信的底层语言

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JSON-RPC 2.0:MCP通信的底层语言

摘要:MCP协议基于JSON-RPC 2.0进行通信,本文详解JSON-RPC消息格式、请求响应模型、批量调用、错误码定义,以及MCP如何在此基础上扩展出初始化握手和能力协商机制。

JSON-RPC 2.0 MCP通信的底层语言

我第一次抓包看MCP的通信内容时有点懵,消息里全是jsonrpc、method、params这些字段,看起来跟HTTP API完全不一样。后来才知道MCP底层用的是JSON-RPC 2.0协议,一个比我预想中更简洁的RPC规范。搞懂JSON-RPC 2.0之后再看MCP的消息流就豁然开朗了,所有工具调用、资源读取、能力协商的本质都是JSON-RPC消息的收发。这篇文章把JSON-RPC 2.0的规范和MCP的使用方式完整讲清楚,配上真实的消息示例。


JSON-RPC 2.0是什么

JSON-RPC 2.0是一个无状态的轻量级远程过程调用协议,2010年发布,用JSON作为数据格式。它的核心思想很简单,客户端发一个JSON对象描述要调用什么方法、传什么参数,服务端返回一个JSON对象包含执行结果。

它跟HTTP API的区别在于传输无关。JSON-RPC不绑定任何传输协议,可以在同一个进程内用,可以通过socket用,可以通过HTTP用,也可以通过标准输入输出用。MCP正是利用了这个特性,在stdio和Streamable HTTP两种传输上跑同一套JSON-RPC消息。

JSON-RPC 2.0跟1.0版本的区别也很好区分。2.0的消息里一定有个"jsonrpc": "2.0"字段,1.0没有。

三种消息格式

JSON-RPC 2.0定义了三种消息类型,请求、响应和通知。我逐个讲。

请求 Request

请求是客户端发给服务端的消息,期望得到响应。它有四个字段。

{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"add_note","arguments":{"title":"测试笔记","content":"这是内容"}}}

jsonrpc字段固定为"2.0",标识协议版本。id字段是客户端生成的唯一标识符,可以是数字或字符串,服务端响应时必须返回相同的id,客户端据此匹配请求和响应。method是要调用的方法名。params是方法参数,可以按位置传(数组)或按名称传(对象),MCP统一用按名称传。

id的值有几个注意事项。不建议用Null,因为Null在响应里表示无法识别请求id。不建议用带小数的数字,因为浮点数精度可能导致匹配失败。

响应 Response

响应是服务端对请求的回复。成功和失败有两种不同的结构。

成功响应。

{"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"笔记创建成功,ID为 1"}]}}

错误响应。

{"jsonrpc":"2.0","id":1,"error":{"code":-32602,"message":"Invalid params","data":{"detail":"title字段不能为空"}}}

响应的规则很严格。resulterror字段不能同时出现,成功时只有result,失败时只有error。id必须跟请求的id一致。如果请求解析失败无法识别id,响应的id为Null。

错误对象有三个字段。code是整数错误码。message是简短错误描述。data是可选的附加信息,可以是任意类型。

通知 Notification

通知是一种特殊的请求,没有id字段,服务端不需要回复。

{"jsonrpc":"2.0","method":"notifications/initialized"}

通知的用途是告诉对方某件事发生了,但不关心结果。MCP里大量使用通知,比如initialized通知、取消通知、进度通知、日志通知、列表变更通知。

通知的不可靠性在于没有响应,发送方无法知道对方是否收到或处理成功。如果你需要确认,就得用请求而不是通知。

错误码体系

JSON-RPC 2.0预留了-32768到-32000的范围给预定义错误。

错误码名称含义
-32700Parse error服务端解析JSON文本失败
-32600Invalid Request发送的JSON不是合法的请求对象
-32601Method not found方法不存在或不可用
-32602Invalid params方法参数无效
-32603Internal error服务端内部错误
-32000到-32099Server error预留给实现自定义的服务端错误

MCP在这个体系上扩展了自己的语义。比如-32602在MCP里经常出现,能力协商不匹配、参数格式错误、不支持的能力请求都会返回这个码。-32000到-32099的范围可以给MCP Server存自定义的业务错误码。

MCP还定义了一些特定的错误情况。协议版本不匹配时,Server返回-32602,data字段里带上支持的版本列表和请求的版本。这比单纯返回一个错误消息有用得多。

MCP如何使用JSON-RPC

MCP在JSON-RPC 2.0之上定义了自己的方法集和消息语义。我把MCP用到的方法分几类。

生命周期方法。initialize是握手请求,Client发给Server协商版本和能力。notifications/initialized是握手完成通知。这两个方法是MCP运行的基础。

工具方法。tools/list请求获取工具列表。tools/call请求调用某个工具。notifications/tools/list_changed通知告诉Client工具列表变了,让Client重新拉取。

资源方法。resources/list列出资源。resources/read读取资源内容。resources/subscribe订阅资源变更。resources/unsubscribe取消订阅。notifications/resources/updated通知资源已更新。

Prompt方法。prompts/list列出模板。prompts/get获取生成的消息。notifications/prompts/list_changed通知模板列表变更。

实用方法。ping心跳检测。logging/setLevel设置日志级别。

Client侧方法。sampling/createMessage由Server发请求让Client用LLM生成文本。elicitation/create由Server发请求让Client向用户提问。

所有这些方法的底层都是JSON-RPC的请求、响应或通知。MCP的精巧之处在于,它没有发明新的序列化格式或传输协议,完全复用JSON-RPC 2.0的消息结构,只是定义了method的命名空间和params的schema。

真实MCP通信消息示例

下面是一个完整的MCP通信流程,从握手到工具调用到关闭。我用笔记管理Server做例子,展示每一步的真实JSON-RPC消息。

// ========== 第1步 Client发送initialize请求 =========={"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{"roots":{"listChanged":true},"sampling":{}},"clientInfo":{"name":"example-client","version":"1.0.0"}}}// ========== 第2步 Server返回initialize响应 =========={"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-06-18","capabilities":{"tools":{"listChanged":true},"resources":{"subscribe":true,"listChanged":true},"prompts":{"listChanged":true},"logging":{}},"serverInfo":{"name":"mcp-notes-server","version":"1.0.0"}}}// ========== 第3步 Client发送initialized通知 ==========// 通知没有id字段,Server不会回复{"jsonrpc":"2.0","method":"notifications/initialized"}// ========== 第4步 Client请求工具列表 =========={"jsonrpc":"2.0","id":2,"method":"tools/list"}// ========== 第5步 Server返回工具列表 =========={"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"add_note","description":"添加一条新笔记,返回新笔记的ID","inputSchema":{"type":"object","properties":{"title":{"type":"string","description":"笔记标题"},"content":{"type":"string","description":"笔记正文"},"tags":{"type":"array","items":{"type":"string"},"description":"标签列表,可选"}},"required":["title","content"]}},{"name":"list_notes","description":"列出所有笔记的摘要信息","inputSchema":{"type":"object","properties":{}}}]}}// ========== 第6步 Client调用add_note工具 =========={"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"add_note","arguments":{"title":"学习MCP","content":"今天学习了JSON-RPC 2.0协议","tags":["MCP","协议"]}}}// ========== 第7步 Server返回工具执行结果 =========={"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"笔记创建成功,ID为 1\n标题 学习MCP\n时间 2025-06-18T10:30:00.000Z"}]}}// ========== 第8步 Server发送日志通知 ==========// Server可以主动发通知,不需要Client先发请求{"jsonrpc":"2.0","method":"notifications/message","params":{"level":"info","logger":"mcp-notes-server","data":"新笔记已创建,当前共1条笔记"}}

上面这8条消息覆盖了MCP通信的核心模式。握手三步走(步骤1到3),请求-响应对(步骤4到5、步骤6到7),服务端主动通知(步骤8)。

与REST和gRPC的对比

MCP选择JSON-RPC而不是REST或gRPC是有道理的。我从几个维度对比。

对比维度JSON-RPC 2.0RESTgRPC
数据格式JSONJSON/XML/任意Protobuf二进制
传输层任意HTTPHTTP/2
双向通信原生支持需WebSocket原生支持
流式传输需扩展需SSE/WebSocket原生支持
消息大小中等中等小(二进制编码)
可读性高(纯JSON)低(需解码)
浏览器支持差(需gRPC-Web)
工具链简单成熟复杂(需proto编译)
状态管理无状态无状态支持有状态流

MCP选JSON-RPC的原因我分析有三个。第一,JSON-RPC传输无关,stdio和HTTP都能跑,符合MCP同时支持本地和远程的设计目标。gRPC绑定HTTP/2,stdio模式跑不了。第二,JSON可读性好,调试时直接看消息内容就行,不用proto解码。第三,JSON-RPC原生支持双向通信,Server可以主动发请求和通知,REST做不到这点,得靠WebSocket或SSE补充。

代价是JSON的序列化效率不如Protobuf,大消息场景下性能差一些。但MCP的消息通常很小(工具参数和返回值),这个开销可以接受。

批处理

JSON-RPC 2.0支持批处理,客户端可以一次发一个数组包含多个请求。服务端处理完后返回一个响应数组。

// 批量请求,一次发两个工具调用[{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"add_note","arguments":{"title":"笔记A","content":"内容A"}}},{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"add_note","arguments":{"title":"笔记B","content":"内容B"}}}]// 批量响应,顺序不保证,靠id匹配[{"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"笔记创建成功,ID为 2"}]}},{"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"笔记创建成功,ID为 1"}]}}]

批处理里的通知不会有对应响应。如果整个批处理都是通知,服务端不返回任何内容。

不过MCP协议规范没有明确要求实现批处理,目前大部分MCP SDK和客户端都是一条一条发消息。了解这个特性有助于理解JSON-RPC的完整能力,但实际开发中你可能用不到。

常见问题与避坑

第一个坑,id用浮点数。JSON-RPC规范说id不建议包含小数部分。我见过有人用时间戳做id,时间戳带了毫秒小数,结果JavaScript的浮点精度问题导致请求和响应的id对不上,客户端一直超时。用整数或字符串做id最安全。

第二个坑,请求和通知搞混。有id的是请求,没id的是通知。我写Server时把通知当请求处理了,处理完还发了个响应回去,客户端收到多余的响应消息直接报错。区分方法很简单,看消息里有没有id字段。有id的才需要回复。

第三个坑,错误响应里带了result字段。JSON-RPC规范要求result和error不能同时存在。我见过有人写了{ "result": null, "error": {...} },虽然语义上是错误响应,但带了result字段导致一些严格的客户端解析失败。记住,错误响应只有error,成功响应只有result。

第四个坑,消息里嵌了原始换行符。stdio传输按换行符分隔消息。如果你手动拼JSON字符串没正确转义换行符,一条消息会被截成两条。永远用JSON.stringify或json.dumps序列化,它们会自动把\n转义成\\n

第五个坑,method名大小写敏感。JSON-RPC规范说method名是大小写敏感的。tools/Listtools/list是两个不同的方法。MCP的method名全用小写加斜线分隔,写代码时注意别手滑大写了。这类错误最恶心的是不报method not found,因为有些Server的实现用了不区分大小写的匹配,换一个Server就挂了。

小结

JSON-RPC 2.0是MCP通信的底层协议,定义了请求、响应、通知三种消息格式和一套错误码体系。MCP在JSON-RPC之上定义了initialize、tools、resources、prompts等方法集,所有MCP功能本质上都是JSON-RPC消息的收发。理解JSON-RPC的关键是区分请求(有id需响应)和通知(无id不响应),确保result和error互斥,用JSON.stringify序列化避免换行符问题。MCP选JSON-RPC而不是REST或gRPC,核心原因是传输无关性和原生双向通信能力。


相关推荐

  • MCP协议全景:Host、Client、Server架构详解
    • Tools原语深度解析:从定义到调用全流程
    • Completions与通知机制:自动补全、进度上报与状态通知
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/14 15:12:26

继承(全)

目录 一. 继承的概念及定义 1.1 继承的概念 1.2 继承的机制 1.3 继承的定义 1.3.1 定义格式​编辑 1.3.2 继承父类成员访问方式的变化 1.4 继承类模板 二. 父类和子类对象赋值兼容转换 2.2 类型转换的特殊处理规则 2.3 应用实例 三. 继承中的作用域 3.1 隐藏规则 …

作者头像 李华
网站建设 2026/8/14 15:10:38

王虹、邓煜、张益唐、韦东奕四位数学家的‌核心成果时间线对照表

以下是王虹、邓煜、张益唐、韦东奕四位数学家的核心成果时间线对照表,完整覆盖他们从学术起步到取得里程碑成果的关键节点,清晰呈现不同成长路径的节奏差异。 一、四位数学家核心成果时间线对照表 时间 节点 王虹(调和分析/几何测度论&…

作者头像 李华
网站建设 2026/8/14 15:03:59

代码实现!教学视频!Python学习者最易上手的机器学习漫游指南

此刻, 你理应已然掌握了线性回归的概念, 随后, 让我们瞧瞧怎样于其中达成它。准备工作fromdf pd.(‘.csv’)df. ‘X’, ‘Y’df.head()可视化sns.(“”, 1.1)sns.(“ticks”)sns.(‘X’,’Y’, datadf)plt.(‘’)plt.(‘’)实现 .() np.(df.X20:len(df.X)).(-1, 1) np.(df.Y20…

作者头像 李华