Featured image of post MCP 工具接入后的 Agent 设计:别只会调工具,还要能兜底

MCP 工具接入后的 Agent 设计:别只会调工具,还要能兜底

MCP 解决的是工具暴露、发现和调用的问题,但 Agent 真正可用还要处理失败分类、权限确认、结果校验和 tracing。

最近接 MCP 工具时有个感受:让 agent 能调工具其实不难,难的是工具出错以后它怎么收场。

很多 demo 都停在“模型发现工具,然后调用工具,最后返回结果”。这个流程当然重要,但一到真实项目里,问题马上会变多:

  • 工具参数不完整。
  • MCP server 连不上。
  • 工具返回了业务错误。
  • 工具返回内容太长。
  • 工具结果里混了不该给模型看的内容。
  • 用户问的是高风险操作,但 agent 没有停下来确认。

所以我现在更愿意把 MCP 看成一个工具协议,而不是完整的 agent 方案。MCP 解决的是“工具怎么暴露、怎么发现、怎么调用”。但 agent 怎么规划、怎么重试、怎么审批、怎么记录,这些还是应用自己要设计。

我踩到的第一个误区就是:工具列表一多,agent 看起来就“能力很强”。但真实情况是,工具越多,出错路径也越多。尤其是工具之间有依赖的时候,模型一次错误调用可能会把后面的上下文都带偏。

失败要分类

MCP 官方规范里,tool 是 server 暴露给模型调用的能力。每个 tool 有 namedescriptioninputSchema,新版本里还可以有 outputSchema。client 可以通过 tools/list 发现工具,通过 tools/call 调用工具。

工具失败不能都当成一种失败。我比较喜欢分成四类:

1
2
3
4
1. 参数错误:模型传错了,应该让模型修正参数后重试
2. 临时错误:网络、限流、超时,可以做有限重试
3. 权限错误:不要重试,直接提示需要授权或人工处理
4. 业务错误:工具正常执行了,但结果不满足条件,需要解释给用户

如果不分这些类型,agent 很容易出现两种问题:要么无限重试,要么一失败就胡编。

一个比较朴素的工具调用兜底逻辑可以这样设计:

1
2
3
4
5
6
7
8
用户请求
  -> 判断是否需要工具
  -> 检查工具是否允许在当前场景使用
  -> 调用工具
  -> 校验结果结构
  -> 如果失败,按失败类型处理
  -> 如果是敏感操作,等待人工确认
  -> 汇总结果,明确哪些是工具返回,哪些是推断

这里我会强制 agent 在最终回答里暴露两类信息:

1
2
已确认:来自工具返回、日志、数据库或 API 的事实
推测:根据事实做出的判断,需要继续验证

这个写法有点啰嗦,但对排障类 agent 很有用。最怕的是模型把推测写成事实,用户再拿这个“事实”去改线上配置。

敏感工具不要让模型自己决定

这里面最容易被忽略的是“工具是否允许在当前场景使用”。MCP 规范也强调了 trust & safety:客户端应该让用户知道哪些工具暴露给了模型,敏感操作前应该有确认。

我自己的倾向是把 MCP 工具分级:

1
2
3
4
read_only    只读查询,比如查文档、查资源、查状态
low_risk     可写但影响小,比如创建草稿、生成 issue
dangerous    可能改配置、删数据、执行命令
external     会访问外部系统或带来数据外发风险

然后给 agent 一个硬规则:dangerousexternal 默认必须人工确认,不能让模型自己判断“应该没事”。

我还会给工具加一个“是否可回滚”的维度。比如创建一条草稿 issue 是低风险,因为删掉就行;修改线上 ConfigMap 也许看起来只是一个字段,但一旦触发 rollout,影响就不是工具自己能兜住的。

trace 比想象中重要

如果用 OpenAI Agents SDK,它本身也提供了不少跟工程化有关的东西,比如 MCP servers、guardrails、handoffs、tracing。这里我觉得 tracing 很重要,因为 agent 一旦开始多步调用工具,事后没有 trace 基本没法排查。

一次完整 trace 至少要能回答几个问题:

1
2
3
4
5
6
用户原始输入是什么?
模型决定调用哪个工具?
传给工具的参数是什么?
工具原始返回是什么?
中间有没有 guardrail 拦截?
最终答案里哪些来自工具,哪些是模型总结?

另外,MCP transport 也要注意。stdio 适合本地工具,client 拉起子进程,通过 stdin/stdout 通信;Streamable HTTP 适合远程或服务化工具。HTTP 场景下要考虑认证、Origin 校验和服务绑定地址。

我现在更愿意把 MCP server 写得“笨”一点:输入严格校验,输出结构稳定,错误信息清楚,不在工具里偷偷做太多推理。推理留给 agent,工具只负责可靠地拿数据或执行动作。

比如一个查询 Kubernetes pod 的 MCP tool,返回最好是这种结构:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
  "namespace": "default",
  "pods": [
    {
      "name": "api-7f8d7c9b8d-abcde",
      "phase": "Running",
      "restartCount": 3,
      "node": "worker-1"
    }
  ],
  "warnings": [
    "restartCount > 0, check container logs"
  ]
}

不要直接返回一大段混杂的自然语言。自然语言看起来顺,但后续不好校验,也不好让 agent 做稳定判断。

小结

接 MCP 以后我会重点看这些东西:

  • 工具有没有清晰的 inputSchema 和 outputSchema。
  • 工具失败有没有分类型。
  • 是否设置超时、限流和审计日志。
  • 敏感工具有没有人工确认。
  • 工具结果进模型前有没有校验和脱敏。
  • 有没有 trace 能复盘每一步。
  • agent 最终回答有没有区分事实和推断。

MCP 的价值是把工具接入标准化,但它不会自动让 agent 变可靠。真正影响线上可用性的,往往是这些不太显眼的兜底逻辑。

参考资料

本文采用 CC BY-NC-SA 4.0 协议,转载请注明出处。
记录工程实践和排障过程。
使用 Hugo 构建
主题 StackJimmy 设计