MCP 协议拆解:一次工具调用背后,到底发生了什么?

MCP 协议拆解:一次工具调用背后,到底发生了什么?
上周调一个 PHP 实现的 MCP Server,遇到个诡异的问题:Claude Code 发起工具调用后,返回的数据总是少一段。不是数据丢包,不是网络断开,返回的 JSON 格式也对,但就是「不全」。折腾了半天,寻思直接扒一下协议层到底怎么跑的。结果这一扒,发现了很多平时写 MCP Server 时根本不会注意到的细节。如果你也在写或者打算写 MCP Server,这几个坑值得提前知道。工具调用的完整生命周期先说结论:一次 MCP 工具调用,从客户端请求到服务端返回,中间走的是 JSON-RPC 2.0 协议。你平时调个接口返回数据可能就几十毫秒,但 MCP 的一轮调用要经过下面这几个环节:Capability 协商 → ListTools → 客户端发起工具调用请求 → 服务端校验 → 执行 → 返回结果。每一步都比想象中多了一些细枝末节的约定。Capability 协商不是白送的我最早写 MCP Server 的时候,以为 capability 就是走个形式。双方握手时报一下「我支持什么」就完了。后来发现不是这样的。在initialize阶段,客户端发起的 capabilities 和服务端回复的 capabilities 决定了整个会话里哪些功能可用。比如客户端不报toolscapability,服务端就不该响应tools/list请求。// 客户端 initialize 请求 { "jsonrpc": "2.0", "id": 1, "method":