ChatGPT Apps 仓库契约与验证阶梯:从“文件已生成”到“仓库可运行”的验收体系
【免费下载链接】skillsSkills Catalog for Codex项目地址: https://gitcode.com/GitHub_Trending/skills4/skills
导读:本文围绕 skills 仓库
chatgpt-apps技能中的repo-contract-and-validation参考文档展开,系统拆解 ChatGPT Apps SDK 应用生成与评审时的最小可工作仓库契约(Minimum Working Repo Contract)与四级验证阶梯(Validation Ladder)。阅读本文后,你将掌握:如何按契约逐项审查一个生成仓库的 Shape、Server、Tools、Widget 与本地开发体验,如何在静态检查、语法编译、本地运行、宿主联调四个层级上递进验证,以及如何通过“报告规则”区分“看起来对”与“真的跑通了”。
为什么需要“仓库契约”:验收的不是文件,而是可运行性
在 repo-contract-and-validation.md 开篇,参考文档就划出了一条清晰的评判底线:
The goal is not "files were created." The goal is "the repo is plausibly runnable and follows a stable working-app contract."
即:评判一个生成的 ChatGPT Apps 仓库,不是看文件是否创建完毕,而是看它是否“合理可运行”、是否遵循一套稳定的“可工作应用”契约。这一原则在整个chatgpt-apps技能中是一以贯之的主线——SKILL.md 的 Build Workflow 第 5a 节明确要求“每个生成的仓库在视为完成前,都应满足一份小而稳定的契约”,第 6 节则要求“对照最小可工作仓库契约进行验证,而不是只看文件是否生成”。
契约的价值在于:它把“质量”从模糊的主观感受,转化为一组可逐项勾选、可分级验证、可汇报边界的客观条目。下面先完整展开契约的五个组成部分。
一、最小可工作仓库契约(Minimum Working Repo Contract)
契约共五个维度:Shape(结构)、Server(服务端)、Tools(工具)、Widget(前端部件)、Local Developer Experience(本地开发体验)。生成每个仓库时,都应满足其中相关部分。
1. Shape:仓库形态与所选原型匹配
契约对“形状”的要求只有两点:
- 仓库结构匹配所选定的 archetype(原型);
- 结构足够简单,用户能一眼识别出 server 与 widget 分别在哪里。
这里的关键概念是archetype。按 app-archetypes.md 的决策规则,每个请求应选择一个主原型并明确声明,共五种:
| Archetype | 适用场景 | 默认结构 |
|---|---|---|
tool-only | 无需 ChatGPT 内 UI,主要是搜索/获取/检索/后台动作 | 仅 MCP server |
vanilla-widget | 小型 demo、workshop、单一 HTML widget | 根级 server +public/静态资源 |
react-widget | 组件化、精致 UI、React/TS 前端工具链 | 拆分server/+web/ |
interactive-decoupled | 棋盘、地图、编辑器、游戏、仪表盘等长状态交互应用 | 拆分server/+web/,data 工具 + render 工具 |
submission-ready | 公开发布、目录提交、评审就绪 | 满足部署与评审要求的最小仓库 |
选择启发式也很直接:请求未提及 UI 则选tool-only;知识源/同步类/连接器/深度研究类强推tool-only+ 标准search/fetch;简单 demo 选vanilla-widget;精致 UI 选react-widget;长生命周期状态或反复交互选interactive-decoupled;只有明确要求发布/评审才升级到submission-ready。
结构是否“简单到一眼能找到 server 和 widget”,在脚手架实现里也有直接体现:scaffold_node_ext_apps.mjs 生成的仓库只有四个文件——package.json、tsconfig.json、public/widget.html(widget)与src/server.ts(server),目录边界一目了然。
2. Server:清晰的 MCP 入口与/mcp端点
契约对 Server 的要求是:
- 有清晰的 MCP server 入口点;
- server 暴露
/mcp端点; - server有意地(intentionally)注册工具;
- 若存在 UI,server 需用MCP Apps UI MIME 类型注册一个 resource/template。
从脚手架源码可以看到这四个要求的完整落地。在 scripts/scaffold_node_ext_apps.mjs 中,端口由PORT环境变量决定,MCP_PATH = "/mcp"被显式定义;HTTP 请求按路径分流,只有/mcp(含子路径)才会进入 MCP 处理,并预先处理OPTIONS预检(CORS 头、mcp-session-id暴露),再通过StreamableHTTPServerTransport承载 MCP 会话。
UI 资源的注册使用@modelcontextprotocol/ext-apps/server提供的registerAppResource,MIME 类型取自 SDK 常量RESOURCE_MIME_TYPE(即 MCP Apps UI 的text/html;profile=mcp-app),并在_meta.ui中附上prefersBorder与 CSP 白名单:
registerAppResource( server, "main-widget", WIDGET_URI, // 例如 ui://widget/main-v1.html {}, async () => ({ contents: [{ uri: WIDGET_URI, mimeType: RESOURCE_MIME_TYPE, // text/html;profile=mcp-app text: WIDGET_HTML, _meta: { ui: { prefersBorder: true, csp: { connectDomains: [], resourceDomains: [] } }, "openai/widgetDescription": "…starter widget rendered by the MCP server.", }, }], }) );对应 SKILL.md 中“以RESOURCE_MIME_TYPE或 MIME 类型注册 widget 资源”的要求。connectDomains/resourceDomains为空数组时表示无外联域名,一旦应用需要调用外部 API,就必须在这里精确放行,这是提交评审时的安全要点(详见 app-archetypes.md 中submission-ready原型的验证重点:_meta.ui.domain与准确的 CSP)。
3. Tools:一工具一意图,注解准确,UI 元数据就位
契约对工具的约束最为细致:
- 每个工具对应一个用户意图(one tool maps to one user intent);
- 描述要能帮助模型正确选工具;
required注解必须存在且准确;- 关联 UI 的工具使用
_meta.ui.resourceUri; _meta["openai/outputTemplate"]只是可选兼容项,不是主契约;- 连接器类、纯数据类、同步类、公司知识库或深度研究类应用,应实现标准
search/fetch工具,而不是自造替代品。
脚手架的registerAppTool是这些规则的完整示范:工具描述以 “Use this when…” 行为提示开头(帮助模型选择),inputSchema用 zod 定义并逐字段describe,annotations四个 hint 全部显式给出,_meta.ui.resourceUri指向 widget URI:
registerAppTool( server, "__TOOL_NAME__", { title: "__APP_TITLE__", description: "Use this when the user wants to render the … widget or inspect a minimal Apps SDK tool result.", inputSchema: { message: z.string().optional().describe("Optional message to show inside the widget."), }, annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false, idempotentHint: true, }, _meta: { ui: { resourceUri: WIDGET_URI }, "openai/toolInvocation/invoking": "Loading …", "openai/toolInvocation/invoked": "… ready", }, }, async ({ message }) => { /* handler */ } );注意契约的措辞是“required annotations are present and accurate”——注解不仅要存在,还要准确:只读工具标readOnlyHint: true,破坏性工具标destructiveHint: true,幂等工具补idempotentHint: true。工具返回体则刻意三分:content(模型的叙述文本)、structuredContent(模型与 widget 共用的结构化数据)、_meta(仅 widget 可见的载荷)。脚手架把_meta["openai/outputTemplate"]放在返回的_meta中作为兼容层,但契约明确它“不是主契约”——主契约是_meta.ui.resourceUri与structuredContent。
关于标准search/fetch:当应用是连接器式、纯数据式、同步导向、面向公司知识库或深度研究时,应直接实现标准search与fetch,不要发明自定义的只读替代品。标准形态见 search-fetch-standard.md:
search:只读,接收单个 query 字符串,返回恰好一个type: "text"的 MCP content 项,其文本为 JSON 编码对象,含results,每条结果含id、title、url;fetch:只读,接收单个文档/条目 id 字符串,返回恰好一个type: "text"的 content 项,其文本为 JSON 编码对象,含id、title、text、url及可选metadata。
契约对应的验证点包括:两个工具都存在、标记只读、输入形状符合标准、返回载荷封装为单个 content 项的 JSON 文本、结果 URL 足够规范可用于引用。
4. Widget:桥接优先,window.openai可选叠加
契约对 Widget 的要求是:
- 需要时初始化 MCP Apps bridge;
- 能接收
ui/notifications/tool-result; - 从
structuredContent渲染; - 交互型 widget 使用
tools/call; - 基线级后续消息使用
ui/message; window.openai是可选且增量的(optional and additive)。
脚手架 widget 的script就是这条契约的逐行实现(见 scaffold_node_ext_apps.mjs):通过postMessage发送 JSON-RPC 2.0 消息与宿主通信,initializeBridge先发ui/initialize再通知ui/notifications/initialized;监听message事件,命中ui/notifications/tool-result时取出message.params.structuredContent并重新渲染;交互按钮通过tools/call发起工具调用;“解释这个应用”按钮通过ui/message向宿主投递用户消息。而window.openai在脚手架里只被用来读取可选的window.openai.theme展示在 meta 栏——这正是“可选叠加”的最小范例。
window.openai的完整能力面在 window-openai-patterns.md 中有详细映射:callTool、sendFollowUpMessage、openExternal、requestDisplayMode、requestModal、uploadFile、selectFiles等运行时 API,以及theme、displayMode、locale、safeArea等上下文信号。核心规则始终是:基线行为建立在 MCP Apps bridge 上(ui/*通知、tools/call、ui/message、ui/update-model-context),window.openai只做 ChatGPT 专属增强,这样应用在非 ChatGPT 宿主上依然有一条连贯的基线路径。
5. Local Developer Experience:本地可起、可查、可连
契约要求每个仓库至少满足:
- 有清晰的本地启动方式;
- 栈允许时,至少有一条低成本检查命令(check command);
- 相关时,回复中要说明如何在 ChatGPT Developer Mode 中连接应用。
脚手架生成的 package.json 恰好提供了这三者的模板:
"scripts": { "dev": "tsx watch src/server.ts", "start": "tsx src/server.ts", "check": "tsc --noEmit" }start/dev是本地启动方式,check是零依赖安装成本的最低检查命令(TypeScript 类型检查,对应验证阶梯 Level 1)。而“如何在 ChatGPT 中连接”,SKILL.md 第 7 节给出了完整链路:本地http://localhost:<port>/mcp启动 →ngrok http <port>暴露公网 HTTPS 隧道 → 用隧道 HTTPS URL +/mcp路径在 ChatGPT 中创建应用 → 在Settings → Apps & Connectors → Advanced settings开启 Developer Mode。文档还特别提醒:工具或元数据变更后要让用户在 ChatGPT 中刷新应用,以便重载最新的工具描述符。
二、验证阶梯(Validation Ladder):能跑多高跑多高
契约定义了“验证什么”,阶梯则定义了“验证到什么程度”。原则是:在不针对单一技术栈过度拟合的前提下,尽量跑到你能跑的最高层级。四级递进如下。
Level 0:静态契约审查(Static contract review)
不运行任何代码,仅对照契约逐项检查仓库,包括:
- 所选 archetype 是否合理;
- 仓库结构是否与 archetype 匹配;
/mcp路由是否存在;- tool/resource/widget 职责是否自洽;
- 若是连接器式或同步导向应用,
search与fetch是否以预期标准形态存在。
这一层几乎零成本,是所有评审的起点,也对应 SKILL.md 中“Run the lowest-cost checks first: static contract review”的顺序要求。
Level 1:语法或编译检查(Syntax or compile checks)
使用技术栈下最便宜的检查,例如:
- Python 语法检查(如
python -m py_compile或python -m compileall); - TypeScript 编译检查(脚手架里就是
tsc --noEmit,即npm run check); - 框架自带的 lint 或构建 sanity check(若已安装)。
这一层能快速捕获低级错误(类型错误、导入错误、语法错误),但不能证明运行时行为正确。
Level 2:本地运行健全性(Local runtime sanity)
条件允许时:
- 启动 server;
- 确认健康路由或
/mcp端点有响应。
脚手架 server 对此提供了现成的可观测抓手:根路径/返回纯文本标识(“…MCP server”),/mcp接受 MCP 会话请求并支持GET/POST/DELETE,可通过curl或直接浏览器访问来确认进程存活与端点可达。注意 scaffold_node_ext_apps.mjs 中StreamableHTTPServerTransport每请求新建、res.on("close")时关闭 transport 与 server 的实现,也保证了本地反复探测不会积累会话泄漏。
Level 3:宿主回路验证(Host loop validation)
条件允许时进行真正的“宿主级”验证:
- 用MCP Inspector检查工具描述符与 widget 渲染;
- 通过ChatGPT Developer Mode实测应用;
- 确认工具结果返回后 widget 能更新。
这是唯一能证明“端到端真的通了”的层级——ui/notifications/tool-result是否送达、structuredContent是否被渲染、tools/call是否回环成功,只有真实宿主回路能给出最终答案。此外 SKILL.md 第 6 节还补充了两条与阶梯配套的实操检查:通过 HTTPS 隧道在 ChatGPT developer mode 中测试、反复调用工具以确认幂等行为。
三、报告规则:必须声明“验到了哪一级”
契约与阶梯的最后一块拼图是报告规则(Reporting Rule):
Always say which validation level was reached and what was not run.
每次交付/评审都必须明确说出:到达了哪个验证级别,哪些级别没有运行。这之所以重要,是因为它把四种本质上不同的结论严格区隔开:
- “仓库结构看起来对”(Level 0 结论);
- “语法是有效的”(Level 1 结论);
- “server 能启动”(Level 2 结论);
- “宿主集成真的被演练过”(Level 3 结论)。
前三种都不能冒充实证“应用可运行”。在 SKILL.md 的输出规范中,这也被固化为固定输出项——“针对最小可工作仓库契约执行的验证”以及“明确说明执行了哪些验证、未执行哪些”;即使只交付脚手架、不安装依赖,也要求“仍要运行低成本检查,并准确说明你没运行什么”。这种“如实上报验证边界”的纪律,恰恰是让整个技能变得更可靠(more reliable)的机制。
四、把契约用在真实评审流中
将契约与阶梯组合起来,一个可复用的 ChatGPT Apps 仓库评审流如下:
- 读 SKILL.md 与 app-archetypes.md,确认所选 archetype 是否合理(对应 SKILL.md 的“分类先行”原则);
- 对照契约五维度逐项勾选:Shape(server/widget 位置清晰)、Server(
/mcp存在、UI resource 用RESOURCE_MIME_TYPE注册)、Tools(一工具一意图、注解准确、UI 工具带_meta.ui.resourceUri、连接器类用标准search/fetch)、Widget(bridge 初始化、ui/notifications/tool-result、structuredContent渲染、交互用tools/call、后续消息用ui/message、window.openai仅作增量)、Local DX(可启动、有check命令、说明 Developer Mode 连接方式); - 跑验证阶梯:静态审查 → 语法/编译 → 本地
/mcp探测 →(可行时)MCP Inspector + ChatGPT Developer Mode 宿主回路; - 按报告规则输出:声明到达的级别与未运行的级别,把“结构对”“语法对”“能启动”“集成跑通”严格分层表述。
这套方法论的源头全部收敛在参考文档 repo-contract-and-validation.md,而它的每个条目都能在当前仓库的 SKILL.md、app-archetypes.md、search-fetch-standard.md、window-openai-patterns.md、interactive-state-sync-patterns.md 以及脚手架 scaffold_node_ext_apps.mjs 中找到对应实现。下次无论是生成一个 ChatGPT 应用仓库,还是评审他人生成的仓库,都可以按“契约逐项审查 + 阶梯分级验证 + 如实上报边界”的框架执行——这正是从“文件已生成”迈向“仓库可运行”的最短路径。
【免费下载链接】skillsSkills Catalog for Codex项目地址: https://gitcode.com/GitHub_Trending/skills4/skills
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考