用 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 server 和 Connect 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 个工具上,结果如下:
| 工具 | 简介 | readOnly | destructive | openWorld |
|---|---|---|---|---|
help | 查询服务指南 | true | false | false |
endpoints | endpoint 增删改查/部署 | false | true | false |
api_call | 调用 endpoint | false | true | true |
logs | 查询调用日志 | true | false | false |
stats | 用量统计 | true | false | false |
collaborators | 协作密钥/邀请 | false | true | true |
subscription | 订阅信息 | true | false | false |
archives | 归档下载 | true | false | true |
有意没有公开的领域
账号本身的删除、支付方式变更、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_call、collaborators、archives)的 openWorldHint 设成了 false,结果被退回。改完后才通过。
这三个里,archives 会签发外部下载 URL,openWorldHint=true 是显然的。api_call(通过用户定义的 webhook 触发外部通信)和 collaborators(向协作者发送邮件邀请)就比较间接,会让人犹豫一下。事后看,把这两个也保守地标成 true 才是对的。
判断标准其实很简单:一旦被退回,下一次审核又要再等大约两周。 只要工具有任何往外通信的路径,别犹豫,直接 true。从时间成本看这是最省的选择。把每个工具产生的副作用一行一行写下来,确认有没有任何一条会到达系统外,标注就会自然就位。
注意事项 2 — 截图不是"屏幕抓图",而是"模板作业"
提交表单里有一项需要展示你的 app 在 ChatGPT 里大概长什么样的截图。自己抓图直接上传一定会被退回。 这一步 OpenAI 给了明确的指南,提交页面里也直接给出了指南链接。流程是这样的:
- 提交表单到截图这一步时,指南链接会把你引导到 OpenAI 官方的 Figma 文件。
- 把 Figma 文件里的 截图模板复制到你自己的 Figma 工作区。
- 把你的 app 在 ChatGPT 里实际运行的画面 贴到模板里指定的位置。比例、留白、构图模板里都已经定好了,你只需要填内容。
- 把完成的画板 导出为图片,在提交表单上传。
也就是说,提交的成果不是单纯的屏幕截图,而是 把你的 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 接入指南 里。
愿这篇文章能成为走同样这条路的开发者的一条小捷径。
Related Posts
现在,在 ChatGPT 里就能做 API —— 3Min API 已加入 ChatGPT Apps
3Min API 加入 ChatGPT Apps 了。无需写代码,也不用租服务器,你就可以在 ChatGPT 的对话流里直接做 API、测试、把合作伙伴邀请进来。我们用一个刚起步的葡萄酒生意场景,带你看看这件事到底意味着什么。
用 AI 管理你的 API:MCP 支持正式上线
3Min API 现已支持 MCP——连接你的 AI 助手,通过自然对话管理端点、查看日志等。
什么是 JSON?写给非开发者店主的入门指南
一份为葡萄酒店店主故事收尾的 JSON 入门指南。从一张 Excel 表出发,沿着键、值、对象、数组的递归结构走一遍,最后还带一个「今天就能动手」的练习:把自己生意里的一桩事写成 JSON。无需任何前置知识。