JSON-RPC 2.0 经换行分隔 stdio


文档摘要

JSON-RPC 2.0 经换行分隔 stdio 本节摘要:模型客户端与工具服务器之间的传输是 JSON-RPC over stdio。亲手滚一次,你就懂每个成帧层在为什么付费。JSON-RPC 2.0 是两页规范,2013 年至今没变,因为它在 stdio、socket、websocket、HTTP 上对称,客户端只要双方守规范就能驱动一个从没见过的服务器。本节构建 stdio 变体——换行分隔的 JSON,每请求一行、每响应一行、传输边界是 。你会学到四种信封形状、五个标准错误码、批处理,以及「一行解析错不毒化整条流」的韧性。 对应原课程:Phase 19 · Lesson 22 · (原英文 )。本节属「Agent Harness 深度构建赛道」第三节。

JSON-RPC 2.0 经换行分隔 stdio

本节摘要:模型客户端与工具服务器之间的传输是 JSON-RPC over stdio。亲手滚一次,你就懂每个成帧层在为什么付费。JSON-RPC 2.0 是两页规范,2013 年至今没变,因为它在 stdio、socket、websocket、HTTP 上对称,客户端只要双方守规范就能驱动一个从没见过的服务器。本节构建 stdio 变体——换行分隔的 JSON,每请求一行、每响应一行、传输边界是 \n。你会学到四种信封形状、五个标准错误码、批处理,以及「一行解析错不毒化整条流」的韧性。

对应原课程:Phase 19 · Lesson 22 · jsonrpc-stdio-transport(原英文 phases/19-capstone-projects/22-jsonrpc-stdio-transport/docs/en.md)。本节属「Agent Harness 深度构建赛道」第三节。

学习目标

阅读完本节,你应当能够:

  1. 说经 stdin/stdout 的换行分隔 JSON 成帧的 JSON-RPC 2.0。
  2. 映射五个标准错误码(-32700/-32600/-32601/-32602/-32603)并以正确语义呈现。
  3. 区分请求、响应、通知、批处理,不发明新信封键。
  4. 每行一个解析错处理,不毒化流的其余部分。
  5. io.BytesIO 构建自终止 demo,无需 spawn 子进程即可运行。

一、问题与直觉

一个 2026 编程 Agent 在单会话里会和大约十二个工具服务器对话,每个是独立进程或远程端点。线格式自 2013 年没变。JSON-RPC 2.0 是两页规范,它存活至今是因为替代方案(gRPC、每调用一次 HTTP、自定义二进制)都强加了 JSON-RPC 不需要的权衡:它们要么选流式、要么选批处理、要么绑传输。JSON-RPC 在 stdio、socket、websocket、HTTP 上对称,客户端只要双方守规范就能驱动一个从没见过的服务器。

本节构建 stdio 变体:换行分隔 JSON,每请求一行、每响应一行,传输边界是 \n

二、从零实现

四种信封形状:两种客户端说,两种服务器说。请求id,服务器必须以同 id 的成功或错误响应。通知没有 id,服务器不得响应——如果对通知返回响应,客户端没法把它挂到调用点,这一条规则让成帧数学保持简单。批处理是请求或通知的 JSON 数组,服务器以响应数组回复(任意顺序,每个非通知条目一个);若全是通知,服务器啥也不回。

五个错误码:

-32700 Parse error JSON 解析不了 -32600 Invalid Request 信封形状错 -32601 Method not found -32602 Invalid params -32603 Internal error

解析错有特殊规则:响应里的 idnull,因为请求从没解析到能取出 id 的程度。-32000 到 -32099 留给服务器自定义错误,其余是应用自定义。本节只用这五个。handler 抛异常,传输包成 -32603,异常类名放 data.exception

换行成帧的韧性(一行错不毒化流):

def serve(stdin, stdout, handler): for line in stdin: # 一行 = 到 \n 含 \n 的字节 try: req = parse_request(line) except ParseError: write_error(stdout, id=None, code=-32700) # id: null continue # 继续读下一行,流不毒化 ... # 派发

传输按行读。一行解析不了,传输写一个 id: null 的 -32700 响应然后继续。流不被毒化,下一行重新解析。坏的 JSON 行不停循环;缺 method 字段不停循环;handler 异常不停循环——传输一直读到 EOF。

三、方法派发与通知

传输不知道有哪些方法。它交给外壳提供的一个 handler(method, params) 可调用对象。handler 返回结果或抛异常。三个异常类呈现特定码:MethodNotFound → -32601,InvalidParams → -32602,其余 → -32603(异常名放 data)。

传输从不见工具注册表,注册表坐在 handler 后面。这是我们要的分层:传输说 JSON-RPC,注册表说工具形状,第 23 节的分发器把它们缝起来。

通知是「发即忘」。外壳用通知发进度事件、取消信号、日志行。通知是长跑工具能流式发状态更新而无需每次往返的机制。本节实现一个出站通知助手 write_notification,服务器在请求在途时发进度——demo 展示:请求进来,handler 发两条进度通知,再写最终响应。

四、可复用产物

code/main.py 定义 StdioTransport、解析助手 parse_request、三个写助手(write_response/write_error/write_notification)与派发循环 serve,错误码常量在模块作用域。code/tests/test_transport.py 覆盖五个错误码、通知(不写响应)、批处理(数组进数组出,通知跳过)、坏 JSON(解析错后继续)、handler 中途写通知的非对称流。

五、框架对比

JSON-RPC 2.0 在 LSP(语言服务器协议)、MCP(模型上下文协议)、DAP(调试适配器协议)里都是传输底子,这正是它的通用性证明。本节的 stdio 变体与 MCP 2026 的 StreamableHTTP 是兄弟——前者本地进程间,后者网络无状态,线格式都是 JSON-RPC 2.0。生产传输会在其上加三样:一是能扛转发的关联 id(你的 id 已是这个,但在网格里还需一个外层 trace id);二是取消通道(类似 $/cancelRequest 的通知带在途调用的 id);三是 content-type 协商握手,让同一 socket 能说 JSON-RPC 与 Streamable HTTP。这些都不改线格式,只加元数据。

六、练习

  1. 通知韧性:写一个全是通知的批处理,确认服务器啥也不回。
  2. 坏 JSON 续跑:在三行有效请求中间插一行坏 JSON,确认前后两行仍被正确处理,坏行回 -32700。
  3. 批处理乱序:发一个五个请求的批,确认响应数组每个非通知条目一个(顺序任意)。
  4. 中途通知:写一个 handler 在返回前发两条进度通知,确认通知先到、响应后到。
  5. 取消通道:实现 $/cancelRequest 通知,取消一个在途长跑调用的 id。

本节要点回顾

  1. JSON-RPC 2.0 是通用语:两页规范,2013 至今,在 stdio/socket/websocket/HTTP 上对称。
  2. 换行成帧:每请求一行、每响应一行,传输边界 \n
  3. 四种信封:请求(有 id)/响应(同 id)/通知(无 id,不得响应)/批处理(数组)。
  4. 五个错误码:-32700 解析/-32600 无效请求/-32601 方法未找到/-32602 无效参数/-32603 内部。
  5. 解析错 id 为 null:请求没解析到能取 id。
  6. 流不毒化:坏 JSON/缺字段/handler 异常都不停循环,读到 EOF。

下一节,我们建「函数调用分发器」——把超时、重试、去重、错误映射全压在一条缝上。


发布者: 作者: Rohit Gupta 转发
评论区 (0)
U