用途
mcp-server-design 用于设计 MCP(Model Context Protocol)服务器向 AI 智能体暴露的接口面——即存在哪些工具、资源与提示词,它们叫什么名字,schema 和描述怎么写。资料强调这是杠杆最高的阶段:大多数糟糕的 MCP 服务器是在这里失败的,而不是在代码里。它要修复的失败模式是:一比一包装 API、工具过多、描述不足的服务器,会拖垮智能体的工具选择准确率并消耗上下文。
资料中的事实针对 MCP 规范修订版 2025-11-25(截至 2026-07-20 为当前版本)。
何时不要使用
- 用 SDK 编写服务器代码 → 交给 mcp-server-implementation。
- 添加 OAuth/令牌 → mcp-server-auth。威胁建模 → mcp-server-security。
- 测试/评估 → mcp-server-testing。发布/注册表 → mcp-server-deployment。
- 为人类开发者设计普通 REST/GraphQL API——消费者不同,规则不同(在那里冗长的 CRUD 没问题;在这里则是反模式)。
- 选择安装哪些现成 MCP 服务器(这是消费者问题,不是设计问题)。
工作流
1. 决定是否应该存在一个服务器
当 2 个以上 AI 客户端需要自主访问该系统,或某个工作流跨会话被复用时(“第三次复用时它就值回票价”),才构建 MCP 服务器。一个你完全掌控的定制智能体加一次性任务 → 直接做工具/函数集成更简单。单个静态值 → 放到现有服务器上的一个资源,而不是新建服务器。先检查 https://github.com/modelcontextprotocol/servers 和 MCP Registry——不要重建已有的东西。
2. 从工作流而非端点推导工具
自上而下地从智能体必须完成的用户工作流出发;绝不要自下而上地从 API 表面出发。每个工具应完成一个结果,在服务器端做多步编排:
- get_user + list_orders + get_status → 一个 orders_track_latest(email)
- list_users + list_events + create_event → calendar_schedule_event(...)
- read_logs(转储)→ logs_search(query) 返回匹配行加上下文
一比一自动包装 REST/OpenAPI 是记录最多的 MCP 反模式:它会成倍增加往返次数、token 和幻觉参数。如果一个工作流需要约 5 次链式工具调用,就把它合并成一个工具。
3. 强制工具预算
工具定义每轮都会被注入模型上下文(约 350 token/工具),且随着目录增长选择准确率会下降——生产团队收敛到每服务器 5–15 个工具(这是启发式,不是规范法律;较弱的模型退化更早)。真实修复案例从 40 个工具降到 13 个(GitHub Copilot),从 3 次重建到 2 个工具(Block 的 Linear 服务器)。需要更多?按领域、权限级别(读 vs 写)或性能特征拆分——绝不按 API 资源拆分。运行 scripts/lint_tool_surface.py(见下)来把关数量和命名。
4. 把每项能力分配到正确的原语
控制模型测试——谁决定它何时发生?
| 原语 | 由谁控制 | 用于 | 备注 |
|---|---|---|---|
| Tool | 模型 | 动作、副作用、动态查询 | 默认选择;输入需校验 |
| Resource | 应用 | URI 上的只读参考数据(db://schema) | 各客户端支持最不均衡的原语——依赖前先确认目标客户端 |
| Prompt | 用户 | 刻意的多步工作流模板(“SOP”) | 以斜杠命令形式呈现 |
当 prompt 才是正确归属时,不要把工作流指令塞进工具描述;也不要把纯参考数据建模成无参数工具而不先考虑资源。
5. 按当前规范指引命名工具
采用 snake_case 的 {service}{action}{resource}:slack_send_message、tickets_search、sentry_get_error_details。规范(SEP-986,2025-11-25):1–128 字符,区分大小写,仅允许 A-Za-z0-9_-.,每服务器内唯一。服务前缀可避免多个服务器同时活跃时的冲突。动作动词在前;不要空格/方括号(某些客户端根本不会显示该工具)。
6. 把描述当提示词写,把 schema 当契约
描述是模型用来路由的依据——要像给新员工写的入职文档,而不是 API 文档。要说明:它做什么(一句具体的话)、何时用/何时不用、每个参数的确切格式、返回结构。“Gets commits”是真实的生产描述,而且毫无用处。
Schema 是经过校验的契约,不是提示:扁平、有类型、顶层参数,受限值用枚举,带默认值,名称无歧义(user_id,绝不用 user 或需要模型猜的嵌套 filters: dict)。约束属于 schema(协议会校验它们),不属于散文。跨工具保持术语一致(处处 user_id——绝不这里 user_id 那里 accountId)。
7. 为上下文窗口设计响应
- 每个列表都分页:limit(默认 20–50,有上限),传入 cursor/offset,返回 has_more + total_count。绝不转储无界记录。
- response_format 参数(默认 "concise" / "detailed"):在 Anthropic 的 Slack 示例中,concise 响应削减约 2/3 token。
- 优先使用人类可读标识符(名称与 ID 并列)——把不透明 UUID 解析为名称可测量地减少后续参数的幻觉。
- 限制响应大小(约 25k token 是常见的客户端上限),并告诉智能体如何获取其余部分(offset/filter),不要静默截断。
8. 把错误写成恢复指令
工具错误是给模型读的,不是给用户读的。每条失败消息都应说明出了什么问题以及下一步做什么:
- "Project 'proj_abc123' not found. Call projects_list to see valid IDs."
- "Rate limited. Retry after 30 seconds."
而不是 Error 500 / 原始堆栈跟踪。(线级机制——isError 与协议错误——属于 mcp-server-implementation。)
9. 声明诚实的注解并限制破坏性工具
注解行为:readOnlyHint、destructiveHint、idempotentHint、openWorldHint(加 title)。客户端据此驱动确认 UX——撒谎的注解是安全漏洞。让读容易、写昂贵:破坏性工具要有明确的确认流程。(完整威胁模型:mcp-server-security。)
10. 对接口面做 lint
python3 "${CLAUDE_SKILL_DIR}/scripts/lint_tool_surface.py" tools.json
检查名称(规范正则 + snake_case + 共享前缀)、工具数量、描述质量下限、schema 扁平度/枚举。非零退出 = 在实现前修复。
输出规范
一份设计文档(或直接编辑),包含:带理由的构建/不构建决定;工具表(名称、一句话结果、注解);原语分配及理由;每个工具的描述 + 扁平有类型 schema + 列表工具的分页/response_format;错误消息示例;供评估的开放问题。然后交给 mcp-server-implementation 去编码。
注意事项
- 工具预算数字是启发式,不是规范——方向(更少、更高信号的工具)得到强力佐证;确切阈值因模型而异。
- “GET→资源,POST→工具”错得足够频繁,会误导——改问“模型是在获取上下文,还是在做工作?”
- 资源/提示词的客户端支持不均衡;依赖它们的设计必须点名目标客户端,或回退到工具。
- 不要围绕单一模型设计:只有前沿模型才能驾驭的 schema 会在连接的最弱客户端上失败。
- 2026-07-28 的规范修订(撰写时为候选发布版)重塑了传输/生命周期,但不改变这些接口面设计规则;如果已过去数月,请在 https://modelcontextprotocol.io/specification 核实命名指引。
参考
- references/tool-design.md——实例(坏→好的描述和 schema)、注解表、分页/响应契约、带来源的 token 成本数字、原语决策树、高级模式(HATEOAS 导航器、渐进式工具发现)。
- scripts/lint_tool_surface.py——确定性的接口面把关(仅标准库)。