SuperAPI AI 工具集

mcp-best-practices

编程与开发

查看项目

更新于 10-16 02:13

技能介绍

MCP Best Practices 是一份用于构建生产级 MCP 服务器的决策参考,基于 TypeScript SDK。它不是教程,而是假定你已经有一个可工作的服务器,需要让它变得正确、快速且安全。

两代规范与版本

关键背景是 2026-07-28 规范发布后形成两代协议:2024-10-07 至 2025-11-25 的版本都以 initialize 握手开始,SDK 称之为 legacy;2026-07-28 进入现代纪元,没有 initialize,改用 server/discover 广告,每个请求带 _meta 信封。升级 TS SDK 不会自动把客户端移到新规范,但其他 SDK 和客户端未必共享这个默认值,例如 rmcp(Rust)>= 3.0 开箱即用 2026-07-28,因此每次升级 SDK 后都应重新确认自己广告的版本。

版本协商通过 versionNegotiation.mode 控制:缺省或 'legacy' 使用 2025 initialize 握手;'auto' 先探测 server/discover,对 2025-only 服务器回退 initialize;{ pin: '2026-07-28' } 则只接受该版本、不回退。服务器应同时服务两代。createMcpHandler 默认同时服务 2025 与现代请求。

服务器搭建与传输

传输决策区分远程与本地:远程 v2(K8s、CF Workers、Node)用 createMcpHandler(factory),每请求无状态;远程 v1 / 无状态用 WebStandardStreamableHTTPServerTransport,sessionIdGenerator: undefined、enableJsonResponse: true;远程有状态(仅 2025 纪元)用 sessionIdGenerator: () => randomUUID();本地 CLI / Claude Desktop 用 StdioServerTransport 或 v2 serveStdio(factory);SSE 客户端需迁移到 Streamable HTTP,因为 v2 已移除 SSE。

无状态模式推荐远程部署。每请求新建服务器是规范做法,server@2.3.0 起强制如此:用同一个 server 对象或同一个无状态传输处理每个 HTTP 请求会在第二次请求失败。在 v2 中 createMcpHandler 接收工厂函数并对每个请求调用一次。McpServer 必须每请求新建,但常量输入(Zod 模式、注解对象、工具描述字符串、支付配置、上游 API 客户端)应提升到模块级别。如果只路由 POST,应对 GET /mcp 显式返回 405,规范要求如此,官方 TS 客户端把 405 视为无流的良性信号,而空 200 会引发重连风暴。

工具设计与模式

v1 使用 server.tool(name, description, zodShape, annotations, handler),位置重载存在歧义;v2 已移除,改用 registerTool() 配置对象,支持 inputSchema、outputSchema、annotations。命名上规范 2025-11-25 建议 1-128 字符、区分大小写、仅 A-Za-z0-9_-.;避免通用名(如 search)造成跨服务器冲突;多服务器活跃时应加服务前缀,前缀要烘焙进工具名本身,因为 serverInfo 的服务器名不保证唯一。

模式规则:每个字段都要 .describe(),这是 LLM 生成参数所依赖的。z.union()、原始 JSON Schema、z.transform() 三种构造会静默失败,客户端 AJV 严格验证也一样。分页是多数服务器首先遇到的基元,tools/list 或 resources/list 超过 50 项应分页,协议 cursor 不透明,不可解析或合成,循环直到 nextCursor 缺失。

注解与安全

注解都是可选提示:readOnlyHint 默认 false、destructiveHint 默认 true(仅当 readOnly=false)、idempotentHint 默认 false、openWorldHint 默认 true。客户端会用它们做同意提示和自动批准决策。所谓的“致命三要素”是:私有数据访问 + 接触不可信内容 + 外部通信三者同处一个智能体,会制造数据窃取条件。应设计工具集,使任何单一智能体都不同时具备这三者。

有状态工具

2026-07-28 没有协议级会话,跨调用状态使用服务器签发的句柄作为普通工具参数传递:创建工具返回 { basket_id: "bsk_a1b2c3" },后续工具把 basket_id 作为参数接收,模型负责携带。句柄是名称而非能力,每次调用都要用它校验调用方,保持不透明并有足够熵,并在创建工具描述中说明保留策略。过期或未知句柄返回工具执行错误,让模型能通过创建新状态恢复。

工具结果投递

一个常见陷阱:当工具同时返回文本 content 块和 structuredContent 时,多个主流客户端(Claude Code、Codex CLI、VS Code Copilot、Goose)会静默丢弃文本块,只把 structuredContent 转发给模型。如果两者内容不同,人类可读的部分会消失。这是客户端行为,规范并未约束。

测试显示(Claude Code 2.1.165,MCP 2025-11-25):只有一个文本块时文本原样传递;content: [] 加 structuredContent 时会把 JSON 字符串化放进 content 槽位;文本块加 structuredContent 时文本块被丢弃、structuredContent 胜出;加 outputSchema 没有区别;两个文本块都被保留。即 structuredContent 在 Claude Code 上不是独立的类型化通道,它被字符串化进标准 tool_result 槽位,token 成本与等价 JSON 文本相同。规范 2025-11-25 没有优先级规则,只有一条向后兼容 SHOULD:返回 structured content 的工具也应把序列化 JSON 放进 TextContent 块。

跨客户端行为不同:Claude Code CLI、OpenAI Codex CLI、VS Code Copilot、Goose 只把 structuredContent 送达模型;Cursor、Claude.ai web、ChatGPT MCP connector 偏好 content 或两者都呈现;Google ADK 默认两者都转发。对服务器作者的建议:不要返回内容不一致的 content 和 structuredContent;若发出 structuredContent,就把相同字节镜像进文本块;优先每个工具或每种模式只用一个通道。

错误处理

两种机制对 LLM 可见性不同:工具错误(CallToolResult 中 isError: true)LLM 可见,用于输入验证、API 失败、业务逻辑错误;协议错误(JSON-RPC 错误响应)客户端 MAY 暴露。根据 SEP-1303,输入验证错误必须是工具执行错误而非协议错误,LLM 需要看到“日期必须在未来”才能自我纠正。在工具处理器内抛出异常时,SDK 会把所有异常(包括抛出的 ProtocolError/McpError)转成 isError: true 结果(UrlElicitationRequiredError 例外),消息保留但错误码和 error.data 被丢弃;应自行返回 isError 结果。

适用场景与限制

该技能适用于编写或审查 MCP 服务器,涵盖传输、工具模式、错误、OAuth 等主题,面向已有可运行服务器、需要确保正确性、性能与安全的生产用户。文档提及的工具链包括 SDK 文档(v2)、MCP Inspector(默认以 legacy 连接,可加 --protocol-era modern)、一致性测试套件,以及用于脚手架搭建的 mcp-server-dev 插件。文末对 x402/MPP 生态标准的说明被截断,该部分内容暂未完整提供。