ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

MCP ping 没响应?TaoToken 接入的 Codex 这样查 JSON-RPC 超时

MCP ping 没响应?TaoToken 接入的 Codex 这样查 JSON-RPC 超时 最近在自建 MCP 客户端时最容易被一条看似最简单的 JSON-RPC 心跳卡住ping 发出去等半天没有回包。MCP 2025-03-26 版在「ping」一节把话说得很清楚但要让 Codex 帮你逐行核对这段逻辑先打开 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 创建 API Key并把 Codex 的模型通道指向 TaoToken。很多朋友的 ping 超时并不是网络断了而是请求格式、响应格式或生命周期顺序与规范不一致ping 请求不能带 params接收方必须立即返回一个空 result超时之后发送方可以终止连接或重连。Codex 在这里的作用不是替你去连生产库而是读取你本地导出的 MCP 客户端代码和 stdout 日志对照规范指出字段差异。下面按 2025-03-26 版规范的章节顺序把 ping 消息、初始化生命周期、stdio 与 Streamable HTTP 传输、错误码和排障拆开讲。你只需要在本地跑最小调试会话把原始 JSON 行贴回对话就能让 Codex 给出可落地的修改建议。1. MCP 2025-03-26 的 ping 到底长什么样1.1 ping 请求只有 jsonrpc、id、method 三个字段规范里对 ping 的定义非常克制。一个合法的 ping 请求看起来就是这样{ jsonrpc: 2.0, id: 123, method: ping }它没有params也不应该出现params: {}。id可以是字符串或数字但不能为空并且在同一会话中不能和之前用过的请求 id 重复。很多自建客户端在这里会犯两个错误一是把 ping 写成通知也就是去掉id只留method二是为了“看起来完整”硬塞一个空对象当参数。前者会让接收方按通知处理通知没有 id接收方不能响应你自然等不到任何回包后者在严格实现里可能被当成无效参数返回-32602。提示ping 是请求不是通知。请求必须有 id通知不能有 id。先检查这一条能省掉一半的“没响应”排查。1.2 合规响应是 result 为空对象不是业务数据接收方收到 ping 之后规范要求立即返回一个空响应。格式如下{ jsonrpc: 2.0, id: 123, result: {} }注意两个细节。第一id必须和请求完全一致字符串123和数字123在严格比较下不是一回事。第二result必须是空对象不能塞{status: ok}或{pong: true}。虽然那些 JSON-RPC 层面仍然合法但不满足 ping 的空响应约定。如果你的客户端只判断“收到了 result 就算成功”可能会把业务响应误当成 ping 回包掩盖真正的协议问题。Codex 在核对时你可以让它重点检查响应构造分支里有没有对空对象做断言。1.3 超时不是异常而是协议允许的终止动作规范没有要求发送方无限等待。如果在合理超时期限内没有收到响应发送方可以考虑连接过期、终止连接或者尝试重新连接。这里有几个实现上的分寸ping 频率应该可配置超时要适配当前网络环境不应无节制地发 ping 增加开销多个 ping 连续失败后可以触发连接复位每次失败都应该留下诊断日志方便后面把原始 JSON 行贴给 Codex 分析。很多客户端把超时当成普通错误抛给上层导致整个 MCP 会话直接崩掉。更稳的做法是先标记该请求超时发送notifications/cancelled停止等待再根据连续失败次数决定是否关闭连接。规范里也提到接收方可以忽略取消通知如果请求已经处理完成或无法取消所以发送方要能容忍“取消通知没有回音”这件事。2. 自建 MCP 客户端 ping 超时Codex 能检查哪几层2.1 先拿 Key再把 Codex 的 config.toml 指向 TaoToken你不需要先把整个 MCP 服务器改完。先用 TaoToken 把 Codex 的模型通道配通让它能读取你贴过去的代码片段和日志。打开 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 注册并创建 API Key然后在模型广场里确认你要用的模型 ID。Key 用占位符YOUR_API_KEY不要写死在仓库里。Codex 的配置文件是~/.codex/config.toml不是 Claude Code 的settings.json两者不要混用。一个可参考的配置片段如下model YOUR_MODEL_ID model_provider taotoken [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY环境变量在本地 shell 里设置export TAOTOKEN_API_KEYYOUR_API_KEY模型 ID 以 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 模型广场当时列表为准不要凭记忆写一个带日期后缀的名字。base_url填https://taotoken.net/api末尾不要加/v1。这两点确认完再启动 Codex。2.2 给 Codex 的提示词要限定“只读代码和日志”Codex 不能替你执行 MCP 服务器也不能直连你的生产库。它适合做的是读取你本地导出的代码、配置、日志文本对照规范给出差异清单。提示词可以这样写下面是我的 MCP 客户端中发送 ping 和处理超时的代码以及一段 stdout 日志。请对照 MCP 2025-03-26 版 ping 一节检查 1. ping 请求是否只包含 jsonrpc、id、method是否误带 params 2. 响应是否为 result: {}id 是否严格匹配 3. 超时后是否发送 notifications/cancelled 并停止等待 4. 初始化前后发送 ping 的时机是否合规。 只输出差异和本地验证步骤不要尝试执行任何外部命令不要连接任何数据库或生产系统。这样 Codex 的输出会收敛在协议对照上而不是跑去编造一个“自动修复并部署”的流程。你拿到建议后仍然在本地修改代码、跑调试会话、抓新的 JSON 行再贴回对话。2.3 Codex 负责分析MCP ping 仍然发生在你的客户端与服务器之间这里要分清两条通道MCP ping 是 MCP 客户端和 MCP 服务器之间的 JSON-RPC 心跳TaoToken 是 Codex 背后的模型 API 通道。Codex 不会替你去 ping 那个 MCP 服务器它只是在你把日志贴过去之后帮你判断日志里的 ping 消息是否符合规范。把这两个概念混在一起很容易写出“让 Codex 直接连上 MCP 服务器执行 ping”的错误方案。正确的桥是本地跑 MCP 调试会话导出原始消息交给 Codex 分析你在本地改代码再验证。3. 对照 2025-03-26 规范逐行查ping、生命周期、批处理、错误码3.1 初始化阶段之前只允许 ping 和日志类消息规范对初始化顺序有明确约束在服务器响应初始化请求之前客户端不应该发送 ping 以外的请求在收到初始化完成通知之前服务器不应该发送 ping 和日志以外的请求。如果你在initialize还没完成时就发了tools/list或resources/read服务器可能直接忽略日志上看起来却像“ping 超时”。排查时先把消息按时间顺序排开确认initialize请求、initialize响应、notifications/initialized通知三者的先后关系。一个常见的自建客户端错误是连接建立后立刻起一个定时器发 ping但initialize请求还在路上。此时服务器可能还没完成能力协商ping 被排队或丢弃。更稳妥的做法是把 ping 定时器绑在notifications/initialized之后启动并在初始化阶段单独设置更短的握手超时。3.2 JSON-RPC 批处理与 ping初始化请求不能进批2025-03-26 版支持 JSON-RPC 批处理发送方可以把多个请求或通知放进数组。MCP 实现可能支持发送端批处理但必须支持接收端批处理。这里有一条容易被忽略的规则初始化请求不能成为 JSON-RPC 批处理的一部分。也就是说你不能把initialize和一条 ping 打包成数组发出去。如果你确实要把 ping 放进批处理先确认服务器在初始化完成后能正确解析数组并且返回的响应数组里每个响应都带对应的 id。ping 本身很简单但批处理会引入顺序、部分失败和取消通知的复杂度。排障阶段建议先让 ping 单独成请求等基础路径稳定后再考虑批处理。3.3 超时、取消通知与常见错误码规范在超时部分提到当请求在超时期间内没有收到成功或错误响应时发送方应该为该请求发出取消通知并停止等待响应。取消通知的method是notifications/cancelled参数里带requestId和可选reason。它仍然是通知没有 id接收方不应该为它返回响应。ping 本身不应该返回错误但如果 method 拼错、参数非法或服务器不支持某些能力可能会看到这些错误码错误码含义与 ping 排查的关系-32601方法未找到可能把ping写成了Ping或服务器未实现该方法-32602无效参数ping 被误加了 params或参数结构不符合方法要求-32603内部错误服务器处理 ping 时内部异常需要看服务器日志Codex 分析日志时可以让它优先找-32601和-32602这两个码往往直接指向消息格式问题。4. 在本地把 ping 调试会话跑起来4.1 stdio 传输最小复现发一条 ping断言空 resultstdio 传输下MCP 服务器作为子进程启动客户端从 stdin 写 JSON-RPC 消息从 stdout 读响应。消息由换行符分隔不能包含嵌入换行编码必须是 UTF-8。下面是一段 Node.js 最小复现脚本重点看它如何断言 ping 响应const { spawn } require(node:child_process); const server spawn(node, [your-mcp-server.js], { stdio: [pipe, pipe, pipe] }); let buffer ; server.stdout.on(data, (chunk) { buffer chunk.toString(utf8); const lines buffer.split(\n); buffer lines.pop(); for (const line of lines) { if (!line.trim()) continue; const msg JSON.parse(line); console.log(收到消息:, JSON.stringify(msg)); if (msg.id ping-1) { const isEmptyResult msg.result typeof msg.result object Object.keys(msg.result).length 0; if (isEmptyResult) { console.log(ping 响应合规result 为空对象); } else { console.log(ping 响应不符合规范:, JSON.stringify(msg)); } server.stdin.end(); } } }); server.stderr.on(data, (chunk) { console.error(服务器 stderr:, chunk.toString(utf8)); }); const ping { jsonrpc: 2.0, id: ping-1, method: ping }; server.stdin.write(JSON.stringify(ping) \n); setTimeout(() { console.log(超时未收到 ping 响应按规范可以终止连接或重连); server.kill(SIGTERM); }, 5000);注意脚本里没有往 ping 里加params也没有把响应结果当成业务数据。服务器如果往 stdout 打印日志会破坏 JSON 行解析所以调试时把日志写到 stderr或者单独重定向。4.2 Streamable HTTP 下 ping 的 POST 与 Accept 头如果你的 MCP 服务器走 Streamable HTTPping 是一个新的 HTTP POST 请求发到 MCP 端点。客户端必须带Accept头列出application/json和text/event-stream。如果输入只有响应或通知服务器接受时返回202 Accepted且不带正文如果输入包含请求服务器可能返回 SSE 流也可能返回 JSON 对象。ping 是请求所以你要准备好解析这两种 Content-Type。这里再强调一次不要把 MCP ping 发到https://taotoken.net/api。这个地址是 Codex 调用模型的 API Base URL不是你的 MCP 服务器端点。MCP 端点由你自己的服务器决定比如https://your-mcp.example.com/mcp。两者混用会导致 404 或协议不匹配。4.3 把原始日志贴回 Codex让它按规范找差异日志至少保留这些字段时间戳、传输方式、原始 JSON 行、请求 id、响应 id、错误码。脱敏之后贴给 Codex并要求它输出一张对照表规范要求、你的实现、差异、本地验证命令。不要让 Codex 执行命令也不要让它连接你的数据库。它只做文本分析真正的执行仍然在你本地。如果你的 Codex 连不上模型通道先回到 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 检查 Key、模型 ID 和用量。配置里base_url写https://taotoken.net/api不要加/v1也不要在这条 URL 后面拼任何查询参数。5. 排障ping 没响应最常见的几个原因5.1 请求里混进了 params 或 method 拼错ping 请求没有参数。如果你写成{jsonrpc:2.0,id:1,method:ping,params:{}}严格服务器可能返回-32602。method 是大小写敏感的ping和Ping不是同一个方法。让 Codex 检查消息构造函数确认 ping 分支没有复用了其他方法的参数模板。5.2 响应 id 对不上、result 里塞了业务数据响应必须带与请求相同的 id。如果 id 类型不一致或者响应里result不是空对象客户端的匹配逻辑就可能走到错误分支。有的服务器为了“方便”在 ping 响应里返回{result:{ok:true}}这不会让 JSON-RPC 解析失败但不符合 ping 的空响应约定。Codex 可以帮你把响应分支里的所有 return 点列出来逐个核对。5.3 把通知当 ping或超时后没有取消没有 id 的{jsonrpc:2.0,method:ping}是通知接收方不能响应。如果你用这种方式发心跳永远等不到回包。超时之后规范建议发送notifications/cancelled并停止等待。很多客户端只是setTimeout后重连没有取消通知可能导致旧请求和新连接交错日志更乱。5.4 初始化前抢跑、批处理位置不对初始化请求必须是双方第一次交互并且不能放进批处理。客户端在服务器响应初始化之前只应发 ping服务器在收到初始化完成通知之前只应发 ping 和日志。顺序错了消息可能被忽略。把日志按时间轴画出来确认没有抢跑。5.5 stdio 换行、编码、stderr 污染stdio 传输要求每条消息由换行符分隔不能包含嵌入换行UTF-8 编码。服务器不能往 stdout 写非 MCP 消息。如果服务器把调试日志打到 stdout客户端解析 JSON 失败就会表现为“ping 没响应”。检查你的进程启动配置把 stderr 单独接出来不要合并到 stdout。5.6 模型通道没通Codex 分析中断如果 Codex 的base_url、环境变量或模型 ID 不对它可能根本没帮你分析日志。打开 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 看控制台用量确认这次请求是否记上账。如果没记上先检查TAOTOKEN_API_KEY是否生效、model_provider是否写对、base_url是否误加了/v1。模型通道通了再回来排查 MCP ping。6. 跑通之后去模型对话和控制台对一下这次验证6.1 用同一把 Key 发一条测试消息配置保存后先在 TaoToken 模型对话 里用同一把 Key 发一条测试消息确认模型 ID 和 Base URL 没填错。如果这里能正常回复说明 Codex 的模型通道没有明显问题。接着再跑本地 MCP ping 调试脚本把 stdout 里的原始 JSON 行贴回 Codex。如果要长期用 Codex 分析 MCP 日志和代码可以打开 Coding Plan 看套餐是否够用。Key 在 控制台 API Keys 创建创建时顺手确认模型广场里的模型 ID。Claude Code 环境变量对照见 接入文档如果你同时维护多个 AI 编程工具可以把 Base URL 和 Key 的写法做成一份本地备忘。6.2 看用量确认这次调用记上账回到 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 的控制台看用量确认这次让 Codex 分析 MCP ping 日志的请求有没有记上。如果用量没有变化优先检查 Codex 是否真的走了taotokenprovider而不是还在用旧环境变量。用量对得上再排查 MCP 客户端本身。6.3 把最小 ping 用例沉淀成回归脚本MCP 规范会继续演进你的 MCP 服务器和客户端也会改。把前面那段最小 ping 脚本留下来固定请求 id断言响应result为空对象超时后发送取消通知。每次升级 MCP SDK 或切换传输方式先跑一遍这个脚本再把差异日志丢给 Codex 对照规范。这样 ping 超时就不再是玄学问题而是一条可以复现、可以回归的检查项。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进