返回所有文章
yeonghyeon yeonghyeon · 2026年4月29日

用 Apps SDK 构建 ChatGPT App:MCP·OAuth 实现与审核回顾

用 Apps SDK 构建 ChatGPT App:MCP·OAuth 实现与审核回顾

大家好,我是 yeonghyeon,负责 3Min API 的开发。

过去几周,我一直在推进 3Min API 的 ChatGPT Apps 注册。中间被退回一次,修正后获得通过,期间我们用同一份代码也提交到了 Claude Connectors。这篇文章是写给走同一条路的开发者的整理:需要实现什么哪些地方容易被退回审核环节实际在做什么,我都放在了一起。

跟具体云服务或部署栈相关的内容,我有意没写。无论底层栈是什么,ChatGPT Apps 注册的流程是一样的。

概述 — ChatGPT Apps 与作为底层的 MCP

ChatGPT Apps 是 OpenAI 推出的扩展模型,让你可以从 ChatGPT 内部调用外部服务。用户在 ChatGPT 里添加你的 app,在对话流程中可以直接调用你后端公开的工具。

它的基础是 Model Context Protocol(MCP)。MCP 是连接 LLM 和外部工具的开放规范,定义了如何列出工具、如何安全地调用它们。ChatGPT Apps 是跑在 MCP 之上的客户端之一,Anthropic 的 Claude Connectors 也建立在同一个规范上。也就是说,把 MCP 服务器一次实现好,两个生态都能覆盖。我们就用同一份代码同时提交了两边,工具定义和认证流程是直接共享的。

为什么我们选择成为工具,而不是做自己的 AI 聊天机器人

一开始我们也考虑过做自家的聊天机器人。但退一步想想,用户其实已经把自己的业务背景充分讲给了 ChatGPT 或 Claude。如果我们再做一个聊天机器人,同一个人就要从头再说一遍同样的故事。

所以我们换了方向。把 3Min API 作为工具,接进你已经在用的 AI 里。 平常你向 ChatGPT 倾诉业务问题的那个对话框,现在可以直接说"那我来用 3Min API 帮你设置好",并真的把事情做完。我们仍在评估自家聊天机器人,但优先级在工具化这边。

实现

SDK 选择 — 仅使用标准 MCP SDK

OpenAI 的确提供了一个 ChatGPT Apps 专用 SDK,但我们没有走那条路。我们的服务器只依赖一个东西:标准 MCP SDK(@modelcontextprotocol/sdk)。原因很简单:同一台服务器要同时被 ChatGPT Apps 和 Claude Connectors 识别。只用标准 SDK 来写,就不会因为某一边生态更新而让代码分叉,我们也能把同一份构建产物原样提交到两个目录。

OpenAI 这边的规范整理在 Build your MCP serverConnect from ChatGPT。两份文档都是从客户端视角描述 ChatGPT 客户端如何识别和调用你的服务器——只要把标准 MCP 服务器认真实现好,OpenAI 那一侧的要求就自然满足了。

认证 — OAuth 2.1 作为基本,API Key 作为同一台服务器上的辅助

对于 ChatGPT Apps 来说,实际上能选的认证就是 OAuth 2.1 一种。OpenAI 的 官方指南 里写得很清楚:"ChatGPT does not support … custom API keys or customer-provided mTLS certificates"。也就是说,要提交到 ChatGPT Apps 目录的 app,把 OAuth 2.1 实现好就够了(对于不涉及用户数据的工具,允许使用 noauth 匿名模式;但像我们这种要操作用户账号下的 endpoint 和日志的工具,OAuth 是必须的)。

不过,如果你想让同一台 MCP 服务器也能在 Cursor、Claude Code、Gemini CLI 这类本地客户端里直接使用,情况就不同了。本地环境是用户自己可以保管密钥的地方,OAuth 流程在那里反而显得太重。所以我们选择了 把 OAuth 2.1 作为 ChatGPT Apps·Claude Connectors 的基本认证,再在同一台服务器上叠加一条 API Key 路径。同一份工具定义,在两种认证下都能直接工作。

OAuth 2.1 — ChatGPT Apps 与 Claude Connectors 的基本认证

跑在托管环境里的客户端没法让用户自己管密钥,OpenAI 的政策也没给其他选项。要让客户端正确识别我们的服务器,以下标准必须就位。

  • RFC 7591 — OAuth Dynamic Client Registration。客户端自己完成注册流程。
  • RFC 8414 — Authorization Server Metadata。在 /.well-known/oauth-authorization-server 暴露认证端点。
  • RFC 9728 — Protected Resource Metadata。暴露 MCP 资源本身的元数据。
  • RFC 7636 PKCE — 防止 authorization code 被中间拦截的 S256 challenge。

流程本身就是标准那一套。客户端动态注册 → 用户登录后签发 authorization code(一次性,10 分钟过期)→ 带 PKCE 校验做 token 交换 → access token(1 小时)+ refresh token(30 天,带轮换)。access token 过期时用 refresh token 续期,每次续期连 refresh token 自身也轮换,以降低被盗用的风险。

实现里最花心思的一处是 保证 code 在原子层面只被消费一次。authorization code 是被中间拦截价值很大的资源,所以我们让 token 交换那一步用一条 update 语句把 code 一次性 claim 下来,避免出现 race。

API Key — 让同一份代码也能在 CLI/IDE 里使用的辅助路径

ChatGPT Apps 走 OAuth 接进来,但我们希望同一份工具定义在 Cursor、Claude Code、Gemini CLI、Smithery 这类本地 MCP 客户端里也能直接使用,所以又叠加了一条 API Key 路径。本地环境里用户可以自己保管密钥,用 x-api-key 头部带签发的 key 是最干净的做法。我们签发带 tm_mcp_ 前缀的、按用户区分的 key,并按 key 跟踪启用/停用状态和最后使用时间。

对外公开的 8 个工具,以及它们的权限标注

3Min API 向 ChatGPT/Claude 公开 8 个工具。每个工具都需要明确声明 MCP 标准的三个 annotation。先看含义:

annotation含义true 的例子
readOnlyHint是否只读(不修改数据)查询日志/统计
destructiveHint是否会引起不可逆的变更修改/删除 endpoint
openWorldHint是否会与外部世界通信或产生副作用发出外部 webhook

把这三个值套到我们的 8 个工具上,结果如下:

工具简介readOnlydestructiveopenWorld
help查询服务指南truefalsefalse
endpointsendpoint 增删改查/部署falsetruefalse
api_call调用 endpointfalsetruetrue
logs查询调用日志truefalsefalse
stats用量统计truefalsefalse
collaborators协作密钥/邀请falsetruetrue
subscription订阅信息truefalsefalse
archives归档下载truefalsetrue

有意没有公开的领域

账号本身的删除、支付方式变更、MCP 认证 key 的签发与吊销 这类一旦同意发生偏差就无法回退、或者直接关系到认证体系本身的动作,我们没有暴露成工具。把这些交给 LLM 判断,一次错误调用就能直接演变成安全事故,而且这也不符合 ChatGPT Apps 审核指南推荐的方向。

测试 — 两条认证路径都跑一遍比较稳

正式提交前,我们把两条路径都跑了一遍。

  • OAuth 路径 — 在 ChatGPT 网页版的 Settings → Apps & Connectors → Advanced settings 里打开 Developer Mode,然后用 'Create app' 注册我们的 MCP 服务器 URL。ChatGPT 会自动去读我们服务器的 /.well-known/... 来识别 OAuth 元数据,所以可以亲自走完同意页面,直到真正调用工具。
  • API Key 路径 — 在 Claude Code、Cursor、Gemini CLI 里加上 MCP 服务器,验证工具调用。同时也注册了 Smithery,确认在目录里能正常出现。

两条路径的代码本身是一样的,但 OAuth 那一侧还要再跑通 well-known 元数据、同意页面、token 续期,所以多了一层难度。提交之前,建议你至少把 真正的 ChatGPT 实例连接到你的服务器、把工具列表拉下来 这一步亲自跑一遍。

提交审核 — 被退回一次之后才明白的事

提交本身,跟着 OpenAI 官方指南(app-submission-guidelines)边看边问 ChatGPT 也能顺利完成。但在表单填写阶段,ChatGPT 会真的连进你的服务器去拉工具列表,所以 提交时刻你的生产环境必须已经在线。还要一并提供测试账号信息(邮箱/密码,或者另起一个审核专用账号),否则 OpenAI 的审核员没法亲自调用工具。

注意事项 1 — 工具权限标注要保守

这是最常见的退回原因。我们第一次提交时,把三个会与外部通信的工具(api_callcollaboratorsarchives)的 openWorldHint 设成了 false,结果被退回。改完后才通过。

这三个里,archives 会签发外部下载 URL,openWorldHint=true 是显然的。api_call(通过用户定义的 webhook 触发外部通信)和 collaborators(向协作者发送邮件邀请)就比较间接,会让人犹豫一下。事后看,把这两个也保守地标成 true 才是对的。

判断标准其实很简单:一旦被退回,下一次审核又要再等大约两周。 只要工具有任何往外通信的路径,别犹豫,直接 true。从时间成本看这是最省的选择。把每个工具产生的副作用一行一行写下来,确认有没有任何一条会到达系统外,标注就会自然就位。

注意事项 2 — 截图不是"屏幕抓图",而是"模板作业"

提交表单里有一项需要展示你的 app 在 ChatGPT 里大概长什么样的截图。自己抓图直接上传一定会被退回。 这一步 OpenAI 给了明确的指南,提交页面里也直接给出了指南链接。流程是这样的:

  1. 提交表单到截图这一步时,指南链接会把你引导到 OpenAI 官方的 Figma 文件
  2. 把 Figma 文件里的 截图模板复制到你自己的 Figma 工作区
  3. 把你的 app 在 ChatGPT 里实际运行的画面 贴到模板里指定的位置。比例、留白、构图模板里都已经定好了,你只需要填内容。
  4. 把完成的画板 导出为图片,在提交表单上传。

也就是说,提交的成果不是单纯的屏幕截图,而是 把你的 app 画面放进 OpenAI 定好的画框里得到的成品。提交前再过一遍指南,严格照着做就行。如果跳过这一步,自己抓图上去,提交会直接被退回。

审核回顾 — 通过之后还有一步

从提交到出结果,大约要等两周以上。被退回再修正后重新提交,通常还要再花差不多的时间。可以在 OpenAI 开发者控制台查看状态,被退回或通过时也会收到邮件。

收到通过邮件并不意味着已经上架。还要在控制台点一下 Publish 按钮,普通用户那边才看得到。

上架之后还有一件事要留意。即使用户已经把你的 app 添加进去了,ChatGPT 也不会在每段对话里都自动去用你的工具。哪怕话题相关,经常也会回出不带工具调用的回复。这种时候要 提醒用户用 @ 提及来直接召唤你的 app。把这一点告诉用户,工具使用率会显著提升。

Claude Connectors 当前进展

Claude Connectors 跑在同一套 MCP 上。代码层面几乎不需要补什么。但审核流程不像 OpenAI 那边整理得那么齐:没有可以查询提交状态的控制台,我们提交快一个月了,还没收到通过或退回的回复。两边同时投是值得推荐的,但排期建议以 OpenAI 这边为主。

亲自试试

如果你想在 ChatGPT 里试 3Min API 的 MCP 工具,或者想看看我们具体公开了什么,只需要 在 ChatGPT Apps 添加界面搜索 "3Min API" 安装即可。完成一次登录之后,平常对话里 @3Min API 提及就能调用工具。

如果想从本地 MCP 客户端(Claude Code、Cursor 等)试,登录后到 设置 → AI 接入 页面签发 API Key,就能马上用。每个工具的细节和调用示例,都整理在 AI 接入指南 里。

愿这篇文章能成为走同样这条路的开发者的一条小捷径。