让 MCP 错误信息支持智能体自主恢复
文章建议将工具执行失败返回为带稳定错误码和消息的结构化结果,并区分协议错误与工具错误。限流案例表明,明确的可重试信息能帮助智能体等待后恢复,而泛化异常可能导致任务直接终止。
AI 深度解读
MCP 工具查客户资料时遇到上游限流,Agent 应该等待重试,还是向用户道歉后放弃?作者的案例里,同一个 Agent 出现了两种表现,差别在于工具有没有把失败原因表达清楚。
作者主张:工具执行失败时,应返回带稳定错误码和重试信息的结构化结果,把异常堆栈留在服务端日志。对正在给 AI 接数据库、内部接口或业务工具的程序员来说,这篇文章讨论的是一个实际问题:怎样让 Agent 在失败后知道下一步该做什么。
工具执行失败,为什么不该直接抛异常
作者团队有一个查询客户资料的内部工具。上游服务触发限流后,最初的实现直接让 HTTP 客户端抛出异常。MCP 服务端最终返回通用内部错误和堆栈,Agent 没找到可用于决定后续动作的信息,于是向用户道歉并停止。
后来,他们把失败转换成带明确错误码的结构化结果。同一个 Agent 识别出了可重试的情况,等待后恢复执行,没有再让用户介入。
这里要区分两层失败:
- 协议或通信出了问题,请求本身无法正常处理。
- 工具已经执行,但业务任务没有完成,例如查不到记录、权限不足、发生冲突或上游限流。
按照原文的划分,前者属于 JSON-RPC 错误的处理范围;后者应返回工具结果,将 isError 设为 true,并提供模型能读取的错误内容。
工具失败的返回值,也是 Agent 决策所依赖的接口契约。
异常堆栈擅长回答「程序在哪里出错」,却未必能回答「调用方接下来怎么办」。它还可能带出内部路径、主机名和依赖库细节。作者因此建议:完整异常进入服务端日志,对外只返回稳定、适合用户阅读的信息。
错误字段应该围绕下一步动作设计
结构化错误的价值不在于把一句报错包进 JSON,而在于让调用方能选择动作。
作者建议定义一小组稳定错误码,在工具边界把内部异常映射到这些错误码。处理函数知道具体失败原因时,抛出对应的 ToolError;外层再统一捕获,把未知异常转换成安全的通用错误。
这是一种职责划分:内部保留足够的排障细节,外部保持可预测的返回约定。库升级或上游报错文案变化时,Agent 不必跟着重新理解一套描述。
原文特别强调 retryable 布尔字段。模型看到超时或 HTTP 409,不能仅凭文字可靠判断重试是否安全。工具应明确给出可否重试;已知等待时长时,也应一并返回。
但 retryable: true 还需要配套执行规则。作者要求验证:限流是否触发有次数上限、带退避的重试;校验失败是否让下一次请求修改参数;禁止访问是否让 Agent 停止并向用户说明。
还有一类错误应该更早拦住。能由严格输入 schema 判断的参数问题,应尽量在处理函数运行前拒绝。只有执行后才能知道的情况,例如上游记录不存在、权限范围不足或服务限流,才留给结构化工具错误处理。
原文提到了错误结果示例和对照表,但提供的正文没有实际代码或表格内容。因此,具体错误码名称、等待时长字段以及完整返回结构,都没有交代清楚,不能直接当成一份可复制的实现规范。
我的判断
我认同作者把错误处理看作调用契约的一部分。普通程序调用 API,需要状态码和响应字段;Agent 调工具,同样需要明确的失败类别。只给一段异常文本,相当于要求调用方从调试信息里猜业务语义。
不过,作者提供的是一次内部工具的前后观察,没有给出模型版本、重复测试次数、恢复成功率或其他方案的对照数据。「改成结构化结果后,这次恢复了」有案例支撑;「结构化结果能让所有 Agent 稳定恢复」则超出了原文证据。
retryable 也有前提。查询客户资料遇到限流,通常比较容易制定重试策略;执行中断后的操作是否已经生效,原文没有展开。可重试标志需要建立在工具对执行结果的判断上。 单独增加一个布尔字段,无法替调用方解决所有不确定性。
和让模型阅读自然语言报错相比,稳定字段更适合编排程序直接分支处理;但错误码、模型提示和重试执行逻辑仍要配合。作者提出的行为测试,比「返回格式已经改好」更值得作为验收标准。
能拿来做什么
- 给现有 Node MCP 工具增加统一错误出口,将已知执行失败映射为稳定错误码,把完整异常留在日志,并用安全的通用结果兜住未知异常。
- 把输入检查前移到 schema,先拦截能提前判断的参数问题,再单独处理记录不存在、权限不足和上游限流等运行时失败。
- 为允许重试的错误提供明确的
retryable信息和已知等待时长,同时在 Agent 调用流程中设置重试次数上限与退避规则。 - 验收 AI 工具时检查后续行为:遇到限流是否等待,参数错误是否修正请求,权限不足是否停止。不要只检查返回值里有没有错误字段。