
接入 MCPModel Context Protocol服务器的 AI 应用一个绕不开的工程问题是当模型发出工具调用请求而下游 MCP 服务端超时或连接中断时客户端如何表现才算健壮。先看失败本身。MCP 调用本质上是一次客户端-服务端请求。从客户端视角看失败可以分成几类连接建立失败服务不可达、DNS 解析失败、TLS 握手超时。读写超时请求已发出但响应迟迟未到或响应读取到一半中断。业务错误返回语法错误、参数错误、方法不存在、内部错误。状态未知型失败请求发送后连接就断了客户端无法确定服务端是否已经执行。这四类失败的工程含义完全不同。真正需要重试的主要是前两类和最后一类中的一部分参数类错误重试没有意义反而放大负载。关键在于“状态未知”场景——客户端不知道服务端是否已经执行了工具调用。如果被调用的工具是非幂等的比如下单、发送通知盲目重试可能导致重复执行。这是重试设计的第一原则在重试之前先判断这次调用是否安全重试。一个安全的重试流程应该先分类再行动对错误进行分类标记可重试与不可重试。对可重试的调用采用指数退避 最大次数限制。对连续失败的调用进入熔断状态快速失败。对仍然失败的调用走降级路径。这里给出一个通用的重试框架示意代码不绑定具体语言MAX_RETRIES 3 BASE_DELAY 0.5 # 秒 MAX_DELAY 8.0 # 秒 def call_with_retry(client, request): attempt 0 while True: try: return client.invoke(request) except Exception as e: if not is_retryable(e) or attempt MAX_RETRIES: raise delay min(BASE_DELAY * (2 ** attempt), MAX_DELAY) delay random.uniform(0, delay * 0.2) # 加抖动 time.sleep(delay) attempt 1代码里几个要点指数退避0.5s、1s、2s、4s给服务端恢复留出时间窗口。加入随机抖动jitter避免多个客户端同时重试形成“重试风暴”。最大重试次数必须存在且不宜过大。常见实践是 2-3 次这里的 3 只是示例值实际需要根据服务端表现和用户可容忍等待时间决定。超时本身要分层连接超时和读超时分开设置。连接超时连不上就快速失败读超时要给足服务端处理时间。如果只有一个总超时慢响应很容易被误判为不可用。比重试更重要的是熔断机制。重试只解决偶发问题不解决“服务端已过载”的问题。如果服务端已经扛不住持续重试只会让情况更糟。熔断器维护一个连续失败计数超过阈值后直接短路一段时间期间所有请求立即失败并走降级不再进入重试循环。熔断进入半开状态后放少量探活请求成功则关闭熔断失败则继续打开。熔断阈值怎么设没有通用答案但有一个原则阈值应基于服务端恢复所需时间来设计而不是拍脑袋定一个固定数字。实际落地可以先用保守值比如连续失败 5 次触发熔断熔断 30 秒再根据线上监控调整。降级是重试仍失败后的最后一道防线。MCP 客户端场景下降级策略可以分几个层次缓存结果降级如果调用的是相对稳定的查询类工具可以使用最近一次成功结果。注意缓存只适用于无副作用或副作用可接受的调用。默认响应降级对已不可用的下游服务返回固定的“服务暂不可用”提示或默认值保证上层流程不中断。显式降级与用户沟通对需要用户决策的调用降级方案应是“告知用户当前工具不可用、AI 正在使用兜底策略”而不是让 AI 把兜底结果伪装成真实结果。一个示意性的降级实现def invoke_with_fallback(client, request, cache_keyNone): try: result client.invoke(request) if cache_key and is_cacheable(request): cache.set(cache_key, result) return result, live except UnavailableError: if cache_key and cache.get(cache_key): return cache.get(cache_key), cache return default_response(request), degraded这里有一个容易被忽略的问题降级响应的可观测性。缓存的旧值、默认值、真实值混在一起如果不在响应中标记来源排障时根本无法定位。建议在响应中加入来源标记例如source: live | cache | degraded并记录到日志。这样才能区分“结果本来就是空值”和“结果来自降级路径”。长任务场景值得单独讨论。MCP 服务端可能存在执行时间较长的工具调用。这时候“客户端超时”不代表“工具执行失败”。处理思路有两种一种是在协议支持范围内调大读超时并配合流式响应另一种是把长任务建模为异步操作——先返回任务标识再轮询或订阅结果。具体采用哪种取决于服务端实现是否支持异步模式集成方不能凭空假设。还有一个通用建议不要对长任务使用与普通请求相同的短超时和激进重试策略否则会产生大量重复任务。最后给出一组可直接对照的工程检查清单请求超时是否分层配置连接超时与读超时是否分离错误分类中是否明确哪些错误可重试、哪些不可重试被调用的工具是否幂等非幂等调用的重试是否被禁止重试次数是否设置了最大上限退避是否是指数退避且带抖动是否实现了熔断熔断后的降级路径是否完整降级响应是否带有来源标记日志能否区分真实结果与兜底结果长任务调用是否与普通请求使用不同的超时和重试策略重试、熔断、降级三者是否形成完整链路而不是互相独立这八项不是标准而是一套评估基线。实际配置多少秒、重试几次必须结合你自己的服务端表现来调整。协议不规定这些值工程上也不能指望规定。本文没有依赖特定 MCP 实现细节因为不同客户端与服务器版本的差异客观存在。落地时的正确做法是查阅你所用 MCP 客户端与服务器版本的具体文档确认超时相关参数是否可配置、错误码如何分类、是否支持流式或异步任务然后在此基础上应用上述重试与降级框架。