ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MCP 中 JSON-RPC 请求完整详解:从 stdio 到 TaoToken 的 Request 与 Notification 实践

MCP 中 JSON-RPC 请求完整详解:从 stdio 到 TaoToken 的 Request 与 Notification 实践 1. 为什么 stdio 下的 JSON-RPC 总在“最后一公里”翻车MCP 全称 Model Context Protocol你可以把它理解成“让大模型调用外部工具和资源的一套标准插头”。它底层不玩花活通信协议就是 JSON-RPC 2.0而本地场景里最常用的传输方式是 stdio——也就是父进程和子进程之间用标准输入输出管道对话。听起来简单但真正动手写一个 MCP Server 或者调试一个第三方 Server 时十有八九会卡在“请求发出去了响应没回来”或者“响应回来了但解析报错”上。问题往往不在业务逻辑而在 JSON-RPC 的报文格式和 stdio 的传输规则没对齐。Request 必须带idNotification 绝对不能带idstdout 只能吐 JSON-RPC 报文任何一句print(debug)都会把整条管道污染成不可解析的垃圾每条报文必须单行、末尾换行JSON 内部不能有裸换行。这些规则单独看都懂合在一起写代码时就容易漏。这篇文章面向三类人正在写 MCP Server 的后端开发、用 Cline/Claude Code 这类客户端接自定义工具的工程师、以及想搞明白“为什么我的 MCP 工具列表刷不出来”的排障选手。我会从 stdio 传输的完整生命周期讲起把 Request 和 Notification 的构造、发送、匹配、处理拆开再结合 TaoToken 的统一 API 通道https://taotoken.net/api演示怎么把模型调用和 MCP 工具链串起来。你跟着做能拿到可复制的 JSON-RPC 片段和一套本地验证请求-响应链路的操作步骤。先记住一个核心检索词MCP JSON-RPC stdio Request Notification 完整生命周期。下面所有内容都围绕它展开。2. TaoToken 前置统一 Key 与 API 通道在 MCP 链路里的位置在讲报文之前得先把“模型从哪来”这件事说清楚。MCP 本身只负责工具调用协议它不提供模型。你的 MCP Client比如 Claude Code、Cline、或者自己写的宿主程序需要一个大模型后端来决策“什么时候调哪个工具”。TaoToken 在这里的角色是统一 Key 和 API 通道你拿一个 Key就能通过兼容接口访问多家模型不用为每个模型单独配一套鉴权和 Base URL。对 MCP 调试来说这带来两个实际好处。第一你的 MCP Client 配置里只需要维护一份 API Key 和一个 Base URL减少变量排障时能快速排除“是不是 Key 配错了”。第二TaoToken 的接口兼容主流协议Claude Code、Cline、Codex 这类工具可以直接把 Base URL 指过来模型对话和工具调用走同一条通道日志集中出问题好定位。你需要提前准备的东西不多一个 TaoToken 的 API Key在控制台创建地址是 https://taotoken.net/console以及确认你的 MCP Client 支持自定义 Base URL。Base URL 填https://taotoken.net/api注意这个地址不带任何查询参数。Key 的格式通常是sk-开头的一串字符创建后只显示一次记得存好。这里要强调一个边界TaoToken 是合法的 API 通道服务不是所谓“中转”或任何灰色设施。你用它就是正常调用模型接口和直接用官方 API 没有本质区别只是入口统一了。MCP 的 stdio 传输发生在你本地进程之间和 TaoToken 的网络请求是两层不要混在一起理解。模型请求走 HTTPS 到 TaoToken工具调用走 stdio 到本地 MCP Server两者通过 MCP Client 这个宿主程序协调。如果你用的是 Claude Code 这类带 MCP 支持的编码工具配置入口一般在 settings 或专门的 MCP 配置文件里。下面第三节我会给出可复制的配置片段包括 Base URL、Key 和 Model ID 三件套以及 MCP Server 的 stdio 启动参数。3. 可复制配置JSON-RPC 报文、MCP Server 声明与 settings 片段这一节是全文的操作核心。我会分三块给配置第一块是 JSON-RPC 报文本身Request 和 Notification 各给可复制片段第二块是 MCP Server 在客户端里的声明以 Claude Code 的 settings 风格为例第三块是模型通道的配置Base URL Key Model ID 三件套。先看 JSON-RPC 请求。MCP 初始化握手是连接建立后的第一条 Request必须带id服务端必须回响应。你可以直接复制这段{ jsonrpc: 2.0, id: 0, method: initialize, params: { protocolVersion: 0.1.0, clientInfo: { name: my-mcp-client, version: 1.0.0 }, capabilities: {} } }字段含义jsonrpc固定2.0id是客户端自定义编号用来匹配后续响应初始化用 0 是常见约定method是initializeparams里协商协议版本和客户端信息。服务端成功响应会带回同样的id{ jsonrpc: 2.0, id: 0, result: { protocolVersion: 0.1.0, serverInfo: { name: my-mcp-server, version: 0.1.0 }, capabilities: { tools: {} } } }握手完成后列出工具用tools/list调用工具用tools/call。调用工具的 Request 长这样{ jsonrpc: 2.0, id: 3, method: tools/call, params: { name: list_directory, arguments: { path: ./docs } } }注意id必须唯一。如果你并发发多个请求靠id区分哪个响应对应哪个请求。响应里的id会原样带回这是匹配的唯一依据。再看 Notification。它和 Request 的唯一区别就是没有id服务端收到后不需要回复。典型用途是日志推送{ jsonrpc: 2.0, method: logging/message, params: { level: info, message: 开始扫描本地文件夹 } }如果你给 Notification 加了id它就不再是 Notification而变成了一个需要响应的 Request服务端不回你就会一直等这是常见坑。接下来是 MCP Server 在客户端里的声明。以 Claude Code 风格的 settings 为例MCP Server 配置通常放在~/.claude/settings.json或项目级.mcp.json里。一个 stdio 类型的 Server 声明如下{ mcpServers: { my-local-tools: { command: node, args: [/absolute/path/to/mcp-server.js], env: { MCP_LOG_LEVEL: info } } } }command是启动 Server 的可执行文件args是参数env是环境变量。关键点Server 进程的 stdout 只能输出 JSON-RPC 报文日志必须走 stderr。你在 Server 代码里用console.error而不是console.log就是这个原因。最后是模型通道配置。Claude Code 这类工具支持自定义 Base URL 和 Key配置片段如下路径以实际工具为准这里给的是通用结构{ apiBaseUrl: https://taotoken.net/api, apiKey: sk-你的Key, model: claude-sonnet-4-20250514 }三件套齐了Base URL 是https://taotoken.net/apiKey 在控制台创建Model ID 按你实际使用的模型填。如果你用 Codex 的auth.json风格结构类似把 Base URL 和 Key 填进对应字段即可。Cline 的 MCP 配置则在扩展设置里Base URL 同样指向 TaoToken 的 API 地址。配置写完后别急着跑复杂工具先用一个最小 Server 验证链路。下一节给验证步骤。4. 验证请求-响应链路用 stdio 本地跑通一次完整往返验证的目标很简单启动一个 MCP Server 子进程通过 stdin 发一条initializeRequest从 stdout 读到带相同id的响应。跑通这一步后面的tools/list和tools/call都是同一套逻辑。先写一个最小的 MCP Server用 Node.js 举例文件叫mcp-server.jsprocess.stdin.setEncoding(utf8); let buffer ; process.stdin.on(data, (chunk) { buffer chunk; let newlineIndex; while ((newlineIndex buffer.indexOf(\n)) ! -1) { const line buffer.slice(0, newlineIndex).trim(); buffer buffer.slice(newlineIndex 1); if (!line) continue; handleMessage(line); } }); function handleMessage(line) { let msg; try { msg JSON.parse(line); } catch (e) { process.stderr.write(JSON parse error: e.message \n); return; } if (msg.method initialize) { const response { jsonrpc: 2.0, id: msg.id, result: { protocolVersion: 0.1.0, serverInfo: { name: demo-server, version: 0.1.0 }, capabilities: { tools: {} } } }; process.stdout.write(JSON.stringify(response) \n); } else if (msg.method tools/list) { const response { jsonrpc: 2.0, id: msg.id, result: { tools: [ { name: list_directory, description: 列出目录内容, inputSchema: { type: object, properties: { path: { type: string } }, required: [path] } } ] } }; process.stdout.write(JSON.stringify(response) \n); } else if (msg.id ! undefined) { const response { jsonrpc: 2.0, id: msg.id, error: { code: -32601, message: Method not found } }; process.stdout.write(JSON.stringify(response) \n); } // 没有 id 的 Notification 不回复 }这段代码做了三件事按换行切分 stdin 数据、解析 JSON、根据method返回响应。注意process.stderr.write用于错误日志process.stdout.write只用于 JSON-RPC 报文。Notification没有id直接不回复。启动 Servernode /absolute/path/to/mcp-server.js然后手动发一条initialize请求。你可以另开一个终端用管道测试echo {jsonrpc:2.0,id:0,method:initialize,params:{protocolVersion:0.1.0,clientInfo:{name:test,version:1.0.0}}} | node /absolute/path/to/mcp-server.js预期输出是一行 JSONid为 0result.serverInfo.name为demo-server。如果你看到这行输出说明 stdio 链路通了。再测tools/listecho {jsonrpc:2.0,id:2,method:tools/list,params:{}} | node /absolute/path/to/mcp-server.js预期返回tools数组里面有你声明的list_directory。最后测 Notification发一条没有id的消息echo {jsonrpc:2.0,method:logging/message,params:{level:info,message:test}} | node /absolute/path/to/mcp-server.js预期没有任何 stdout 输出因为 Notification 不需要响应。如果这里输出了东西说明你的 Server 错误地给 Notification 回了响应。跑通这三步你就验证了 Request 的请求-响应匹配和 Notification 的静默处理。接下来把 Server 声明写进 MCP Client 的配置Client 会自动完成initialize握手和tools/list拉取。如果 Client 里工具列表刷不出来回到这一节用管道手动测能快速定位是 Server 问题还是 Client 配置问题。5. 常见报错排查401、local proxy failed、reading choices、OAuth这一节对照真实报错逐个拆解。这些错误我在调试 MCP 链路时基本都踩过按出现频率排序。401 Unauthorized。这个错误几乎都出在模型通道的 Key 上不是 MCP 协议层。检查三件事Key 是否复制完整sk-开头那串别漏字符、Base URL 是否写成https://taotoken.net/api不要带多余路径或查询参数、Key 是否已过期或被删除。如果你在 Claude Code 里看到 401去控制台重新创建一个 Key替换配置里的apiKey字段。注意 MCP Server 本身的 stdio 通信不涉及 401这个错误一定来自模型 API 调用。local proxy failed。这个报错通常出现在 Client 尝试连接模型 API 时网络层没通。先确认你的网络能正常访问https://taotoken.net/api可以用curl测一下curl -I https://taotoken.net/api如果返回 HTTP 状态码比如 200 或 401说明网络通问题在 Key 或配置。如果连接超时检查本机网络设置。注意不要使用任何非正规的网络工具正常网络环境下这个地址是可达的。另外确认你的 Client 没有配置额外的本地代理端口有些工具会默认走127.0.0.1:xxxx如果那个端口没服务就会报 local proxy failed把代理设置关掉或指向正确地址。reading choices 报错。这个错误一般出现在模型响应解析阶段字面意思是读取choices字段失败。原因通常是 API 返回的不是预期格式比如返回了一个错误对象而不是正常的 completion 结构。排查步骤先看完整响应体确认error字段是否存在如果存在按错误信息处理多半还是 Key 或模型 ID 问题。另一个常见原因是 Model ID 填错了比如填了一个不存在的模型名API 返回错误结构Client 却按正常结构去读choices就报这个错。确认你填的 Model ID 是 TaoToken 支持的模型标识。OAuth 相关报错。有些 MCP Client 或工具在首次连接时会走 OAuth 流程如果你看到 OAuth 报错通常是因为 Client 期望的鉴权方式和你的配置不匹配。对于 TaoToken 的 API Key 模式你不需要走 OAuth直接在配置里填 Key 即可。如果工具强制要求 OAuth检查是否有“使用 API Key”的选项或者看该工具的文档是否支持自定义 Base URL Key 的模式。Claude Code 和 Cline 都支持 API Key 直填不需要 OAuth。JSON 解析报错Unexpected token。这个错误在 MCP Server 开发中最常见根源是 stdout 被污染。检查你的 Server 代码里有没有console.log、print、或者任何往 stdout 写非 JSON 内容的行为。所有调试信息、日志、异常堆栈都必须走 stderr。另外检查 JSON 内部有没有裸换行JSON-RPC 报文必须单行字符串里的换行要用\n转义。请求发出后一直等不到响应。先确认id是否唯一且正确带回。如果 Server 返回的响应id和请求不一致Client 匹配不上就会一直等。再确认 Server 是否真的处理了该method如果 method 不存在且你没返回错误响应Client 也会挂起。最后检查 stdio 缓冲有些语言的标准输出有缓冲需要手动 flush否则报文卡在缓冲区里发不出来。排查顺序建议先用手动管道测 Server第 4 节的方法确认 Server 本身没问题再检查 Client 配置里的 Base URL、Key、Model ID 三件套最后看网络层。这样能避免在多个变量之间来回猜。6. 把 MCP 工具链接到 TaoToken从模型对话到 Coding Plan 的落地路径链路跑通之后你可以把 MCP 工具调用和 TaoToken 的模型通道组合成完整工作流。MCP Client 负责决策和工具调度TaoToken 负责提供模型能力两者通过 Client 的配置衔接。实际使用中你会在 Client 里看到模型根据你的指令自动选择 MCP 工具、构造tools/call请求、拿到结果后继续推理。如果你想先单独验证模型通道是否正常可以用模型对话页面发一条测试消息确认 Key 和 Base URL 没问题地址是 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmodel_chat。这一步能排除模型侧的问题让你专注调 MCP 协议。如果你主要做长期编码或 Agent 类任务MCP 工具调用会非常频繁建议了解 Coding Plan地址是 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentcoding_plan。它适合需要持续调用模型和工具的场景能减少频繁配置的麻烦。Key 的管理和创建在控制台地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentconsole。API Key 的专门页面是 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentapi_keys创建后记得保存。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentdoc里面有各工具的配置示例。如果你用 Claude Code专门的接入说明在 https://taotoken.net/claude-code?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentclaude_code里面有 settings 配置的完整字段。回到 MCP 本身最后给你一个实用技巧在 Server 里加一个“回显”工具把收到的params原样返回。调试 Client 时先调这个工具确认请求参数完整到达 Server再调真实工具。这样能把“参数没传对”和“工具逻辑有问题”分开。另外Notification 适合做进度推送比如长任务执行到一半发一条logging/messageClient 收到后可以更新 UI但不要指望它触发响应逻辑。Request 和 Notification 的边界守住链路就稳了。
返回列表