自己写一个 MCP Server:工具设计比协议难十倍

📅 2026/4/26 ✍️ 小文 📖 约 1 分钟

MCP 的 SDK 已封装好握手和传输,真正的难点是工具粒度、参数设计、错误信息怎么写。本文按开发顺序走一遍,附三个上线后才暴露的设计错误。

MCP 已是 Agent 接外部能力的默认协议。真正写起来,难点从来不是协议本身——SDK 把 transport、握手、能力协商都封装好了——而是工具设计。

一、先想清楚给谁用

  • 通用 Agent(Claude Desktop、Cursor):工具名和描述是模型唯一的信息来源,必须自解释,宁可啰嗦。
  • 内部业务 Agent:可以依赖系统提示词补上下文,工具能更精简。

这决定工具粒度和描述详细程度。

二、粒度:别把 REST API 一对一翻译

新手最常犯的错是把现有 20 个接口直接变成 20 个工具。模型每次在 20 个里挑,选错率极高。

实际经验是三层:

  1. 资源级:get_user、list_orders,目的单一,可以保留;
  2. 场景级:find_order_by_customer_email,把常见两步合成一步,减少往返;
  3. 禁止:execute_api(path, method, body) 这种万能工具,等于把 API 文档整个丢给模型。

判断标准很简单:如果参数里有一个是「选路」用的(method、endpoint),说明粒度错了。

三、参数设计的四条规则

  • 能用枚举就别用自由字符串,status="all" 比 status="any" 稳定;
  • 每个参数都要有 description,模型看不到你的内部文档;
  • 给默认值,模型经常漏参数;
  • 返回结构化数据,不要返回自然语言让模型自己整理。

四、错误信息是给模型看的

这是上线后最常见的返工点。原始异常栈对模型毫无意义,它只会回用户「出错了」。

错误要写成可行动的句子:

BAD:  ValidationError: 422
GOOD: 参数 status 必须是 open/closed/all,收到 "pending",
       请确认用户要的是 open 还是 closed。

同时区分两类:可重试(参数错、找不到)告诉它怎么改,它会自己重试;不可重试(权限不足、服务宕机)明确说不要重试,直接告知用户。

五、三个上线后暴露的坑

工具数量膨胀。 加到第 30 个工具后模型开始乱选。解法是按领域拆成多个 MCP Server 按需加载,别全塞一个。

写操作缺幂等。 create_invoice 被重复调用,客户收到两张发票。所有写操作都要支持幂等键或去重窗口。

返回体过大。 一次 list_orders 返回 500 条 JSON 直接撑爆上下文。服务端要截断并明确告知「仅返回前 50 条」。

六、测试怎么测

别只测「工具能跑通」。准备 20~30 个典型用户请求,看模型选中的工具和参数是否合理。这个用例集比单元测试更有价值,因为它测的是接口对模型是否友好,而这才决定 MCP 的质量。

写 MCP Server 的本质不是写 API,是写一份给模型看的、可执行的 API 文档。

📤 分享到