详解将 OpenAPI spec 转换为 MCP 服务器的机械映射规则(operationId→工具名、schema→inputSchema 等),并指出通用转换器的常见失败场景。
每个评估 Model Context Protocol(MCP)服务器的团队最终都会遇到同一个问题:我们是手动维护一份 API 的第二描述供 Agent 使用,还是从已有的契约文件直接生成?如果你已经维护了一份 OpenAPI 文档,第二个选择才是合理的做法。OpenAPI 规范几乎天然就是一个工具目录——每个操作都有名称、输入、输出和传输方式。将其转换为 MCP 实际上是一个映射问题,而非建模问题。
本教程会讲解这个映射具体是什么、通用转换器在真实规范上会在哪些地方失败,以及如何用真实的 Agent 客户端验证结果。
映射关系是机械性的:
生成的 MCP 服务器是一个轻量适配层。当 Agent 调用 list_projects 工具时,服务器根据 schema 校验参数、执行 HTTP 请求并返回响应体。它不会凭空创造端点,也不会对任何内容做摘要——契约即行为。
最后这一点正是这种方法比让 LLM 从粘贴到 prompt 中的文档"学习 API"效果更好的原因。模型拿到的是带机器可读输入 schema 的结构化工具列表,且服务器会在请求离开进程前强制类型校验。
生成的代码。 代码生成器输出一份独立的 MCP 服务器,支持 TypeScript 或 Python。输出的代码归你所有,意味着你可以自定义中间件——但一旦规范发生变化,代码就会漂移,而且现在你多了一个需要部署的服务。
基于规范的本地代理。 运行时直接读取 OpenAPI 文档并暴露为工具,无需代码生成步骤。Powerduck 采用的正是这种模式:在桌面工作区打开规范文件,将其作为本地 MCP 端点提供服务;或者用 npx 对文档以无头模式启动。规范文件作为单一数据源,因此在下次启动时编辑 schema 就会改变工具。
托管端点。 将同一份规范发布到 URL,即可获得一个带访问令牌、版本控制和访问权限管理的 MCP 端点。这适合合作伙伴或内部团队——他们不应该看到你的源文件。
工具的可发现性取决于其名称。在提供任何服务之前,确保每个 operationId 都是唯一、稳定且以动词开头的:list_projects、get_project、create_project,而不是 getAll、fetchOne、projectsGet。Agent 通过名称和描述选择工具;projectsGet 什么有效信息都没传达。
如果你的规范中没有 operationId,从方法和路径生成它们(get /projects/{id} 变成 get_project),并在整个发布周期中保持该映射稳定。重命名一个工具对所有引用了它的已保存 Agent prompt 来说都是一个破坏性变更。
一个包含 300 个操作的内部 API 不应该变成 300 个工具。Agent 能良好处理的工具数量在几十个左右;超过这个数量,可发现性下降,错误的工具会被调用。方案如下:
通过标签暴露公共子集(x-mcp-expose: true)。
按受众拆分:一个服务器处理计费操作,另一个处理只读目录访问。
在托管端点上排除危险动词,仅保留在本地服务器上。
规范本身已经带了标签;大多数团队需要的只是一个过滤器,而不是重写。
MCP 服务器绝不将凭证嵌入规范中。以下两种模式可行:
{
"mcpServers": {
"powerduck-cloud": {
"command": "npx",
"args": ["@powerduck/openapi-to-mcp-server", "--spec", "./openapi.yaml"],
"env": {
"PD_API_TOKEN": "paste-locally-never-commit"
}
}
}
}
对于托管端点,客户端持有平台颁发的每个用户令牌;规范中的 securitySchemes 保持为格式声明(Bearer、请求头中的 API key、基本认证),而实际密钥在每次请求时注入。从第一天起就将读写作用域视为不同令牌——一个能浏览的 Agent 不应该能够删除。
这是朴素转换器止步的地方。真实 API 包含 SSE 流,而悄悄丢弃它们的转换会给 Agent 一个残缺的产品图景。在 Powerduck 的 OpenAPI 3.2 文档中,SSE 端点携带 text/event-stream 加上一个 x-protocol 扩展,描述事件名称和条目 schema。当该文档作为 MCP 提供服务时,流操作打开连接、收集 Agent 请求的事件,并将它们作为结构化结果返回,而不是永远挂起。
WebSocket 和 gRPC 操作在 workspace 中用同样方式记录;目前只有 HTTP 和 SSE 映射为普通 MCP 工具,因此将其他的做好标记,而不是假装它们不存在。
提供端点不是终点。在真实的 MCP 客户端中验证:
连接并确认工具数量与暴露的操作集匹配。
调用一个不带参数的 GET 工具,以及一个故意传错类型的——后者必须返回 schema 校验错误,而不是上游的 500。
调用一个 POST 工具并确认请求体以规范中声明的准确 content type 到达服务器。
检查需要令牌的操走在令牌缺失时是否干净地失败。
从头到尾完整执行一个 SSE 工具。
一个常见失败是双重编码:转换器将 body 包装成 JSON,生成的客户端又做了一次,导致上游收到一个字符串。步骤 3 中的 POST 测试能立即捕获这个问题。
每个环境的服务器。 规范中列出了 staging 和 production;转换器会选第一个,然后 Agent 悄无声息地打到 prod。在提供服务时明确指定服务器。
描述中的枚举。 一个包含十二个枚举值的状态字段,如果这些值只存在于叙述文本中,就会被用凭空捏造的值调用。枚举应该放在 schema 中。
自由形式对象。 additionalProperties: true 的操作会变成无用的工具——Agent 不知道该发送什么。在提供服务前收紧这些定义。
分页假设。 如果每个列表都使用游标分页,在操作描述中说明一次;否则 Agent 会去问用户。
你不需要一个 MCP initiative 就能获得价值。拿一份规范——你的团队最常粘贴到聊天中的那份——在本地提供服务,然后让一个 Agent 指向它。之后的工作流程很简单:规范驱动文档、mock、测试和 MCP 端点都来自同一份文件,因此没有什么额外的东西需要维护。
当你希望合作伙伴接入时,带访问控制和版本控制的托管版本就已存在;浏览器内 demo 打开一份示例文档,无需安装任何东西即可展示服务动作。
延伸阅读:Your API already describes the tools your agent needs 深入讲解了工具发现,publishing API docs and an MCP endpoint from one spec 涵盖了带自定义域名的托管路径。