Alex Cheng
本文目录01 · 三层职责02 · 消息与结果03 · 选择传输04 · 版本与旧机制05 · 完整应用方案06 · 故障验收与扩展
← Agent、Skill 与 MCP

MCP 进阶:远程调用、任务状态与故障恢复

从消息与传输到任务追踪,设计可以检查、重试和恢复的 MCP 应用。

Alex Cheng · 2026-09-10

本地能调用工具,换成远程服务却超时;进度显示到了 100%,结果却没有保存;重试一次,同一份报告出现了两份。遇到这些问题时,继续研究工具名称通常没有用,应该沿着请求、传输和业务状态寻找断点。

MCP 基础篇解释了谁负责什么。这里进一步讨论一次调用如何跨进程、跨网络完成,以及连接中断后怎样知道事情到底发生了没有。

连接成功只证明消息能到达。要让应用可靠,还要知道消息对应哪项工作、失败停在哪里,以及重试会带来什么结果。

本文用“批量检查 20 份需求文档,生成检查报告”的虚构任务贯穿。先拆机制,再给实施顺序与验收清单。涉及协议行为时区分版本:2026-07-28 已调整核心交互,旧实现的握手和会话经验不能直接套到所有新服务上。

01 / 定义:分清消息、传输和业务状态

可以用寄送工作单来理解这三层:工作单写什么、通过什么送达、业务是否办完,是三个不同问题。消息描述操作;传输负责递送;业务代码负责真正读取资料、处理数据和保存结果。

层次负责的问题典型故障
协议消息调用什么方法,参数如何表达,回应如何对应参数结构错、方法不支持、版本不匹配
传输消息如何在进程或网络间到达进程退出、连接中断、代理超时
业务执行读取哪些文件,检查哪些规则,结果保存在哪里越权、文件损坏、重复生成、结果未落盘

一个 HTTP 200 不能直接证明检查成功:响应里仍可能是工具失败。连接超时也不能直接证明业务没发生:服务端可能完成了操作,只是客户端没有拿到回应。排错必须保留这两种可能。

图 1 · 沿实际断点排错,避免用“重新连接”处理业务错误。

图里的每一层都留下自己的证据。消息层记录方法与关联标识,传输层记录状态与时间,业务层记录任务与结果位置。把它们关联起来,才知道一次错误发生在进入业务之前还是之后。

02 / 推导:为什么进度不能代替结果

典型 JSON-RPC 请求有 id,响应用相应标识匹配请求;通知不要求同样的回应。这个差别决定了你应当怎样解释“检查到第 12 份”和“检查完成”。

  1. 用户发起任务。应用记录一次业务任务,例如检查指定的 20 份文档。
  2. 工具开始处理。可能产生阶段反馈、诊断记录或中间数据。
  3. 中间反馈到达。它说明某个阶段发生过,不保证后面的保存步骤已经完成。
  4. 最终结果返回。仍要检查成功/失败、处理数量、未处理列表和报告是否可读取。

下面是概念层的关联方式,不是可以直接发送的完整协议报文。具体字段由使用的 SDK 和协议版本生成。

业务任务:检查文档清单 B-01
一次调用:request_id = 42
中间状态:已检查 12 / 20,尚未完成
最终结果:成功 18,失败 2,报告句柄 report-7
回读检查:report-7 可访问,包含失败文档与原因

协议请求 ID 与业务任务 ID 不要混用。重连或重试可能产生新的请求,但仍在处理同一项业务。若用每次请求的新 ID 去判断重复工作,服务端就可能重复创建报告。

进度最好表达实际工作量:已检查多少份、正在保存还是等待结果。若无法估计总量,显示阶段比编造百分比更诚实。日志用于诊断,进度用于理解执行状态,最终结果用于验收,三者不能互相替代。

03 / 选择:本地 stdio 与远程 HTTP

业务含义清楚后,再决定怎样连接。stdio 常见于客户端启动本地子进程:请求进入标准输入,协议消息从标准输出返回。Streamable HTTP 则通过 HTTP 接口连接服务,适合跨机器访问。远程化带来的不只是一个 URL,还有认证、代理、超时和部署维护。

判断项stdioStreamable HTTP
典型使用本地工具、个人练习、客户端管理子进程多个客户端访问集中服务
先查什么可执行路径、参数、工作目录、环境变量URL、认证、网络链路、协议兼容
容易混淆程序没有终端输出,不一定是卡死,可能在等待协议输入HTTP 可达,不代表有权限调用目标工具
典型陷阱普通调试日志写入 stdout,破坏协议消息代理缓冲或超时影响流式反馈与长请求

stdio 服务的普通诊断日志应与协议输出分开,例如写入 stderr。不要把“加一行 print 看看”当无害调试;它可能使客户端在解析消息时失败。

HTTP 中,流式响应与是否保存业务状态是两个维度。SSE 可以让一个响应陆续送出事件,不能据此断言服务必须持有业务会话;无状态请求也不意味着业务不能有任务记录。检查 SDK 配置时,先弄清它改变哪一层,再测试实际行为。

04 / 版本:读懂旧机制,避免新项目走错路

旧代码中常见 initialize、会话 ID、Sampling、Roots 等术语。它们值得读懂,因为排查存量系统会遇到;但不要把“见过”当作“新项目应采用”。

机制原本解决什么现在怎样判断
旧版初始化与会话协商能力,并维护连接相关状态2026-07-28 核心已转向自描述的无状态请求;先看双方实际协议版本
Sampling服务端请求客户端代为调用模型已弃用;新实现先考虑明确的模型调用归属
Roots客户端告知相关目录或文件范围已弃用;以显式参数或服务配置表达范围,并单独执行权限检查
协议 Logging将服务端诊断消息传给客户端已弃用;不等于应用不需要日志

这里“弃用”意味着新设计不再优先依赖,不等于存量 SDK 当天停止支持。迁移应查看兼容范围,分阶段替换,再复测。尤其不能把进度反馈、普通运行日志和旧协议 Logging 当成一个开关。

新版 MRTR(多轮往返请求)把“执行中还需要输入”表达为返回待补输入,客户端取得答案后继续调用。理解时抓住业务含义即可:等待输入还没有完成,补齐后仍需继续执行并验收。不要把它和旧版服务端主动请求的具体报文混写。

Roots 从来不该替代访问控制。“这个目录与任务相关”是上下文;“这个身份可以读取这里”是权限。路径解析、越界检查和操作系统权限仍要落实。目录参数也一样,不能允许客户端传任意路径就直接读取。

例如批量检查器需要模型归纳时,可以由应用拿到检查结果后统一调用模型,也可以由服务端在明确预算与凭证边界下调用。归属取决于系统职责。不能因为旧 Sampling 把调用转给客户端,就宣称模型成本自动消失。

05 / 完整方案:让批量检查器可中断、可查询

现在把前面几层组合起来。目标不是把所有高级能力都用一遍,而是让读者能对“20 份文档的检查结果在哪里”给出明确答案。以下是一种业务接口设计,工具名由我们定义,不是 MCP 内置方法。

① 冻结输入与完成标准

  • 练习资料位于一个专用目录,共 20 个 Markdown 文件;每份有唯一编号和版本。
  • 检查三项:是否写明责任人、异常处理、验收条件。先采用确定性规则,模型归纳留到流程跑通后。
  • 结果必须逐文档列出通过、缺项或读取失败;20 份都必须有去向。
  • 重复提交相同业务请求不能生成两份独立报告;同一请求键更换输入应被拒绝。

② 定义业务任务与三个工具

start_review(project_id, document_ids, request_key)
  → job_id, state
get_review(job_id)
  → state, processed, total, failures, report_id
read_report(report_id)
  → documents, findings, failed_documents, input_versions

state: queued → running → succeeded / partial / failed

这是自定义业务工具,不冒充协议的 Tasks 扩展。状态、结果和任务归属需要实际保存;job_id 不是访问令牌,每次查询还要检查当前身份能否访问对应任务。小型本地练习可以用文件或 SQLite,远程多实例需要适当的共享存储。

③ 先实现和验证业务层

请在独立练习项目实现批量需求检查器。
先做不依赖模型的业务函数,再包成 MCP 工具。
输入文件只从配置的练习目录读取,拒绝越界路径。
每个文档检查“责任人”“异常处理”“验收条件”三个标题。
一个文件读取失败,不把整批假装成全部成功。
持久化任务、输入版本、逐文件结果与报告。
request_key 与规范化后的输入绑定:重复相同输入返回同一任务;
相同键但不同输入报冲突。
实现 start_review、get_review、read_report。
运行前报告 SDK 与协议版本,使用该版本适配层,
不要混用旧握手、会话机制与新版字段。
交付启动方法、业务测试、MCP 调用记录和未完成项。

这份实现任务需要编码助手真正写出代码和测试,不能把返回方案当作已建成系统。先直接调用业务函数验证数量、缺项和失败,再接 MCP。否则出错时难以区分文件处理与协议连接。

④ 接上客户端并做一轮观察

  1. 启动练习服务,用支持所选版本的测试客户端或 Inspector 连接。先确认发现的工具名和输入结构。
  2. 调用 start_review,传入明确的 20 个文档编号与 request_key=review-demo-01。
  3. 记录返回的 job_id,定期调用 get_review。使用固定低频间隔即可,不要无休止快速轮询。
  4. 出现终态后读取报告,对照 20 个输入编号逐个核对。成功数与失败数之和应为 20。
  5. 同一输入重提一次,确认返回原任务;更换一份输入但复用原键,确认返回冲突。

采用查询接口,进度就不必完全依赖一条持续不断的连接。客户端关闭后可用任务编号重新查询;但前提是任务和结果真的持久化。仅把变量存在进程内,重启仍会丢失。

图 2 · 业务任务记录把“连接是否还在”与“工作是否完成”分开。

读图时关注断连后的分支:先查询已有任务,不直接创建新任务。只有确认任务不存在或允许重做,才执行下一步;未知状态不是“可以放心重试”的同义词。

06 / 验收与扩展:主动制造故障,观察真实结果

正常跑完一批只是起点。下面的检查应在练习环境进行,每次保留输入、操作、状态变化和最终报告。它们检验的是业务闭环,不能被“客户端显示已连接”替代。

操作预期结果不通过时优先检查
让第 7 份文档不可读取该文档明确失败,其余结果仍可核对是否把异常吞掉或整批误标成功
处理期间断开客户端重连后按任务号查到真实状态状态是否仅存于连接或内存
重复发送同一个请求键返回同一任务,不重复创建是否在业务层原子判断重复
换输入但保留原键报冲突,不返回无关旧结果请求键是否绑定输入摘要
用其他项目身份查询任务拒绝访问,不泄漏结果是否把知道 job_id 当作授权
处理进程退出后重启恢复可恢复任务,或显式标记中断恢复策略与状态保存是否一致
结果保存失败不能只因 processed=20 宣布完成完成状态是否早于持久化提交

扩到远程 HTTP 时,用实际部署的传输再跑一遍。加入反向代理后检查超时与响应缓冲;多个实例时检查任务记录是否共享;取消任务时定义“请求取消”与“已取消”的区别,部分产生的结果是否保留也要写清。

再加入模型总结时,保留原始检查结果,单独记录模型输入范围和输出。模型说“整体合格”不能覆盖失败文档;模型调用失败也不应抹掉已经完成的确定性检查。

换个场景:导出任务超时后,为什么不能立即再点一次?

因为超时只说明没有及时得到回应,第一次导出可能已完成。先通过任务号或业务请求键确认状态,再决定继续等待、回读结果或重试。若系统根本没有查询能力,就需要补业务追踪和去重设计,而不是只把超时时间调大。

进阶机制最终应回到这个判断:它是否让业务状态更清楚、故障更可定位、恢复更可控。懂得少用一个不必要的机制,也是一种设计能力。

交流产品判断与 AI 实践 →
MCP 图解放大视图