🧐 如何让 AI 接入自己的 API?开发了一个将 OpenAPI 文档转为 MCP 服务的工具

前言

hello 大家好,我是欧呦,前阵子写了两篇文章介绍了一个 MCP 服务的开发流程,自己也在 MCP 比较火热的时候在公司里开发了两个 MCP 服务,也做了一次分享,对于 MCP 的开发实现以及如何实现更好的效果有了一些自己的心得。

先给不熟悉 MCP 的朋友介绍一下 MCP,MCP 是一个用于给 AI 大语言模型提供额外上下文的协议,简单来说就是在和 AI 对话的过程中在上下文里提供一些额外的信息,类似于接入知识库,同时还可以让 AI 大语言模型执行一些操作,例如调用 API 或者进行文件操作等等。

目前大部分的 MCP 服务其实都是一些 API 的套壳,目的在于把现有的一些 API 提供给 AI 进行调用, GET 类的 API 可以给 AI 提供外部系统的信息,POST,PATCH,PUT,DELETE 类的 API 就用来进行执行操作。例如 GitHub MCPFigma API 逻辑都是如此,在配置 MCP 的服务时,把自己用于鉴权的 token 带上就可以让 AI 根据情况调用 API 了。

那既然是把 API 套一层壳,那是不是有更简单的方式来实现一个 MCP 的服务,而不是从零开始开发呢?例如直接提供一个 OpenAPI 的文档,直接一比一的把文档中的 API 转换成 MCP 里的工具。有了这个想法之后,我在网上查了一下,果然已经有现成的工具可以帮助我们做这个转换。例如:

  • openapi-mcp-generator 一个命令行工具,可以把 OpenAPI 文档直接转成 MCP 服务的源代码,用的是 TS 实现的。
  • MCP-Link 可以直接跑一个 MCP 服务,提供了 SSE 的接口,在连接服务的时候可以传入 OpenAPI 的地址,连接时实时将 OpenAPI 文档转换为 MCP 中的工具,用的是 Go 实现的。

除此以外还有不少的开源工具帮助我们做这个事情,我自己是比较认可 MCP-Link 的思路的,直接提供一个 MCP 服务,在启动或者和客户端创建连接的时候实时解析 OpenAPI,这样大部分人在使用的时候只需要提供一个 OpenAPI 文档就好了,不用关心 MCP 服务是如何实现和部署的。

perl 复制代码
{
  "mcpServers": {
    "@service-name": {
      "url": "http://localhost:8080/sse?s=[OpenAPI-Spec-URL]&u=[API-Base-URL]&h=[Auth-Header]:[Value-Prefix]"
    }
  }
}

但是在体验 MCP-Link 的过程中也遇到一些问题:

  1. 转换出来的 MCP 工具的描述不够完善,只获取了 openAPI 文档中 API 的 description 字段,但是有些 API 文档中除了 description 外还有 summary 字段,两者都是用于描述 API 的作用的,更完善的 API 描述可以帮助 AI 选择正确的工具进行调用。
  2. 只提供了 SSE 连接的方式,用户需要先用 go 把服务给跑起来,然后再在 AI 客户端中配置连接,没有提供标准输入输出的配置方式,如果只是在本地使用还是麻烦了点。
  3. MCP-link 在调用外部服务后响应的内容比较简单,只有错误信息,没有响应状态码等更多信息,有些外部服务的响应信息参考性不强,提供状态码可以让 AI 更好的分析工具的调用进行以进行下一步操作。
  4. ...

还有一些我认为可行的优化项,可能 MCP-Link 还比较初期,所以实现的不是很完善,我原本想自己基于这个项目优化一下,奈何代码是用 Go 写的,我不是很熟悉,因此决定自己从零实现一个类似的服务,用 TS 来开发,把一些细节点完善的更好一些。

OpenAPI2MCP

这个项目目前已经初步开发完成并开源了 openapi2mcp,核心功能就是我们只需要提供 OpenAPI 规范文件,工具就会自动分析 API 的结构、参数要求和响应格式,并生成相应的 MCP 工具定义。

下面介绍基础的使用方式,首先我们拉取并构建项目

bash 复制代码
git clone https://github.com/oil-oil/openapi2mcp.git
cd openapi2mcp
pnpm install
pnpm run build

然后启动服务:

sql 复制代码
pnpm start:sse

启动服务后在支持 MCP 的客户端中配置服务的 url 就好了,请求参数中携带 OpenAPI 的文档 URL,外部服务的请求地址(如果 OpenAPI 文档中有正确的 servers 字段就无需携带)以及自定义的请求头,默认端口是 3000:

json 复制代码
{
  "mcpServers": {
    "openapi2mcp": {
      "url": "http://localhost:3000/sse?base_url=https://petstore3.swagger.io/api/v3&openapi_spec=https://petstore3.swagger.io/api/v3/openapi.json&headers.Content-Type=application%2Fjson"
    }
  }
}

这里我使用了宠物商店(petstore)的 OpenAPI 文档作为例子,转换之后的 MCP 服务效果是这样的:

除了构建服务提供 SSE 连接,还能支持通过标准输入输出的方式进行 MCP 服务的启用,只需要修改配置为:

json 复制代码
{
  "mcpServers": {
    "openapi2mcp": {
      "command": "node",
      "args": ["/path/to/openapi2mcp/dist/index.js"],
      "env": {
        "TRANSPORT_TYPE": "stdio",
        "API_BASE_URL": "https://petstore3.swagger.io/api/v3",
        "OPENAPI_SPEC_URL": "https://petstore3.swagger.io/api/v3/openapi.json",
        "HEADER.Content-Type": "application/json"
      }
    }
  }
}

标准输入输出的方式需要将原本带在 url 中的查询参数改成环境变量的方式传入,在 AI 客户端每一次启动服务的时候就会去拉取 OpenAPI 文档进行解析了。

除了 SSE 和标准输入输出,还支持了 Streamble HTTP 的传输方式,实际上 Streamble HTTP 从 2025-03-26 版本开始成为 MCP 协议中的标准传输机制,而 SSE 已经被官方弃用了。 支持还有很多的 AI 客户端还是用的 SSE 的方式去接入的,所以我也还是实现了 SSE 连接的方式,三种传输方式可以尽量兼容所有的 AI 客户端,避免 MCP 服务没法连接的问题。

一些优化点

既然自己实现了一个转换服务,当然要把之前发现的一些问题给优化一下,我做了以下这些优化点:

1. MCP 工具描述优化

在将 OpenAPI 文档中的描述转为 MCP 工具的描述时,除了转换 description 外,还会把 summary 给拼接上,为 AI 提供更准确的工具描述

2. 增加缓存逻辑

由于 OpenAPI 文档的获取和转换是在服务启动或者创建连接时实时执行的,如果有些 OpenAPI 文档的体积比较大,API 比较多,在读取文件和解析时的速度就会比较慢。

因此我在 SSE 或 Stream Http 模式下增加了缓存的逻辑,如果一个 OpenAPI 文档已经被解析过了,那么就会被缓存下来,如果 MCP 服务被多个客户端连接并且配置的是同一个 OpenAPI 文档时,获取文件和解析的逻辑只会执行一次,后续的连接都不会重复执行,大大加快了 MCP 服务的连接速度。

缓存功能的特点包括:

  1. 智能键生成:基于 OpenAPI、配置选项和认证信息生成唯一的缓存键
  2. LRU 淘汰策略:当达到最大缓存数量时,自动淘汰最少使用的条目
  3. 定期清理:通过定时器自动清理过期的缓存条目
  4. 安全考虑:对敏感的认证信息进行哈希处理,不直接存储原始令牌

而且我还提供了一些可以传入的环境变量,可以更加自由的进行缓存配置:

变量 描述 默认值 必需
CACHE_ENABLED 启用 OpenAPI 规范缓存 true
CACHE_TTL 缓存生存时间,单位:秒 3600
CACHE_MAX_SIZE 最大缓存条目数 200
CACHE_CHECK_INTERVAL 缓存清理间隔,单位:秒 300

3. 智能合并 API

默认的转换逻辑是一个 API 对应一个 MCP 工具,但是有些 AI 客户端(例如 Cursor) 仅支持最多 40 个 MCP 工具,如果我们有一份超过 40 个 API 的文档转换后就不能在 cursor 中使用了,因此我提供了一个合并 API 的功能,开启后,当解析 API 文档判断到有同一个资源的不同操作时,会将它们合并成一个 MCP 工具。

以 petstore 为例:

未合并时(8 个独立工具):

  • get_pet_petId - GET /pet/{petId}
  • post_pet_petId - POST /pet/{petId}
  • delete_pet_petId - DELETE /pet/{petId}
  • get_pet_findByStatus - GET /pet/findByStatus
  • get_pet_findByTags - GET /pet/findByTags
  • post_pet - POST /pet
  • put_pet - PUT /pet
  • post_pet_petId_uploadImage - POST /pet/{petId}/uploadImage

启用合并后(3 个统一工具):

  1. pet_operations - 处理 /pet 操作:

    json 复制代码
    {
      "operation": {
        "get": { "status": "available" },
        "post": { "body": { "name": "doggie", "photoUrls": ["url1"] } },
        "put": { "body": { "id": 1, "name": "doggie" } }
      }
    }
  2. pet_item_operations - 处理 /pet/{petId} 操作:

    json 复制代码
    {
      "operation": {
        "get": { "petId": "1" },
        "post": { "petId": "1", "name": "doggie" },
        "delete": { "petId": "1" }
      }
    }
  3. pet_item_uploadImage_operations - 处理 /pet/{petId}/uploadImage:

    json 复制代码
    {
      "operation": {
        "post": { "petId": "1", "body": { "additionalMetadata": "test" } }
      }
    }

除了合并 API 的路径和参数,API 的描述也会合并在一起:

不过这里要强调一下,合并之后其实对于 AI 调用 API 工具可能帮助没有那么大,毕竟合并前后传递给 AI 的上下文长度并没有改变多少,描述也没有变得更加清晰,目前仅仅只是为了兼容一些 AI 客户端而实现的功能。

4. 更多更简便的配置

由于有很多 API 在实际请求的时候需要配置认证信息,比如在请求头中带上 token 或者账号密码,因此在 OpenAPI2MCP 的配置中也是支持传入自定义的请求头的,由于请求头是一个键值对的格式,而环境变量仅支持传入字符,请求参数则仅支持传入编码后的字符串,因此传递时看起来不是那么直观:

环境变量

json 复制代码
"env": {
        "TRANSPORT_TYPE": "stdio",
        "API_BASE_URL": "https://petstore3.swagger.io/api/v3",
        "OPENAPI_SPEC_URL": "https://petstore3.swagger.io/api/v3/openapi.json",
        "HEADERS": "{\"Authorization\":\"Bearer your-token\",\"Content-Type\":\"application/json\"}"
      }

URL 请求参数

如果在 URl 中传入请求参数,你需要将这个 JSON 字符串进行 URL 编码。 原始 JSON 字符串: {"Authorization":"Bearer your-token","Content-Type":"application/json","X-Custom-Header":"custom-value"}

URL 编码后(注意空格、引号、冒号、逗号等都需要编码):

http://localhost:3000/sse?base_url=https://petstore3.swagger.io/api/v3&openapi_spec=https://petstore3.swagger.io/api/v3/openapi.json&HEADERS=%7B%22Authorization%22%3A%22Bearer%20your-token%22%2C%22Content-Type%22%3A%22application%2Fjson%22%2C%22X-Custom-Header%22%3A%22custom-value%22%7D

这样在实际配置时还是挺麻烦的,尤其在需要传入多个请求头时,因此我增加了点语法的支持:

环境变量

json 复制代码
{
  "mcpServers": {
    "openapi2mcp": {
      "command": "node",
      "args": ["/path/to/openapi2mcp/dist/index.js"],
      "env": {
        "TRANSPORT_TYPE": "stdio",
        "API_BASE_URL": "https://petstore3.swagger.io/api/v3",
        "OPENAPI_SPEC_URL": "https://petstore3.swagger.io/api/v3/openapi.json",
        "HEADER.Authorization": "Bearer your-token-here",
        "HEADER.Content-Type": "application/json"
      }
    }
  }
}

URL 参数

http://localhost:3000/sse?base_url=https://petstore3.swagger.io/api/v3&openapi_spec=https://petstore3.swagger.io/api/v3/openapi.json&headers.Content-Type=application%2Fjson

虽说 URL 的请求参数中有些特殊符号还是得编码,不过可读性还是大大加强,配置起来也更加简单。除了 Hedaer 的配置优化,我还提供了一些用于控制 MCP 服务请求外部服务行为的参数:

变量 描述 默认值 必需
VERIFY_SSL 启用 SSL 证书验证 true
TIMEOUT 请求超时时间(毫秒) 30000
HEADER.* 自定义请求头 -
HEADERS 请求头的 JSON 字符串 (legacy) -

一些思考

OpenAPI2MCP 现有的功能已经可以实现将大部分的 OpenAPI 文档直接转为 MCP 服务了,但是最终转换的效果还是很依赖 OpenAPI 文档的质量,如果调试效果不满意只能优化自己的 OpenAPI 文档。我有想过在服务中集成一些 AI 功能,帮助用户优化最终 MCP 工具的质量,例如根据实际业务场景补充更具体的描述,或者精简一些无用 API 和描述缩短上下文长度,但是考虑到 AI 生成速度的问题还没有落地,只能从一些能够通过代码处理的逻辑中尽可能优化转换逻辑。

后续我会继续思考如何让这个工具转换出来的 MCP 服务效果更好,提供一些更加精细化的安全性配置,比如基于某个 API 配置特殊请求头等,欢迎大家上手体验。

如果文章对你有帮助,可以为我点个赞👍🏻,respect~

相关推荐
海云前端18 分钟前
前端写简历有个很大的误区,就是夸张自己做过的东西。
前端
葡萄糖o_o27 分钟前
ResizeObserver的错误
前端·javascript·html
AntBlack28 分钟前
Python : AI 太牛了 ,撸了两个 Markdown 阅读器 ,谈谈使用感受
前端·人工智能·后端
leo__52041 分钟前
matlab实现非线性Granger因果检验
人工智能·算法·matlab
struggle202542 分钟前
Burn 开源程序是下一代深度学习框架,在灵活性、效率和可移植性方面毫不妥协
人工智能·python·深度学习·rust
MK-mm1 小时前
CSS盒子 flex弹性布局
前端·css·html
我在北国不背锅1 小时前
基于Java开发的浏览器自动化Playwright-MCP服务器
java·playwright·mcp
小小小小宇1 小时前
CSP的使用
前端
sunbyte1 小时前
50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | AnimatedNavigation(动态导航)
前端·javascript·vue.js·tailwindcss
CareyWYR1 小时前
每周AI论文速递(2506209-250613)
人工智能