|
| 1 | +--- |
| 2 | +title: "语义至关重要:如何将 OpenAPI 规范转化为 MCP 工具" |
| 3 | +summary: "本文探讨了如何在 OpenAPI 规范中嵌入语义描述,并将其转换为 AI Agent 可消费的 MCP 工具。文章强调 OpenAPI 应作为 API 描述的单一事实来源,介绍了工具命名、能力描述、JSON-LD 使用、安全性注意事项及如何完成转换的建议。" |
| 4 | +authors: ["Christian Posta"] |
| 5 | +translators: ["云原生社区"] |
| 6 | +categories: ["AI"] |
| 7 | +tags: ["OpenAPI", "MCP", "AI工具", "JSON-LD", "语义建模"] |
| 8 | +draft: false |
| 9 | +date: 2025-04-24T18:09:59+08:00 |
| 10 | +links: |
| 11 | + - icon: language |
| 12 | + icon_pack: fa |
| 13 | + name: 阅读英文版原文 |
| 14 | + url: https://blog.christianposta.com/semantics-matter-exposing-openapi-as-mcp-tools/ |
| 15 | +--- |
| 16 | + |
| 17 | +最近我在 [The API Experience Podcast](https://podcasts.apple.com/us/podcast/the-api-experience-podcast/id1698168565) 上和 [Matt McLarty](https://www.linkedin.com/in/mattmclartybc/) 以及 [Mike Amundsen](https://www.linkedin.com/in/mamund/) 聊了聊我最近写的一篇博客,主题是 [如何用“能力”来描述 API](https://blog.christianposta.com/from-apis-to-capabilities-what-ai-agents-mean-for-application-architecture/)。其中一个被提到的观点是,是否可以在 OpenAPI 规范中直接嵌入语义信息。我当时的一个评论是:“理想情况下,你应该可以从 OpenAPI 规范直接生成一个 MCP(Model-Computable Proxy)服务端,从而向 Agent 或 AI 模型暴露你的能力。”这个观点与 [Kevin Swiber](https://www.linkedin.com/in/kevinswiber/) 的一个深刻观察不谋而合,即 [MCP 将成为终极的 API 使用者](https://www.layered.dev/mcp-the-ultimate-api-consumer-not-the-api-killer),而不是所谓的 API 杀手。我想借此机会展开讲讲,因为这个思路非常值得深入探讨。 |
| 18 | + |
| 19 | +我首先想到的是,确实,如今大家普遍用 OpenAPI 规范文档来描述自己的 API。但正如 Mike Amundsen 指出的那样,这种描述在“语义信息”以及“以能力为中心的 API 表达”方面仍存在明显不足。当人类开发者直接使用 API 时,他们可以自行填补“语义上下文”的空白,选择合适的 API 并构建逻辑。但 AI agent 或工具并不具备这种人类主观判断力,LLM 需要尽可能多的上下文信息,才能做出正确的工具选择决策。 |
| 20 | + |
| 21 | +然而,我们并不希望由此产生“语义描述分裂”的问题:一套面向 MCP 的 API 语义描述,另一套则用于给开发者直接阅读和使用。我们更希望有一个统一的“单一真相来源(single source of truth)”,那就是 [OpenAPI 规范](https://swagger.io/specification/)。但要做到这一点,就需要我们在 OpenAPI 描述中投入更多精力,将 API 的“语义意义”以及“能力描述”表达得更加清晰、丰富。 |
| 22 | + |
| 23 | +## 从 API 到能力 |
| 24 | + |
| 25 | +在过去十几年中,许多企业投入大量精力,将内部和外部的业务能力通过 API 对外暴露。这一趋势不会消失。虽然 MCP(Model-Computable Protocol)非常令人兴奋,但归根结底,它只是一个**供 AI 模型调用工具的协议适配层**。但如果我们希望正确地向模型暴露工具,就必须[以“能力”而非单纯的 API 合约结构来描述](https://blog.christianposta.com/from-apis-to-capabilities-what-ai-agents-mean-for-application-architecture/): |
| 26 | + |
| 27 | +- 工具名称应具有唯一性,且具备动词导向的动作意义(例如使用 “listAllTodoTasks” 而非简单的 “list”); |
| 28 | +- 提供详细的**用途说明**; |
| 29 | +- 展示在什么场景下可以调用,并提供请求/响应示例; |
| 30 | +- 明确使用工具的**前置条件**。 |
| 31 | + |
| 32 | +## 使用 OpenAPI 规范 |
| 33 | + |
| 34 | +OpenAPI 规范(OpenAPI Specification)本身包含了许多字段与结构,支持我们为 API 添加丰富的语义含义: |
| 35 | + |
| 36 | +- 利用 `info` 部分添加全局信息; |
| 37 | +- 多个部分支持链接至 `externalDocs`,提供外部文档支持; |
| 38 | +- 大多数部分都支持 `title`、`summary` 和 `description` 字段; |
| 39 | +- 借助 [JSON-LD](https://json-ld.org/) 可将字段链接到行业通用或企业专属的数据语义模型,提供更深层的语义描述; |
| 40 | +- 如果上述方式都不满足需求,还可以使用自定义的扩展属性 `x-*` 扩展规范。 |
| 41 | + |
| 42 | +下面是一个具体示例: |
| 43 | + |
| 44 | +在 `info` 字段中添加丰富语义的 Todo API 描述: |
| 45 | + |
| 46 | +```yaml |
| 47 | +openapi: 3.0.3 |
| 48 | +info: |
| 49 | + title: Enhanced Todo API |
| 50 | + description: > |
| 51 | + 本 API 提供了管理个人或团队待办事项(todos)的能力,包括创建、更新、组织和检索任务,任务附带丰富的元数据,如截止日期、优先级和标签。 |
| 52 | + 该 API 专为系统和 AI Agent 设计,支持动态任务协调、进度追踪和工作流规划。 |
| 53 | + 特别适用于目标跟踪、工作助手或生产力工具等需要任务编排和上下文决策的场景。 |
| 54 | + version: 1.0.0 |
| 55 | + termsOfService: https://example.com/terms/ |
| 56 | + contact: |
| 57 | + name: API Support Team |
| 58 | + url: https://example.com/support |
| 59 | + |
| 60 | + license: |
| 61 | + name: Apache 2.0 |
| 62 | + url: https://www.apache.org/licenses/LICENSE-2.0.html |
| 63 | +``` |
| 64 | +
|
| 65 | +我们还应该链接该 API 的使用文档: |
| 66 | +
|
| 67 | +```yaml |
| 68 | +externalDocs: |
| 69 | + description: 查看该 API 所有使用场景的详细文档 |
| 70 | + url: https://example.com/docs |
| 71 | +``` |
| 72 | +
|
| 73 | +在具体 API 路径中,加入更详细的语义描述: |
| 74 | +
|
| 75 | +```yaml |
| 76 | + /todos: |
| 77 | + get: |
| 78 | + summary: 获取现有待办事项,用于上下文任务感知与规划 |
| 79 | + description: > |
| 80 | + 客户端或 AI Agent 可调用该接口,检索现有的待办事项列表,支持按完成状态过滤、分页控制。 |
| 81 | + 该能力可帮助理解当前任务状态、识别待办任务、规划下一步行动。 |
| 82 | + 特别适用于依赖实时上下文的工作流,如生产力跟踪、个人助手、自动化规划系统。 |
| 83 | + operationId: listAllTodoTasks |
| 84 | + tags: |
| 85 | + - todos |
| 86 | + parameters: |
| 87 | + - name: limit |
| 88 | + in: query |
| 89 | + description: 返回的最大条目数 |
| 90 | + schema: |
| 91 | + type: integer |
| 92 | + format: int32 |
| 93 | + minimum: 1 |
| 94 | + maximum: 100 |
| 95 | + default: 20 |
| 96 | + - name: completed |
| 97 | + in: query |
| 98 | + description: 按任务完成状态进行筛选 |
| 99 | + schema: |
| 100 | + type: boolean |
| 101 | + responses: |
| 102 | + '200': |
| 103 | + description: JSON 格式的待办事项数组 |
| 104 | + content: |
| 105 | + application/json: |
| 106 | + schema: |
| 107 | + type: object |
| 108 | + properties: |
| 109 | + data: |
| 110 | + type: array |
| 111 | + items: |
| 112 | + $ref: '#/components/schemas/Todo' |
| 113 | + pagination: |
| 114 | + $ref: '#/components/schemas/Pagination' |
| 115 | +``` |
| 116 | +
|
| 117 | +使用 [JSON-LD(JSON 语义链接数据)](https://json-ld.org/),我们可以为请求或响应中的数据模型赋予确切的语义含义。通过链接到行业标准(如 [schema.org](https://schema.org)、[w3.org](https://www.w3.org/))或企业自定义语义本体,可以让 API 的数据结构具备强语义。以下是一个添加 JSON-LD 语义结构的示例: |
| 118 | +
|
| 119 | +```yaml |
| 120 | +openapi: 3.0.3 |
| 121 | +info: |
| 122 | + # 为简洁省略部分内容 |
| 123 | + x-linkedData: |
| 124 | + "@context": |
| 125 | + schema: "https://schema.org/" |
| 126 | + hydra: "http://www.w3.org/ns/hydra/core#" |
| 127 | + vocab: "https://api.example.com/vocab#" |
| 128 | + "@type": "schema:WebAPI" |
| 129 | + "@id": "https://api.example.com/v1" |
| 130 | + "schema:name": "Enhanced Todo API" |
| 131 | + "schema:description": "一个提供待办管理、元数据和语义注释的综合 API" |
| 132 | + "schema:provider": |
| 133 | + "@type": "schema:Organization" |
| 134 | + "schema:name": "Example Organization" |
| 135 | + "schema:url": "https://example.com" |
| 136 | + "schema:dateModified": "2025-04-15" |
| 137 | +``` |
| 138 | +
|
| 139 | +如果上述方法都无法满足你的需求,你还可以选择[扩展 OpenAPI 规范](https://swagger.io/docs/specification/v3_0/openapi-extensions/),添加你自己的自定义属性。例如,当你对路径的 `description` 或 `summary` 字段存在向后兼容性问题时,可以添加自定义字段以增强描述能力: |
| 140 | + |
| 141 | +```yaml |
| 142 | + /todos: |
| 143 | + get: |
| 144 | + summary: 获取现有待办事项,用于上下文任务感知与规划 |
| 145 | + description: 简要描述在此 |
| 146 | + tags: |
| 147 | + - todos |
| 148 | + x-company-mcp: |
| 149 | + name: very-descriptive-name-here |
| 150 | + description: 更详细的描述信息 |
| 151 | +``` |
| 152 | + |
| 153 | +## 转换为 MCP 工具 |
| 154 | + |
| 155 | +接下来我们要思考:如何将 OpenAPI 规范转换为 MCP 工具?我们可以用 `operationId` 作为工具的名称,但描述信息该如何处理?MCP 工具需要以“能力”为中心进行描述,并提供足够的上下文,让 AI 模型能够判断该使用哪个工具、在什么时机使用。 |
| 156 | + |
| 157 | +在将 OpenAPI 映射为 MCP 工具时,可以直接使用 `operation` 的描述信息和参数说明,也可以在映射过程中对这些内容进行增强。你甚至可以借助 JSON-LD 的 URI 来补全数据结构的语义信息。以下是一个典型的 MCP 工具响应示例: |
| 158 | + |
| 159 | +```json |
| 160 | +{ |
| 161 | + "jsonrpc": "2.0", |
| 162 | + "id": 123, |
| 163 | + "result": { |
| 164 | + "tools": [ |
| 165 | + { |
| 166 | + "name": "listAllTodoTasks", |
| 167 | + "description": "允许客户端或 AI Agent 检索现有的待办事项列表,支持按完成状态过滤并支持分页参数限制。 |
| 168 | + 该能力有助于理解当前任务状态、识别未完成事项以及规划后续行动。适用于生产力追踪、个人助手或依赖实时上下文的自动化规划系统。", |
| 169 | + "inputSchema": { |
| 170 | + "type": "object", |
| 171 | + "properties": { |
| 172 | + "limit": { |
| 173 | + "type": "integer", |
| 174 | + "description": "返回的最大项数(1-100)", |
| 175 | + "minimum": 1, |
| 176 | + "maximum": 100, |
| 177 | + "default": 20 |
| 178 | + }, |
| 179 | + "completed": { |
| 180 | + "type": "boolean", |
| 181 | + "description": "按完成状态筛选" |
| 182 | + } |
| 183 | + } |
| 184 | + }, |
| 185 | + "annotations": { |
| 186 | + "title": "增强版 Todo API", |
| 187 | + "readOnlyHint": true, |
| 188 | + "openWorldHint": false |
| 189 | + } |
| 190 | + } |
| 191 | + ] |
| 192 | + } |
| 193 | +} |
| 194 | +``` |
| 195 | + |
| 196 | +将 OpenAPI 规范作为 API 的“**单一事实来源**”(Single Source of Truth)——包括其衍生物如 MCP shim——至关重要。如果忽视 OpenAPI 的质量和一致性,会在后续流程中造成严重问题,尤其是在与 AI Agent、LLM 或基于 MCP 的工具集成时。描述不清或结构不一致的规范可能导致服务间不匹配、MCP 工具生成困难、版本或后端兼容性问题,甚至会破坏 AI Agent 的工作流。 |
| 197 | + |
| 198 | +而对于 AI 模型而言,如果工具描述模糊或不完整,可能会导致错误的理解:选错工具、使用无效参数,或者误解意图(如应当查询却去创建数据)。这些问题不仅会降低 Agent 的行为质量,还可能导致“幻觉式”响应、偏离目标的操作,甚至彻底失败。 |
| 199 | + |
| 200 | +我们可以总结如下对照表: |
| 201 | + |
| 202 | +| MCP 工具字段 | OpenAPI 对应字段 | 说明 | |
| 203 | +|--------------------|-------------------------------------------|----------------------------------------------------------| |
| 204 | +| 工具名称 | `operationId` | 唯一、便于机器识别;如无,可退回至 HTTP 方法+路径 | |
| 205 | +| 工具描述 | `summary` / `description` | 简洁用 `summary`,详细说明用 `description` | |
| 206 | +| 输入结构(参数) | `parameters`,`description` | 包括类型、约束的结构化输入 | |
| 207 | +| 输出结构(响应) | `responses` | 包括成功与错误响应的结构化输出 | |
| 208 | +| 调用细节 | `servers`, `path`, `method` | 包括 URL、HTTP 方法、服务器信息 | |
| 209 | +| 安全性 | `security`, `components.securitySchemes` | 用于描述需要鉴权的接口 | |
| 210 | + |
| 211 | +### 关于安全的补充说明 |
| 212 | + |
| 213 | +**LLM 提示注入(prompt injection)** 是当前 AI 安全的一个重大风险(参见 [OWASP Top 10 for LLM 应用](https://owasp.org/www-project-top-10-for-large-language-model-applications/))。当工具的 `description` 或参数说明被恶意注入指令时,就可能发生所谓的 **工具投毒攻击(tool poisoning)**(详见 [Invariant Labs 的安全通告](https://invariantlabs.ai/blog/mcp-security-notification-tool-poisoning-attacks))。因此,务必要对 OpenAPI 规范进行审核和“清洗”。理想情况下,这应当在 API 治理流程中完成;但作为兜底方案,也可以在 OpenAPI 转换为 MCP 工具的过程中加入清洗流程。我将会在后续博客中深入探讨这个问题。 |
| 214 | + |
| 215 | +## 这种映射应当发生在哪里? |
| 216 | + |
| 217 | +正如前文所述,虽然未来可能出现原生支持 MCP 的实现方案,但企业最终仍是要基于已有的 API 投资来暴露 MCP 工具。那么这种 **OpenAPI 到 MCP 的映射** 应该在哪里发生呢? |
| 218 | + |
| 219 | +- 有可能会出现“AI 原生工具”来完成这类转换,它们会考虑到上面提到的语义因素; |
| 220 | +- 有可能你会使用支持将 REST API 暴露为 MCP 端点的 API Gateway; |
| 221 | +- 也可能你需要自行构建一些映射逻辑工具。 |
0 commit comments