最近接 MCP 工具时有个感受:让 agent 能调工具其实不难,难的是工具出错以后它怎么收场。
很多 demo 都停在“模型发现工具,然后调用工具,最后返回结果”。这个流程当然重要,但一到真实项目里,问题马上会变多:
- 工具参数不完整。
- MCP server 连不上。
- 工具返回了业务错误。
- 工具返回内容太长。
- 工具结果里混了不该给模型看的内容。
- 用户问的是高风险操作,但 agent 没有停下来确认。
所以我现在更愿意把 MCP 看成一个工具协议,而不是完整的 agent 方案。MCP 解决的是“工具怎么暴露、怎么发现、怎么调用”。但 agent 怎么规划、怎么重试、怎么审批、怎么记录,这些还是应用自己要设计。
我踩到的第一个误区就是:工具列表一多,agent 看起来就“能力很强”。但真实情况是,工具越多,出错路径也越多。尤其是工具之间有依赖的时候,模型一次错误调用可能会把后面的上下文都带偏。
失败要分类
MCP 官方规范里,tool 是 server 暴露给模型调用的能力。每个 tool 有 name、description、inputSchema,新版本里还可以有 outputSchema。client 可以通过 tools/list 发现工具,通过 tools/call 调用工具。
工具失败不能都当成一种失败。我比较喜欢分成四类:
如果不分这些类型,agent 很容易出现两种问题:要么无限重试,要么一失败就胡编。
一个比较朴素的工具调用兜底逻辑可以这样设计:
这里我会强制 agent 在最终回答里暴露两类信息:
这个写法有点啰嗦,但对排障类 agent 很有用。最怕的是模型把推测写成事实,用户再拿这个“事实”去改线上配置。
敏感工具不要让模型自己决定
这里面最容易被忽略的是“工具是否允许在当前场景使用”。MCP 规范也强调了 trust & safety:客户端应该让用户知道哪些工具暴露给了模型,敏感操作前应该有确认。
我自己的倾向是把 MCP 工具分级:
然后给 agent 一个硬规则:dangerous 和 external 默认必须人工确认,不能让模型自己判断“应该没事”。
我还会给工具加一个“是否可回滚”的维度。比如创建一条草稿 issue 是低风险,因为删掉就行;修改线上 ConfigMap 也许看起来只是一个字段,但一旦触发 rollout,影响就不是工具自己能兜住的。
trace 比想象中重要
如果用 OpenAI Agents SDK,它本身也提供了不少跟工程化有关的东西,比如 MCP servers、guardrails、handoffs、tracing。这里我觉得 tracing 很重要,因为 agent 一旦开始多步调用工具,事后没有 trace 基本没法排查。
一次完整 trace 至少要能回答几个问题:
另外,MCP transport 也要注意。stdio 适合本地工具,client 拉起子进程,通过 stdin/stdout 通信;Streamable HTTP 适合远程或服务化工具。HTTP 场景下要考虑认证、Origin 校验和服务绑定地址。
我现在更愿意把 MCP server 写得“笨”一点:输入严格校验,输出结构稳定,错误信息清楚,不在工具里偷偷做太多推理。推理留给 agent,工具只负责可靠地拿数据或执行动作。
比如一个查询 Kubernetes pod 的 MCP tool,返回最好是这种结构:
不要直接返回一大段混杂的自然语言。自然语言看起来顺,但后续不好校验,也不好让 agent 做稳定判断。
小结
接 MCP 以后我会重点看这些东西:
- 工具有没有清晰的 inputSchema 和 outputSchema。
- 工具失败有没有分类型。
- 是否设置超时、限流和审计日志。
- 敏感工具有没有人工确认。
- 工具结果进模型前有没有校验和脱敏。
- 有没有 trace 能复盘每一步。
- agent 最终回答有没有区分事实和推断。
MCP 的价值是把工具接入标准化,但它不会自动让 agent 变可靠。真正影响线上可用性的,往往是这些不太显眼的兜底逻辑。