周五下午三点二十五分,Agent编排在第三步卡住了。
日志里那句 tool_call_timeout exceeded 已经红了快三十秒。第三步是一个查库存接口,逻辑很简单:调供应商接口,拿到JSON,过滤出在架商品数量。代码是上周刚review过的,prompt写得工整,tool schema配得标准——按理说不该出问题。
但它就卡在那里,挂在 Awaiting response 状态。
运维群里弹出告警的时候,我正在和后端对另一个接口的返回格式。点进去一看:供应商那边响应时间从平时的 200ms 跳到了 8 秒,直接把我们的重试窗口撑爆了。
Agent 不知道该怎么办。它拿到了超时错误,但不知道这个错误是「网络抖动」还是「服务挂了」。它也不知道要不要重试、重试几次、间隔多少秒。这些判断,本来应该是编排层来处理的事情,但我们当时全权委托给了 MCP 客户端——而那个客户端,抄的是官方示例,连指数退避都没做。
MCP是个好协议,但好协议不会替你处理网络抖动。
这句话听着像废话,但真正踩过这个坑的人会懂:MCP 定义了「工具长什么样」「调用怎么发出去」「结果怎么拿回来」,但它没有定义「调用失败了怎么办」「重试几次算合理」「幂等性怎么保证」。这些可靠性细节,是协议设计者留给开发者的空白——也是面试官最爱追问的地方。

Agent 工具调用超时故障链路
故障持续了四分钟,峰值积压了 37 个请求。后来加了本地重试 + 降级逻辑,问题才解决。但那个下午,我重新翻了 MCP 的协议文档和 Function Calling 的训练机制,发现有些设计取舍,欠下的债迟早要还。
MCP 定义了「工具长什么样」「调用怎么发出去」「结果怎么返回」,唯独没定义「调用失败了怎么办」。这把可靠性设计的自由度留给了实现者,也把踩坑的概率留给了后来人。
我翻了一下当时的客户端代码,重试逻辑长这样:
try:
result = await client.call_tool("query_inventory", params)
except TimeoutError:
result = None # 直接放弃
没有指数退避,没有熔断,没有根据错误类型区分处理。如果这是人类在处理,大概率会想「再试一次看看」,等两秒,再试,再等四秒。但代码里这段等值重试都没有。

当时那个MCP客户端的重试策略,朴素到令人心痛
MCP 协议的定位,本质上是一个「传输层规范」——它解决的是「工具调用格式统一」的问题,而不是「调用可靠性」的问题。这两个问题是正交的。一个协议不能既解决互操作性,又顺手帮你把重试策略写了。它选了前者,把后者开放给了实现者。
这个选择有它的道理:不同业务场景对可靠性的要求天差地别。读一次缓存数据失败了要不要重试?当然不。但库存查询失败了?可能得重三次,每次间隔更长。协议层没法做这种判断。
但问题在于,大多数开发者在引入 MCP 的时候,容易把它当成「一揽子解决方案」。协议文档告诉你怎么定义工具、怎么发请求、怎么处理结果——它把好写的部分都写了,留下一个「失败处理」的空壳,等你自己填。而当你自己填的时候,你往往低估了这个空壳的重量。
我后来复盘,发现我们组踩的坑不算最离谱的。有人曾经完全信任 MCP 的 tool call 结果,不做额外校验,直接拿来做业务决策。结果模型hallucinate了一个不存在的tool返回值,业务逻辑按这个假数据执行了——因为MCP只管「格式正确」,不管「值正确」。
这就是协议边界。理解了这一点,才能理解为什么MCP「不是银弹」。

MCP协议职责边界
说白了,MCP 给你的东西就像毛坯房:水管、电线、墙都给你留好了,但马桶要不要换、厨房灶台选什么品牌,你得自己决定。很多人看协议文档觉得「这不挺完整吗」,结果拎包入住之后才发现,漏水的那段水管协议根本不管。
本质就是:MCP 定义了「工具长什么样」「调用怎么发出去」「结果怎么回来」,但它不管「网络抖了怎么办」。
二、排查过程
先拉一下当时的调用链路。

Agent工具调用链路
问题出在哪一目了然:链路里每个节点都在做自己该做的事,LLM生成请求、MCP转发调用、供应商返回结果——唯独没人负责「结果没按时回来该怎么办」这个灵魂拷问。
我去翻那个 MCP 客户端的源码。找到重试逻辑的位置时,我笑了——那个 retry 函数体里,只有一行注释:
# TODO: implement retry with exponential backoff
raise TimeoutError()
TODO 还是红色的。这代码估计从官方示例复制过来就没动过。
但真正让我愣住的,是翻到 MCP 协议规范里那段话:
MCP 的设计原则之一:保持协议的简洁性,将可靠性策略留给上层实现。
好,简洁。优雅。问题是,我们的「上层实现」就是那个连 TODO 都没清的官方示例。
接下来我把供应商的监控拉出来看。8 秒响应持续了大概 45 秒,然后自己恢复了。这不是服务挂了,是典型的网络抖动——某个链路的 TCP 重传延迟了一下,供应商的服务本身健康得很。
如果是人类工程师在处理这个接口,他会怎么做?
先重试一次,等两秒;还不行,再等四秒;再不行,等八秒,同时告警;连续三次都超时,那才考虑「服务可能真挂了」。
Agent 不知道这些。它只会拿着错误信息发呆,等人类来救。

这就是「排查过程」里最值钱的一课:网络抖动和永久故障,处理逻辑完全不同,但它们在 MCP 的错误返回里长一个样。协议不负责区分,Agent 的编排层得自己判断。
所以我们后来做了两件事:第一,给 MCP 客户端补上指数退避重试;第二,在编排层加了错误分类器——超时小于 5 秒且有历史正常记录的,走快速重试;超时超过 30 秒的,或者连续超时的,直接切备用供应商。
不是什么高深的设计,就是把「网络会抖」这件事承认了,然后认真对待。
这和前面的教训一脉相承:MCP 定义了「工具长什么样」「调用怎么发出去」「结果怎么回来」,但把超时策略、重试机制、错误分类这些可靠性保障全留给了编排层。
这不是MCP的疏忽,而是一种刻意的设计取舍——协议层保持简洁,把复杂度交给应用层。问题是,应用层往往也没想清楚这些边界在哪里。

这里的信息密度不小
三、MCP协议可靠性设计
1. 协议边界:什么它管,什么不管
先说清楚MCP的Scope。
MCP协议本身只负责三件事:
- 工具描述:定义一个工具叫什么名字、接受什么参数、返回什么格式
- 调用传输:把工具调用请求发出去,把结果拿回来
- 上下文注入:把工具返回的数据塞进prompt上下文

MCP协议的三件事:描述、传输、上下文
协议层不管的事包括:超时设置、重试次数、退避策略、错误分类、幂等保证、连接池管理。这些全都甩给了客户端实现。
换句话说,MCP给你的是一张标准化的「工牌」——工具能证明自己是谁、有什么用,但工具不保证自己永远在线。
这就引出了第一个设计取舍:协议的通用性 vs 可靠性。MCP选择了通用性,所以它能连接Slack、Github、数据库、文件系统这些完全不同的东西。但代价是,每个具体的工具调用链路,都需要应用层自己保障可靠性。
2. 工具调用的容错设计原则
说到工具可靠性设计,有几个实战中特别容易翻车的地方。
第一,路径问题。 很多工具设计用相对路径,比如 edit_file('./src/utils.js')。这在单人单会话的场景下没问题,但Agent在多步操作中会 cd 来 cd 去,当前工作目录随时在变。一旦相对路径对不上上下文,整个任务链就断了。解决方案很简单:永远用绝对路径,或者让工具自己维护一个「起点」的概念。
第二,格式复杂度。 有的工具返回嵌套三层的JSON,还有的要求Agent输出精确到行号的diff格式。这不是「让返回更丰富」,这是给Agent上刑。真实项目里我见过Agent花了一半Token在思考「这个引号怎么转义」,而不是在想业务逻辑。工具设计应该像设计后端API一样——参数明确、返回简洁、错误码清晰。
第三,状态假设。 有些工具隐式依赖状态,比如「上次查询的结果」。但Agent的执行顺序是不确定的——它可能并行调用多个工具,也可能因为重试而重复执行。工具必须是幂等的,或者状态必须是显式传递的。

工具可靠性设计三原则
3. 编排层必须兜底的五件事
回到故障本身。供应商接口超时这件事,MCP协议不会替你处理,Agent本身也不会处理——因为它没有这个信息。那编排层需要兜底哪些事?
第一,超时配置要分场景。 不同工具的合理超时不一样:查本地配置可能是50ms,调外部供应商可能是10秒,调大模型推理可能是60秒。超时设置不能一刀切,也不能全设成30秒等上帝。
第二,重试策略要分错误类型。 网络超时值得重试,401未授权不值得重试,500服务器错误可以重试一次,400参数错误重试也没用。MCP客户端需要能区分这些,而不是对所有错误一视同仁。
第三,熔断机制要有。 如果一个工具连续失败,编排层应该主动降低调用频率,而不是继续往上面撞。指数退避是最基础的实现,更激进的方案是直接熔断切到降级逻辑。
第四,幂等保证要明确。 工具本身要支持幂等,或者编排层要给工具调用加上调用ID,确保重试不会导致重复操作。比如库存扣减这种操作,不能因为网络超时就扣两次。
第五,可观测性要到位。 超时了要知道是哪个环节超时,重试了要知道重试了几次,失败了要知道失败原因是什么。日志要打清楚,链路追踪要能串起来。

编排层可靠性保障
这次故障最后怎么解决的?临时方案是把超时从3秒改成10秒,加上指数退避,让供应商那边恢复的时候能自动重连进来。但根源上是编排层的设计缺陷——我们把太多可靠性保障寄托在了一个「够用就行」的MCP客户端上,而那个客户端的示例代码,从来就不是为生产环境设计的。
所以回到那句话:MCP是个好协议,但好协议不会替你处理网络抖动。 它给你的是一张工牌,不是保险单。工牌让你能进门,保险单才让你摔倒了有人扶。编排层的活儿,就是给自己上这份保险。
踩过这个坑的人自然明白:MCP 定义了「工具长什么样」「调用怎么发出去」「结果怎么拿回来」,但它不定义「网络抖了怎么办」「超时了要不要重试」「重试几次间隔多久」。
协议归协议,工程归工程。

值得停下来想一想
四、工程教训
故障恢复之后,我们做了两件事。第一件事是把那个抄来的官方示例客户端换掉了,换成了带指数退避和熔断的自定义实现。第二件事是给每个工具调用都加了一个 retry_policy 字段,写清楚什么错误码该重试、什么该直接失败。
这两件事听起来平平无奇,但背后藏着我们踩过的三个真坑。
第一个坑:工具设计得太像「给人用」
当时库存接口的设计是 edit_file('./src/utils.js') 这种风格——相对路径加自然语言描述。我们觉得这样好理解,prompt 里写「请调用查库存工具」就行。
但 Agent 在多步操作中已经通过别的工具 cd 到了别的目录,它完全忘了自己「身在何处」,导致路径解析失败。这个 bug 藏在第四步,排查的时候绕了很久。
教训:工具参数要像设计后端 API 一样设计。永远用绝对路径,永远让错误变得不可能发生。 update_stock(sku: str, warehouse_id: str) 比「查一下库存」好一万倍。
第二个坑:编排层把「容错」当成了默认值
我们默认了「网络不会抖」「服务不会挂」「返回格式永远正确」。这三个默认值,在测试环境成立,在生产环境全部被打破。
当时排查的时候发现,供应商返回的 JSON 里有个字段叫 in_stock,值是字符串 "true" 而不是布尔值 true。JSON 解析没报错,但后续的 if in_stock 判断直接把字符串当 True 处理了——Python 里 "false" 也是真值。
教训:编排层必须兜底的东西,比你想象的多。输入校验、超时处理、重试策略、错误分类——这些不能靠默认,得自己写。
第三个坑:把「框架能力」当成了「自己的本事」
出问题之后翻官方文档才发现,MCP 客户端库里其实有 RequestOptions 支持自定义超时和重试。我们没用,是因为 demo 里没写,review 的时候也没人提。
技术选型的时候,最容易踩的坑是「demo 怎么写我们就怎么用」。demo 为了展示核心逻辑,容错代码全砍掉了;生产环境照搬 demo,就是给自己埋雷。
教训:看 demo 学概念可以,拿 demo 当生产代码不行。容错、监控、边界情况,这些东西 demo 永远不会教你,只能靠踩坑学到。
事故当天,我们在群里发了三个改动点:一个改了客户端,两个改了编排层。后来 QA 问:「这个改完测了哪些场景?」
我回:「测了网络抖动、测了服务挂了、测了返回格式不对。」
他发了个「好家伙」的表情包。

这是理解全篇的关键
五、构建可靠的Agent编排
故障修完,代码回滚,周一复盘。Leader问了个问题:下次怎么让这套东西自己扛住,而不是靠人肉盯盘?
说实话,我没有标准答案。但有几个在踩坑路上逐渐清晰的方向。
核心原则只有一条:让Agent做决策,让基础设施扛韧性。
Agent擅长的是意图理解、路径规划、结果判断。把网络抖动、接口超时、依赖服务不可用这些破事交给它决策,就像让博士后去算今天外卖几点送到——既浪费又容易出错。
这意味着编排层的责任清单要重新划界:

Agent编排层职责边界
说人话:写Agent prompt的时候,只管告诉它「要做什么」「什么算成功」「什么算失败」。别让它操心重试几次、超时设多少、要不要熔断。
工具设计的第一性原则:像给机器写API,而不是给同事写文档。
接口命名要无歧义,参数边界要清晰,返回格式要可预期。把edit_file('./src/utils.js')改成update_file(path: absolute_path, content: string),相对路径的坑就填了一半。
编排层的五个必做项:
超时控制:对下游P99响应时间做统计,超出两倍P99就降级而非死等。重试策略:指数退避配最大重试次数和熔断阈值,防止雪崩。依赖隔离:关键工具单独配置连接池。健康检查:定期ping下游服务,状态异常提前标记。降级兜底:接口不可用时给默认返回值或跳过该步骤。

工具可靠性设计
Anthropic那篇《Building Effective Agents》说过一句话:最好的Agent系统不是功能最多的,而是最知道自己能力边界的。
MCP协议把工具标准化了,但标准化不等于可靠。一套能在生产环境稳定跑着的Agent编排系统,70%的功夫在协议之外:容错、降级、超时、监控。这些东西枯燥、重复、不酷,但关键时刻真能让你在周五下午三点二十五分准时下班。

背后的逻辑值得细品
参考文献
[1] 2026年最新AI agent面试(03)_工具协议MCP_A2A_FC大家好,我是浩哥,这是我输出AI agent面试 - 掘金. https://juejin.cn/post/7662637781215068223 [2] 2026年最新AI agent面试(01)_Agent基础与推理范式_人工智能_sijingqian-智能体开发者社区. https://adg.csdn.net/6a56fdff662f9a54cb8f82ea.html [3] 2026年Agent大厂面试题汇总:ReAct、Function Calling、MCP. https://zhuanlan.zhihu.com/p/2028511483969937686 [4] 2026 年MCP 面试题怎么答:真正做过Agent 的人会怎么讲. https://interviewaibox.co/zh/blog/mcp-interview-questions-guide-2026 [5] 我们复盘了100个失败的AI Agent项目,总结出这3个“必踩的坑”在AI圈,你一定见过这样的场景:一个炫酷的Agen - 掘金. https://juejin.cn/post/7516910928668852259 [6] AI Agent 工作流错误偏差累积问题. https://blog.csdn.net/Jailman/article/details/149575405 [7] 2026 AI Agent开发完全指南:从MCP协议到多Agent协作系统_人工智能_今天AI了吗-智能体开发者社区. https://adg.csdn.net/6a5345f2662f9a54cb8e916d.html [8] Agent编排的10个常见错误:生产环境踩坑总结-腾讯云开发者社区-腾讯云. https://developer.cloud.tencent.com/article/2669801