MCP 经常被解释成若干 HTTP 请求的组合,但抓包时只盯着路径和状态码,很容易错过真正决定成败的上下文:客户端当前处在哪个会话阶段、双方承诺了哪些能力、工具目录是否仍然有效,以及一次响应应当关联哪条请求。把这些消息放进状态机里,许多看似随机的错误都会变成可定位的迁移失败。
传输层负责把消息送达,JSON-RPC 负责标识调用,而会话顺序决定消息此刻是否有效。
HTTP只是运输层,状态才是主角
一条典型链路至少包含三种身份信息。HTTP 头描述媒体类型并携带服务器返回的会话标识;JSON-RPC 的 id 用于配对请求与结果;method 则声明这次消息要推动什么行为。它们不能相互替代。请求到达正确地址却丢失会话标识,服务端可能把它当成陌生连接;复用了错误的 id,客户端日志又难以把结果归回原任务。
| 线索 | 作用范围 | 排错时要看什么 |
|---|---|---|
| HTTP 状态 | 单次传输 | 请求是否被接受、是否存在网关问题 |
| 会话标识 | 连续交互 | 后续消息是否沿用同一上下文 |
| JSON-RPC id | 一次调用 | 响应与请求能否准确配对 |
| method | 协议动作 | 当前阶段是否允许执行该动作 |

握手要完成两次语义确认
会话起点是 initialize。客户端在参数中给出期望的协议版本、自身信息和可提供的能力;服务器返回最终采用的版本、服务端身份以及工具或资源等能力声明。响应头中的 Mcp-Session-Id 应被客户端保存,并在同一会话的后续请求中继续携带。这里不是简单的连通性测试,而是双方建立共同语境。
收到初始化结果后,客户端还要发送 notifications/initialized,表示已经处理完协商结果并准备进入工作状态。它属于通知,所以没有常规请求 id,也不等待业务结果;传输端返回已接受即可。若客户端省略这一步便直接查询或调用工具,不同实现可能表现出拒绝、超时或状态不一致。排查时应把“服务端返回初始化结果”和“客户端确认就绪”视为两个独立检查点。
工具目录是一份可执行契约
进入就绪状态后,客户端通过 tools/list 获取目录。每个工具不仅有名称和自然语言说明,还应给出输入模式、必填字段以及可能影响执行决策的注解。模型看到的并不是服务器内部函数,而是这份对外契约。名称必须精确匹配,参数类型、枚举范围和必填关系也必须被调用端尊重。
- 名称解决“调用哪一个”的路由问题,不能用含糊简称猜测。
- 描述帮助模型判断适用意图,但不能覆盖结构化模式中的硬约束。
- 输入模式让客户端在发送前进行基础校验,减少无效往返。
- 只读、破坏性、幂等等注解可用于审批和交互提示,却不应替代服务端权限检查。
客户端通常会把多个服务器返回的目录合并为可用工具注册表。合并时需要保留工具与服务器、会话之间的映射;否则模型即使生成了正确名称,调度层也可能把调用送到错误连接。目录缓存还应有清晰的失效策略,因为它只是某个时刻的能力快照。

调用请求如何找到正确执行器
当模型提出使用某项能力,应用先解析工具名称与参数,再构造 tools/call。请求的参数通常由 name 和 arguments 组成,调度器应依据注册表找到对应会话并发送。服务端执行完毕后,可以返回便于模型阅读的文本内容,也可以同时提供结构化结果,供程序继续分页、筛选或渲染。
- 模型输出调用意图,应用层不应把它直接视为已执行结果。
- 客户端依据输入模式校验字段,并在高风险操作前执行授权流程。
- 服务器完成真实操作,把成功数据或协议化错误放进响应。
- 应用将必要结果加入对话上下文,模型据此组织下一步回答。
这里存在两类容易混淆的失败。HTTP 非成功状态通常指向认证、路由或传输问题;HTTP 成功而 JSON-RPC 返回错误,则表示消息已到达协议处理器,但方法、参数或执行过程失败。即便两层都成功,业务结果也可能为空。日志若只记录一个“请求失败”,就会把完全不同的修复路径压成同一条线索。
列表变更为何需要闭环
工具可能上线、修改或暂时停用。若服务器声明支持目录变化通知,能力改变时可发出 notifications/tools/list_changed。这条消息的意义不是直接携带全部新目录,而是让客户端知道原缓存已经过期。客户端收到后再次调用 tools/list,用新结果原子替换注册表,才算完成同步。
刷新过程需要处理并发窗口:旧工具可能正在执行,新对话又已经看见更新后的目录。稳妥做法是让在途调用绑定创建时的会话与契约版本,新请求则读取最新快照;不要在刷新中途逐项修改共享字典。若重新拉取失败,应保留上一份可识别为陈旧的目录,并限制可能造成副作用的调用,而不是悄悄清空全部能力。
抓包排错应沿着关联线索走
面对“工具看得见却用不了”,可以从状态而非正文内容开始检查。先确认初始化请求与结果采用同一协议版本,再确认客户端发送了就绪通知;随后核对每条后续请求是否携带同一会话标识。若目录正常,检查调用名称和参数是否严格符合当时获取的模式;若问题发生在更新之后,则比对通知时间、刷新请求以及注册表替换时刻。
- 为每次请求记录时间、method、JSON-RPC id、会话标识的脱敏摘要和 HTTP 状态。
- 为工具目录计算版本指纹,调用日志写明使用了哪一份快照。
- 将传输错误、协议错误与业务空结果分开统计,避免错误告警。
- 会话结束后主动释放缓存和连接,防止旧标识被下一轮误用。
这种观察方式把 MCP 从一串孤立报文还原为有因果的流程:握手建立共同能力,就绪通知打开工作阶段,目录形成调用契约,执行结果回流给模型,变化通知再驱动目录更新。只要为每次状态迁移保留可关联证据,协议实现与应用调度之间的责任边界就会清楚,排错也不再依赖反复猜测请求体。
本文《从握手到热更新:把MCP调用链读成一台状态机》由 xkmchenmu 发布于 xkmchenmu Blog。 转载请保留原文链接并注明出处。
支付宝扫一扫