Build ChatGPT App
Overview
Scaffold ChatGPT Apps SDK implementations with a docs-first, example-first workflow, then generate code that follows current Apps SDK and MCP Apps bridge patterns.
This skill produces:
- A primary app-archetype classification and repo-shape decision
- A tool plan (names, schemas, annotations, outputs)
- An upstream starting-point recommendation (official example, ext-apps example, or local fallback scaffold)
- An MCP server scaffold (resource registration, tool handlers, metadata)
- A widget scaffold (MCP Apps bridge first,
window.openai compatibility/extensions second)
- A reusable Node +
@modelcontextprotocol/ext-apps starter scaffold for low-dependency fallbacks
- A validation report against the minimum working repo contract
- Local dev and connector setup steps
- A short stakeholder summary of what the app does (when requested)
When to Use
Use this skill when the task involves any of the following:
- Scaffolding a new ChatGPT App (MCP server + widget UI)
- Planning tool surfaces for an Apps SDK project
- Adapting an official OpenAI example or
@modelcontextprotocol/ext-apps example into a ChatGPT app
- Refactoring an existing Apps SDK demo into a production-ready structure
- Wiring the MCP Apps bridge (
postMessage JSON-RPC) or window.openai compatibility APIs
- Applying Apps SDK metadata, CSP allowlists, domain settings, or URI versioning
- Preparing an app for local ChatGPT Developer Mode testing or public submission
- Troubleshooting tool descriptors, widget rendering, or connector setup
Trigger keywords: ChatGPT app, Apps SDK, MCP server, widget, ext-apps, connector, scaffold, tool plan, submission, window.openai, MCP Apps bridge, structuredContent, resourceUri.
Prerequisites
- Docs-first workflow is mandatory. Use
$openai-docs first whenever building or changing a ChatGPT Apps SDK app. If $openai-docs is unavailable, use mcp__openaiDeveloperDocs__search_openai_docs and mcp__openaiDeveloperDocs__fetch_openai_doc.
- Node.js installed for the fallback scaffold script (
scripts/scaffold_node_ext_apps.mjs).
- PowerShell as the primary shell on Windows host. All commands below assume PowerShell unless noted.
- ngrok (or equivalent HTTPS tunnel) for local ChatGPT Developer Mode testing.
- MCP Inspector for runtime validation of tool descriptors and widget rendering.
- OpenAI Platform dashboard access for submission tasks (org verification + Owner role).
Procedure
0. Classify the App Archetype
Before choosing examples, repo shape, or scaffolds, classify the request into one primary archetype and state it explicitly.
tool-only — no UI; MCP server with tools only
vanilla-widget — minimal HTML widget via MCP Apps bridge
react-widget — React widget using @modelcontextprotocol/ext-apps/react or similar
interactive-decoupled — decoupled data/render tools with stateful widget
submission-ready — public directory launch target
Rules:
- Prefer a single primary archetype instead of mixing several.
- If the request is broad, infer the smallest archetype that can still satisfy it.
- Escalate to
submission-ready only when the user asks for public launch, directory submission, or review-ready deployment.
- Call out the chosen archetype in your response so the user can correct it early.
Load references/app-archetypes.md for the full decision rubric.
1. Plan Tools Before Code
Define the tool surface area from user intents before writing any implementation code.
- Use one job per tool.
- Write tool descriptions that start with "Use this when..." behavior cues.
- Make inputs explicit and machine-friendly (enums, required fields, bounds).
- Decide whether each tool is data-only, render-only, or both.
- Set annotations accurately:
readOnlyHint, destructiveHint, openWorldHint; add idempotentHint when true.
- If the app is connector-like, data-only, sync-oriented, or intended for company knowledge or deep research, default to the standard
search and fetch tools instead of inventing custom read-only equivalents.
- For educational/demo apps, prefer one concept per tool so the model can pick the right example cleanly.
- Group demo tools by learning objective: data into the widget, widget actions back into the conversation or tools, host/layout environment signals, and lifecycle/streaming behavior.
Load references/search-fetch-standard.md when search and fetch may be relevant.
2. Choose an App Architecture
Choose the simplest structure that fits the goal.
- Minimal demo pattern — for quick prototypes, workshops, or proofs of concept.
- Decoupled data/render pattern — for production UX so the widget does not re-render on every tool call.
Prefer the decoupled pattern for non-trivial apps:
- Data tools return reusable
structuredContent.
- Render tools attach
_meta.ui.resourceUri and optional _meta["openai/outputTemplate"].
- Render tool descriptions state prerequisites (for example, "Call
search first").
2a. Start From an Upstream Example When One Fits
Default to upstream examples for greenfield work when they are close to the requested app.
- Check the official OpenAI examples first for ChatGPT-facing apps, polished UI patterns, React components, file upload flows, modal flows, or apps that resemble the docs examples.
- Use
@modelcontextprotocol/ext-apps examples when the request is closer to raw MCP Apps bridge/server wiring, or when version-matched package patterns matter more than ChatGPT-specific polish.
- Pick the smallest matching example and copy only the relevant files; do not transplant an entire showcase app unchanged.
- After copying, reconcile the example with the current docs you fetched: tool names/descriptions, annotations,
_meta.ui.*, CSP, URI versioning, and local run instructions.
- State which example you chose and why in one sentence.
Load references/upstream-example-workflow.md for the selection and adaptation rubric.
2b. Use the Starter Script When a Low-Dependency Fallback Helps
Use scripts/scaffold_node_ext_apps.mjs only when:
- The user wants a quick, greenfield Node starter
- A vanilla HTML widget is acceptable
- No upstream example is a better starting point
Skip the script when:
- A close official example exists
- The user already has an existing app structure
- They need a non-Node stack
- They explicitly want React first
- They only want a plan/review instead of code
To run the fallback scaffold (PowerShell):
node scripts/scaffold_node_ext_apps.mjs
After running it:
- Patch the generated output to match the current docs and the user request: adjust tool names/descriptions, annotations, resource metadata, URI versioning, and README/run instructions.
- The generated widget keeps follow-up messaging on the standard
ui/message bridge and only uses window.openai for optional host signals/extensions.
- If you choose the script instead of an upstream example, say why the fallback is better for that request.
3. Scaffold the MCP Server
Generate a server that:
- Registers a widget resource/template with the MCP Apps UI MIME type (
text/html;profile=mcp-app) or the SDK constant (RESOURCE_MIME_TYPE) when using @modelcontextprotocol/ext-apps/server.
- Registers tools with clear names, schemas, titles, and descriptions.
- Returns
structuredContent (model + widget), content (model narration), and _meta (widget-only data) intentionally.
- Keeps handlers idempotent or documents non-idempotent behavior explicitly.
- Includes tool status strings (
openai/toolInvocation/*) when helpful in ChatGPT.
Keep structuredContent concise. Move large or sensitive widget-only payloads to _meta.
4. Scaffold the Widget UI
Use the MCP Apps bridge first for portability, then add ChatGPT-specific window.openai APIs when they materially improve UX.
- Listen for
ui/notifications/tool-result (JSON-RPC over postMessage).
- Render from
structuredContent.
- Use
tools/call for component-initiated tool calls.
- Use
ui/update-model-context only when UI state should change what the model sees.
Use window.openai for compatibility and extensions (file upload, modal, display mode, etc.), not as the only integration path for new apps.
API Surface Guardrails
- Some examples wrap the bridge with an
app object (for example, @modelcontextprotocol/ext-apps/react) and expose helper names like app.sendMessage(), app.callServerTool(), app.openLink(), or host getter methods.
- Treat those wrappers as implementation details or convenience layers, not the canonical public API to teach by default.
- For ChatGPT-facing guidance, prefer the current documented surface:
window.openai.callTool(...)
window.openai.sendFollowUpMessage(...)
window.openai.openExternal(...)
window.openai.requestDisplayMode(...)
- Direct globals:
window.openai.theme, window.openai.locale, window.openai.displayMode, window.openai.toolInput, window.openai.toolOutput, window.openai.toolResponseMetadata, window.openai.widgetState
- If you reference wrapper helpers from repo examples, map them back to the documented
window.openai or MCP Apps bridge primitives and call out that the wrapper is not the normative API surface.
Load references/window-openai-patterns.md for the wrapper-to-canonical mapping and for React helper extraction patterns.
5. Add Resource Metadata and Security
Set resource metadata deliberately on the widget resource/template:
_meta.ui.csp with exact connectDomains and resourceDomains
_meta.ui.domain for app submission-ready deployments
_meta.ui.prefersBorder (or OpenAI compatibility alias when needed)
- Optional
openai/widgetDescription to reduce redundant narration
Avoid frameDomains unless iframe embeds are core to the product.
5a. Enforce a Minimum Working Repo Contract
Every generated repo should satisfy a small, stable contract before you consider it done:
- The repo shape matches the chosen archetype.
- The MCP server and tools are wired to a reachable
/mcp endpoint.
- Tools have clear descriptions, accurate annotations, and UI metadata where needed.
- Connector-like, data-only, sync-oriented, and company-knowledge-style apps use the standard
search and fetch tool shapes when relevant.
- The widget uses the MCP Apps bridge correctly when a UI exists.
- The repo includes enough scripts or commands for a user to run and check it locally.
- The response explicitly says what validation was run and what was not run.
Load references/repo-contract-and-validation.md for the detailed checklist and validation ladder.
6. Validate the Local Loop
Validate against the minimum working repo contract, not just "did files get created."
Lowest-cost checks first:
- Static contract review
- Syntax or compile checks when feasible
- Local
/mcp health check when feasible
Then move up to runtime checks:
4. Verify tool descriptors and widget rendering in MCP Inspector
5. Test the app in ChatGPT developer mode through HTTPS tunneling
6. Exercise retries and repeated tool calls to confirm idempotent behavior
7. Check widget updates after host events and follow-up tool calls
If you are only delivering a scaffold and are not installing dependencies, still run low-cost checks and say exactly what you did not run.
Load references/repo-contract-and-validation.md for the validation ladder.
7. Connect and Test in ChatGPT (Developer Mode)
For local development, include explicit ChatGPT setup steps:
Run the MCP server locally on http://localhost:<port>/mcp.
Expose the local server with a public HTTPS tunnel:
ngrok http <port>
Use the tunneled HTTPS URL plus /mcp path when connecting from ChatGPT.
In ChatGPT, enable Developer Mode under Settings → Apps & Connectors → Advanced settings.
In ChatGPT app settings, create a new app for the remote MCP server and paste the public MCP URL.
Tell users to refresh the app after MCP tool/metadata changes so ChatGPT reloads the latest descriptors.
Note: Some docs/screenshots still use older "connector" terminology. Prefer current product wording ("app") while acknowledging both labels when giving step-by-step instructions.
8. Plan Production Hosting and Deployment
When the user asks to deploy or prepare for launch, generate hosting guidance for the MCP server (and widget assets if hosted separately):
- Host behind a stable public HTTPS endpoint (not a tunnel) with dependable TLS.
- Preserve low-latency streaming behavior on
/mcp.
- Configure secrets outside the repo (environment variables / secret manager).
- Add logging, request latency tracking, and error visibility for tool calls.
- Add basic observability (CPU, memory, request volume) and a troubleshooting path.
- Re-test the hosted endpoint in ChatGPT Developer Mode before submission.
9. Prepare Submission and Publish (Public Apps Only)
Only include these steps when the user intends a public directory listing:
- Use
apps-sdk/deploy/submission for the submission flow and apps-sdk/app-submission-guidelines for review requirements.
- Keep private/internal apps in Developer Mode instead of submitting.
- Confirm org verification and Owner-role prerequisites before submission work.
- Ensure the MCP server uses a public production endpoint (no localhost/testing URLs) and has submission-ready CSP configured.
- Prepare submission artifacts: app metadata, logo/screenshots, privacy policy URL, support contact, test prompts/responses, localization info.
- If auth is required, include review-safe demo credentials and test the login path end-to-end.
- Submit for review in the Platform dashboard, monitor review status, and publish only after approval.
Mandatory Docs-First Workflow
Use $openai-docs first whenever building or changing a ChatGPT Apps SDK app.
- Invoke
$openai-docs (preferred) or call the OpenAI docs MCP server directly.
- Fetch current Apps SDK docs before writing code, especially (baseline pages):
apps-sdk/build/mcp-server
apps-sdk/build/chatgpt-ui
apps-sdk/build/examples
apps-sdk/plan/tools
apps-sdk/reference
- Fetch
apps-sdk/quickstart when scaffolding a new app or generating a first-pass implementation, and check the official examples repo/page before inventing a scaffold from scratch.
- Fetch deployment/submission docs when the task includes local ChatGPT testing, hosting, or public launch:
apps-sdk/deploy
apps-sdk/deploy/submission
apps-sdk/app-submission-guidelines
- Cite the docs URLs you used when explaining design choices or generated scaffolds.
- Prefer current docs guidance over older repo patterns when they differ, and call out compatibility aliases explicitly.
- If doc search times out or returns poor matches, fetch the canonical Apps SDK pages directly by URL and continue; do not let search failure block scaffolding.
If $openai-docs is unavailable, use:
mcp__openaiDeveloperDocs__search_openai_docs
mcp__openaiDeveloperDocs__fetch_openai_doc
Load references/apps-sdk-docs-workflow.md for suggested doc queries and a compact checklist.
Prompt Guidance
Use prompts that explicitly pair this skill with $openai-docs so the resulting scaffold is grounded in current docs.
Preferred prompt patterns:
Use $build-chatgpt-app with $openai-docs to scaffold a ChatGPT app for <use case> with a <TS/Python> MCP server and <React/vanilla> widget.
Use $build-chatgpt-app with $openai-docs to adapt the closest official Apps SDK example into a ChatGPT app for <use case>.
Use $build-chatgpt-app and $openai-docs to refactor this Apps SDK demo into a production-ready structure with tool annotations, CSP, and URI versioning.
Use $build-chatgpt-app with $openai-docs to plan tools first, then generate the MCP server and widget code.
When responding, ask for or infer these inputs before coding:
- Use case and primary user flows
- Read-only vs mutating tools
- Demo vs production target
- Private/internal use vs public directory submission
- Backend language and UI stack
- Auth requirements
- External API domains for CSP allowlists
- Hosting target and local dev approach
- Org ownership/verification readiness (for submission tasks)
Interactive State Guidance
Load references/interactive-state-sync-patterns.md when the app has long-lived widget state, repeated interactions, or component-initiated tool calls (for example, games, boards, maps, dashboards, editors).
Use it to choose patterns for:
- State snapshots plus monotonic event tokens (
stateVersion, resetCount, etc.)
- Idempotent retry-safe handlers
structuredContent vs _meta partitioning
- MCP Apps bridge-first update flows with optional
window.openai compatibility
- Decoupled data/render tool architecture for more complex interactive apps
Default Starting-Point Order
For greenfield apps, prefer these starting points in order:
- Official OpenAI examples when a close example already matches the requested stack or interaction pattern.
- Version-matched
@modelcontextprotocol/ext-apps examples when the user needs a lower-level or more portable MCP Apps baseline.
scripts/scaffold_node_ext_apps.mjs only when no close example fits, the user wants a tiny Node + vanilla starter, or network access/example retrieval is undesirable.
Do not generate a large custom scaffold from scratch if a close upstream example already exists. Copy the smallest matching example, remove unrelated demo code, then patch it to the current docs and the user request.
Output Expectations
When using this skill to scaffold code, produce output in this order unless the user asks otherwise:
- Primary app archetype chosen and why
- Tool plan and architecture choice (minimal vs decoupled)
- Upstream starting point chosen (official example, ext-apps example, or local fallback scaffold) and why
- Doc pages/URLs used from
$openai-docs
- File tree to create or modify
- Implementation (server + widget)
- Validation performed against the minimum working repo contract
- Local run/test instructions (including tunnel + ChatGPT Developer Mode app setup)
- Deployment/hosting guidance (if requested or implied)
- Submission-readiness checklist (for public launch requests)
- Risks, gaps, and follow-up improvements
For direct scaffold requests, do not stop at the plan: give the brief plan, then create the files immediately.
Pitfalls
- Do not invent APIs or CLI flags. Only use documented
window.openai.* methods and MCP Apps bridge primitives. Wrapper helpers (app.sendMessage(), app.callServerTool(), etc.) are implementation details, not the canonical public API.
- Do not skip the docs-first workflow. Always fetch current Apps SDK docs before writing code. If search times out, fetch canonical pages directly by URL.
- Do not generate a large custom scaffold from scratch if a close upstream example exists. Copy the smallest matching example, remove unrelated demo code, then patch.
- Do not use
frameDomains unless iframe embeds are core to the product.
- Do not put large or sensitive widget-only payloads in
structuredContent. Move them to _meta.
- Do not use
ui/update-model-context casually. Only when UI state should change what the model sees.
- Do not submit private/internal apps to the public directory. Keep them in Developer Mode.
- Do not use localhost/testing URLs for submission. The MCP server must use a public production endpoint.
- Do not let doc search failure block scaffolding. Fetch canonical pages directly by URL and continue.
- Do not mix multiple archetypes. Pick one primary archetype; escalate to
submission-ready only for public launch.
- Do not forget to tell users to refresh the app after MCP tool/metadata changes so ChatGPT reloads the latest descriptors.
- Do not drop the "what validation was run and what was not run" statement — always include it.
Verification
Static Contract Review
Check the generated repo against the minimum working repo contract:
# Verify repo structure matches archetype
Get-ChildItem -Recurse -Name | Select-String -Pattern "server|web|widget|mcp"
Syntax / Compile Checks
# TypeScript compile check (if applicable)
npx tsc --noEmit
# Node syntax check for server entry
node --check server/index.ts
Local /mcp Health Check
# Start the server
npm run dev
# In another terminal, verify the /mcp endpoint responds
curl http://localhost:<port>/mcp
MCP Inspector Validation
# Launch MCP Inspector against the local server
npx @modelcontextprotocol/inspector node server/index.ts
Verify:
- Tool descriptors appear with correct names, schemas, and annotations
- Widget resource/template is registered with
text/html;profile=mcp-app MIME type
structuredContent and _meta are returned correctly
ChatGPT Developer Mode Test
# Tunnel the local server
ngrok http <port>
Then in ChatGPT:
- Settings → Apps & Connectors → Advanced settings → enable Developer Mode
- Create a new app, paste the tunneled HTTPS URL +
/mcp
- Exercise each tool and confirm widget rendering
- Test repeated tool calls for idempotent behavior
- Check widget updates after host events and follow-up tool calls
Submission Readiness Checklist (Public Apps Only)
References
| File |
When to Load |
references/app-archetypes.md |
Classifying requests into supported app shapes before choosing examples or scaffolds |
references/apps-sdk-docs-workflow.md |
Doc queries, page targets, and code-generation checklist for the docs-first workflow |
references/interactive-state-sync-patterns.md |
Long-lived widget state, repeated interactions, or component-initiated tool calls (games, boards, maps, dashboards, editors) |
references/repo-contract-and-validation.md |
Generating or reviewing a repo against the minimum working repo contract and validation ladder |
references/search-fetch-standard.md |
Connector-like, data-only, sync-oriented apps, or apps meant to work with company knowledge or deep research |
references/upstream-example-workflow.md |
Starting a greenfield app or deciding whether to adapt an upstream example or use the local fallback scaffold |
references/window-openai-patterns.md |
ChatGPT-specific widget behavior or translating repo examples that use wrapper-specific app.* helpers |
scripts/scaffold_node_ext_apps.mjs |
Minimal Node + @modelcontextprotocol/ext-apps fallback starter scaffold (vanilla HTML widget) |
1---2name: build-chatgpt-app3description: Scaffolds ChatGPT Apps SDK apps that pair an MCP server with widget UI (MCP Apps bridge, window.openai, CSP, tool plans, Developer Mode, directory submission). Use when designing ChatGPT app tools, registering UI resources, wiring ext-apps, or producing a docs-aligned scaffold. Not for generic MCP servers outside ChatGPT Apps (mcp-server-authoring). Never invent window.openai APIs from memory — fetch current Apps SDK docs first.4---5
6# Build ChatGPT App
7
8## Overview
9
10Scaffold ChatGPT Apps SDK implementations with a docs-first, example-first workflow, then generate code that follows current Apps SDK and MCP Apps bridge patterns.
11
12This skill produces:
13
14- A primary app-archetype classification and repo-shape decision
15- A tool plan (names, schemas, annotations, outputs)
16- An upstream starting-point recommendation (official example, ext-apps example, or local fallback scaffold)
17- An MCP server scaffold (resource registration, tool handlers, metadata)
18- A widget scaffold (MCP Apps bridge first, `window.openai` compatibility/extensions second)
19- A reusable Node + `@modelcontextprotocol/ext-apps` starter scaffold for low-dependency fallbacks
20- A validation report against the minimum working repo contract
21- Local dev and connector setup steps
22- A short stakeholder summary of what the app does (when requested)
23
24## When to Use
25
26Use this skill when the task involves any of the following:
27
28- Scaffolding a new ChatGPT App (MCP server + widget UI)
29- Planning tool surfaces for an Apps SDK project
30- Adapting an official OpenAI example or `@modelcontextprotocol/ext-apps` example into a ChatGPT app
31- Refactoring an existing Apps SDK demo into a production-ready structure
32- Wiring the MCP Apps bridge (`postMessage` JSON-RPC) or `window.openai` compatibility APIs
33- Applying Apps SDK metadata, CSP allowlists, domain settings, or URI versioning
34- Preparing an app for local ChatGPT Developer Mode testing or public submission
35- Troubleshooting tool descriptors, widget rendering, or connector setup
36
37**Trigger keywords:** ChatGPT app, Apps SDK, MCP server, widget, ext-apps, connector, scaffold, tool plan, submission, `window.openai`, MCP Apps bridge, structuredContent, resourceUri.
38
39## Prerequisites
40
41- **Docs-first workflow is mandatory.** Use `$openai-docs` first whenever building or changing a ChatGPT Apps SDK app. If `$openai-docs` is unavailable, use `mcp__openaiDeveloperDocs__search_openai_docs` and `mcp__openaiDeveloperDocs__fetch_openai_doc`.
42- **Node.js** installed for the fallback scaffold script (`scripts/scaffold_node_ext_apps.mjs`).
43- **PowerShell** as the primary shell on Windows host. All commands below assume PowerShell unless noted.
44- **ngrok** (or equivalent HTTPS tunnel) for local ChatGPT Developer Mode testing.
45- **MCP Inspector** for runtime validation of tool descriptors and widget rendering.
46- **OpenAI Platform dashboard access** for submission tasks (org verification + Owner role).
47
48## Procedure
49
50### 0. Classify the App Archetype
51
52Before choosing examples, repo shape, or scaffolds, classify the request into one primary archetype and state it explicitly.
53
54- `tool-only` — no UI; MCP server with tools only
55- `vanilla-widget` — minimal HTML widget via MCP Apps bridge
56- `react-widget` — React widget using `@modelcontextprotocol/ext-apps/react` or similar
57- `interactive-decoupled` — decoupled data/render tools with stateful widget
58- `submission-ready` — public directory launch target
59
60**Rules:**
61- Prefer a single primary archetype instead of mixing several.
62- If the request is broad, infer the smallest archetype that can still satisfy it.
63- Escalate to `submission-ready` only when the user asks for public launch, directory submission, or review-ready deployment.
64- Call out the chosen archetype in your response so the user can correct it early.
65
66**Load `references/app-archetypes.md`** for the full decision rubric.
67
68### 1. Plan Tools Before Code
69
70Define the tool surface area from user intents before writing any implementation code.
71
721. Use one job per tool.
732. Write tool descriptions that start with "Use this when..." behavior cues.
743. Make inputs explicit and machine-friendly (enums, required fields, bounds).
754. Decide whether each tool is data-only, render-only, or both.
765. Set annotations accurately: `readOnlyHint`, `destructiveHint`, `openWorldHint`; add `idempotentHint` when true.
776. If the app is connector-like, data-only, sync-oriented, or intended for company knowledge or deep research, default to the standard `search` and `fetch` tools instead of inventing custom read-only equivalents.
787. For educational/demo apps, prefer one concept per tool so the model can pick the right example cleanly.
798. Group demo tools by learning objective: data into the widget, widget actions back into the conversation or tools, host/layout environment signals, and lifecycle/streaming behavior.
80
81**Load `references/search-fetch-standard.md`** when `search` and `fetch` may be relevant.
82
83### 2. Choose an App Architecture
84
85Choose the simplest structure that fits the goal.
86
87- **Minimal demo pattern** — for quick prototypes, workshops, or proofs of concept.
88- **Decoupled data/render pattern** — for production UX so the widget does not re-render on every tool call.
89
90Prefer the decoupled pattern for non-trivial apps:
91- Data tools return reusable `structuredContent`.
92- Render tools attach `_meta.ui.resourceUri` and optional `_meta["openai/outputTemplate"]`.
93- Render tool descriptions state prerequisites (for example, "Call `search` first").
94
95### 2a. Start From an Upstream Example When One Fits
96
97Default to upstream examples for greenfield work when they are close to the requested app.
98
991. Check the **official OpenAI examples** first for ChatGPT-facing apps, polished UI patterns, React components, file upload flows, modal flows, or apps that resemble the docs examples.
1002. Use **`@modelcontextprotocol/ext-apps` examples** when the request is closer to raw MCP Apps bridge/server wiring, or when version-matched package patterns matter more than ChatGPT-specific polish.
1013. Pick the smallest matching example and copy only the relevant files; do not transplant an entire showcase app unchanged.
1024. After copying, reconcile the example with the current docs you fetched: tool names/descriptions, annotations, `_meta.ui.*`, CSP, URI versioning, and local run instructions.
1035. State which example you chose and why in one sentence.
104
105**Load `references/upstream-example-workflow.md`** for the selection and adaptation rubric.
106
107### 2b. Use the Starter Script When a Low-Dependency Fallback Helps
108
109Use `scripts/scaffold_node_ext_apps.mjs` only when:
110- The user wants a quick, greenfield Node starter
111- A vanilla HTML widget is acceptable
112- No upstream example is a better starting point
113
114**Skip the script when:**
115- A close official example exists
116- The user already has an existing app structure
117- They need a non-Node stack
118- They explicitly want React first
119- They only want a plan/review instead of code
120
121**To run the fallback scaffold (PowerShell):**
122
123```powershell
124node scripts/scaffold_node_ext_apps.mjs
125```
126
127After running it:
128- Patch the generated output to match the current docs and the user request: adjust tool names/descriptions, annotations, resource metadata, URI versioning, and README/run instructions.
129- The generated widget keeps follow-up messaging on the standard `ui/message` bridge and only uses `window.openai` for optional host signals/extensions.
130- If you choose the script instead of an upstream example, say why the fallback is better for that request.
131
132### 3. Scaffold the MCP Server
133
134Generate a server that:
135
1361. Registers a widget resource/template with the MCP Apps UI MIME type (`text/html;profile=mcp-app`) or the SDK constant (`RESOURCE_MIME_TYPE`) when using `@modelcontextprotocol/ext-apps/server`.
1372. Registers tools with clear names, schemas, titles, and descriptions.
1383. Returns `structuredContent` (model + widget), `content` (model narration), and `_meta` (widget-only data) intentionally.
1394. Keeps handlers idempotent or documents non-idempotent behavior explicitly.
1405. Includes tool status strings (`openai/toolInvocation/*`) when helpful in ChatGPT.
141
142**Keep `structuredContent` concise.** Move large or sensitive widget-only payloads to `_meta`.
143
144### 4. Scaffold the Widget UI
145
146Use the MCP Apps bridge first for portability, then add ChatGPT-specific `window.openai` APIs when they materially improve UX.
147
1481. Listen for `ui/notifications/tool-result` (JSON-RPC over `postMessage`).
1492. Render from `structuredContent`.
1503. Use `tools/call` for component-initiated tool calls.
1514. Use `ui/update-model-context` only when UI state should change what the model sees.
152
153Use `window.openai` for compatibility and extensions (file upload, modal, display mode, etc.), not as the only integration path for new apps.
154
155#### API Surface Guardrails
156
157- Some examples wrap the bridge with an `app` object (for example, `@modelcontextprotocol/ext-apps/react`) and expose helper names like `app.sendMessage()`, `app.callServerTool()`, `app.openLink()`, or host getter methods.
158- Treat those wrappers as implementation details or convenience layers, not the canonical public API to teach by default.
159- For ChatGPT-facing guidance, prefer the current documented surface:
160 - `window.openai.callTool(...)`
161 - `window.openai.sendFollowUpMessage(...)`
162 - `window.openai.openExternal(...)`
163 - `window.openai.requestDisplayMode(...)`
164 - Direct globals: `window.openai.theme`, `window.openai.locale`, `window.openai.displayMode`, `window.openai.toolInput`, `window.openai.toolOutput`, `window.openai.toolResponseMetadata`, `window.openai.widgetState`
165- If you reference wrapper helpers from repo examples, map them back to the documented `window.openai` or MCP Apps bridge primitives and call out that the wrapper is not the normative API surface.
166
167**Load `references/window-openai-patterns.md`** for the wrapper-to-canonical mapping and for React helper extraction patterns.
168
169### 5. Add Resource Metadata and Security
170
171Set resource metadata deliberately on the widget resource/template:
172
173- `_meta.ui.csp` with exact `connectDomains` and `resourceDomains`
174- `_meta.ui.domain` for app submission-ready deployments
175- `_meta.ui.prefersBorder` (or OpenAI compatibility alias when needed)
176- Optional `openai/widgetDescription` to reduce redundant narration
177
178Avoid `frameDomains` unless iframe embeds are core to the product.
179
180### 5a. Enforce a Minimum Working Repo Contract
181
182Every generated repo should satisfy a small, stable contract before you consider it done:
183
184- The repo shape matches the chosen archetype.
185- The MCP server and tools are wired to a reachable `/mcp` endpoint.
186- Tools have clear descriptions, accurate annotations, and UI metadata where needed.
187- Connector-like, data-only, sync-oriented, and company-knowledge-style apps use the standard `search` and `fetch` tool shapes when relevant.
188- The widget uses the MCP Apps bridge correctly when a UI exists.
189- The repo includes enough scripts or commands for a user to run and check it locally.
190- The response explicitly says what validation was run and what was not run.
191
192**Load `references/repo-contract-and-validation.md`** for the detailed checklist and validation ladder.
193
194### 6. Validate the Local Loop
195
196Validate against the minimum working repo contract, not just "did files get created."
197
198**Lowest-cost checks first:**
1991. Static contract review
2002. Syntax or compile checks when feasible
2013. Local `/mcp` health check when feasible
202
203**Then move up to runtime checks:**
2044. Verify tool descriptors and widget rendering in MCP Inspector
2055. Test the app in ChatGPT developer mode through HTTPS tunneling
2066. Exercise retries and repeated tool calls to confirm idempotent behavior
2077. Check widget updates after host events and follow-up tool calls
208
209If you are only delivering a scaffold and are not installing dependencies, still run low-cost checks and say exactly what you did not run.
210
211**Load `references/repo-contract-and-validation.md`** for the validation ladder.
212
213### 7. Connect and Test in ChatGPT (Developer Mode)
214
215For local development, include explicit ChatGPT setup steps:
216
2171. Run the MCP server locally on `http://localhost:<port>/mcp`.
2182. Expose the local server with a public HTTPS tunnel:
219
220 ```powershell
221 ngrok http <port>
222 ```
223
2243. Use the tunneled HTTPS URL plus `/mcp` path when connecting from ChatGPT.
2254. In ChatGPT, enable Developer Mode under **Settings → Apps & Connectors → Advanced settings**.
2265. In ChatGPT app settings, create a new app for the remote MCP server and paste the public MCP URL.
2276. Tell users to refresh the app after MCP tool/metadata changes so ChatGPT reloads the latest descriptors.
228
229> **Note:** Some docs/screenshots still use older "connector" terminology. Prefer current product wording ("app") while acknowledging both labels when giving step-by-step instructions.
230
231### 8. Plan Production Hosting and Deployment
232
233When the user asks to deploy or prepare for launch, generate hosting guidance for the MCP server (and widget assets if hosted separately):
234
2351. Host behind a stable public HTTPS endpoint (not a tunnel) with dependable TLS.
2362. Preserve low-latency streaming behavior on `/mcp`.
2373. Configure secrets outside the repo (environment variables / secret manager).
2384. Add logging, request latency tracking, and error visibility for tool calls.
2395. Add basic observability (CPU, memory, request volume) and a troubleshooting path.
2406. Re-test the hosted endpoint in ChatGPT Developer Mode before submission.
241
242### 9. Prepare Submission and Publish (Public Apps Only)
243
244Only include these steps when the user intends a public directory listing:
245
2461. Use `apps-sdk/deploy/submission` for the submission flow and `apps-sdk/app-submission-guidelines` for review requirements.
2472. Keep private/internal apps in Developer Mode instead of submitting.
2483. Confirm org verification and Owner-role prerequisites before submission work.
2494. Ensure the MCP server uses a public production endpoint (no localhost/testing URLs) and has submission-ready CSP configured.
2505. Prepare submission artifacts: app metadata, logo/screenshots, privacy policy URL, support contact, test prompts/responses, localization info.
2516. If auth is required, include review-safe demo credentials and test the login path end-to-end.
2527. Submit for review in the Platform dashboard, monitor review status, and publish only after approval.
253
254## Mandatory Docs-First Workflow
255
256Use `$openai-docs` first whenever building or changing a ChatGPT Apps SDK app.
257
2581. Invoke `$openai-docs` (preferred) or call the OpenAI docs MCP server directly.
2592. Fetch current Apps SDK docs before writing code, especially (baseline pages):
260 - `apps-sdk/build/mcp-server`
261 - `apps-sdk/build/chatgpt-ui`
262 - `apps-sdk/build/examples`
263 - `apps-sdk/plan/tools`
264 - `apps-sdk/reference`
2653. Fetch `apps-sdk/quickstart` when scaffolding a new app or generating a first-pass implementation, and check the official examples repo/page before inventing a scaffold from scratch.
2664. Fetch deployment/submission docs when the task includes local ChatGPT testing, hosting, or public launch:
267 - `apps-sdk/deploy`
268 - `apps-sdk/deploy/submission`
269 - `apps-sdk/app-submission-guidelines`
2705. Cite the docs URLs you used when explaining design choices or generated scaffolds.
2716. Prefer current docs guidance over older repo patterns when they differ, and call out compatibility aliases explicitly.
2727. If doc search times out or returns poor matches, fetch the canonical Apps SDK pages directly by URL and continue; do not let search failure block scaffolding.
273
274If `$openai-docs` is unavailable, use:
275- `mcp__openaiDeveloperDocs__search_openai_docs`
276- `mcp__openaiDeveloperDocs__fetch_openai_doc`
277
278**Load `references/apps-sdk-docs-workflow.md`** for suggested doc queries and a compact checklist.
279
280## Prompt Guidance
281
282Use prompts that explicitly pair this skill with `$openai-docs` so the resulting scaffold is grounded in current docs.
283
284Preferred prompt patterns:
285
286- `Use $build-chatgpt-app with $openai-docs to scaffold a ChatGPT app for <use case> with a <TS/Python> MCP server and <React/vanilla> widget.`
287- `Use $build-chatgpt-app with $openai-docs to adapt the closest official Apps SDK example into a ChatGPT app for <use case>.`
288- `Use $build-chatgpt-app and $openai-docs to refactor this Apps SDK demo into a production-ready structure with tool annotations, CSP, and URI versioning.`
289- `Use $build-chatgpt-app with $openai-docs to plan tools first, then generate the MCP server and widget code.`
290
291When responding, ask for or infer these inputs before coding:
292
293- Use case and primary user flows
294- Read-only vs mutating tools
295- Demo vs production target
296- Private/internal use vs public directory submission
297- Backend language and UI stack
298- Auth requirements
299- External API domains for CSP allowlists
300- Hosting target and local dev approach
301- Org ownership/verification readiness (for submission tasks)
302
303## Interactive State Guidance
304
305**Load `references/interactive-state-sync-patterns.md`** when the app has long-lived widget state, repeated interactions, or component-initiated tool calls (for example, games, boards, maps, dashboards, editors).
306
307Use it to choose patterns for:
308
309- State snapshots plus monotonic event tokens (`stateVersion`, `resetCount`, etc.)
310- Idempotent retry-safe handlers
311- `structuredContent` vs `_meta` partitioning
312- MCP Apps bridge-first update flows with optional `window.openai` compatibility
313- Decoupled data/render tool architecture for more complex interactive apps
314
315## Default Starting-Point Order
316
317For greenfield apps, prefer these starting points in order:
318
3191. **Official OpenAI examples** when a close example already matches the requested stack or interaction pattern.
3202. **Version-matched `@modelcontextprotocol/ext-apps` examples** when the user needs a lower-level or more portable MCP Apps baseline.
3213. **`scripts/scaffold_node_ext_apps.mjs`** only when no close example fits, the user wants a tiny Node + vanilla starter, or network access/example retrieval is undesirable.
322
323Do not generate a large custom scaffold from scratch if a close upstream example already exists. Copy the smallest matching example, remove unrelated demo code, then patch it to the current docs and the user request.
324
325## Output Expectations
326
327When using this skill to scaffold code, produce output in this order unless the user asks otherwise:
328
3291. Primary app archetype chosen and why
3302. Tool plan and architecture choice (minimal vs decoupled)
3313. Upstream starting point chosen (official example, ext-apps example, or local fallback scaffold) and why
3324. Doc pages/URLs used from `$openai-docs`
3335. File tree to create or modify
3346. Implementation (server + widget)
3357. Validation performed against the minimum working repo contract
3368. Local run/test instructions (including tunnel + ChatGPT Developer Mode app setup)
3379. Deployment/hosting guidance (if requested or implied)
33810. Submission-readiness checklist (for public launch requests)
33911. Risks, gaps, and follow-up improvements
340
341For direct scaffold requests, do not stop at the plan: give the brief plan, then create the files immediately.
342
343## Pitfalls
344
345- **Do not invent APIs or CLI flags.** Only use documented `window.openai.*` methods and MCP Apps bridge primitives. Wrapper helpers (`app.sendMessage()`, `app.callServerTool()`, etc.) are implementation details, not the canonical public API.
346- **Do not skip the docs-first workflow.** Always fetch current Apps SDK docs before writing code. If search times out, fetch canonical pages directly by URL.
347- **Do not generate a large custom scaffold from scratch if a close upstream example exists.** Copy the smallest matching example, remove unrelated demo code, then patch.
348- **Do not use `frameDomains` unless iframe embeds are core to the product.**
349- **Do not put large or sensitive widget-only payloads in `structuredContent`.** Move them to `_meta`.
350- **Do not use `ui/update-model-context` casually.** Only when UI state should change what the model sees.
351- **Do not submit private/internal apps to the public directory.** Keep them in Developer Mode.
352- **Do not use localhost/testing URLs for submission.** The MCP server must use a public production endpoint.
353- **Do not let doc search failure block scaffolding.** Fetch canonical pages directly by URL and continue.
354- **Do not mix multiple archetypes.** Pick one primary archetype; escalate to `submission-ready` only for public launch.
355- **Do not forget to tell users to refresh the app** after MCP tool/metadata changes so ChatGPT reloads the latest descriptors.
356- **Do not drop the "what validation was run and what was not run" statement** — always include it.
357
358## Verification
359
360### Static Contract Review
361
362Check the generated repo against the minimum working repo contract:
363
364```powershell
365# Verify repo structure matches archetype
366Get-ChildItem -Recurse -Name | Select-String -Pattern "server|web|widget|mcp"
367```
368
369### Syntax / Compile Checks
370
371```powershell
372# TypeScript compile check (if applicable)
373npx tsc --noEmit
374
375# Node syntax check for server entry
376node --check server/index.ts
377```
378
379### Local /mcp Health Check
380
381```powershell
382# Start the server
383npm run dev
384
385# In another terminal, verify the /mcp endpoint responds
386curl http://localhost:<port>/mcp
387```
388
389### MCP Inspector Validation
390
391```powershell
392# Launch MCP Inspector against the local server
393npx @modelcontextprotocol/inspector node server/index.ts
394```
395
396Verify:
397- Tool descriptors appear with correct names, schemas, and annotations
398- Widget resource/template is registered with `text/html;profile=mcp-app` MIME type
399- `structuredContent` and `_meta` are returned correctly
400
401### ChatGPT Developer Mode Test
402
403```powershell
404# Tunnel the local server
405ngrok http <port>
406```
407
408Then in ChatGPT:
4091. Settings → Apps & Connectors → Advanced settings → enable Developer Mode
4102. Create a new app, paste the tunneled HTTPS URL + `/mcp`
4113. Exercise each tool and confirm widget rendering
4124. Test repeated tool calls for idempotent behavior
4135. Check widget updates after host events and follow-up tool calls
414
415### Submission Readiness Checklist (Public Apps Only)
416
417- [ ] Public production HTTPS endpoint (no localhost/tunnel)
418- [ ] Submission-ready CSP configured (`_meta.ui.csp` with exact `connectDomains` and `resourceDomains`)
419- [ ] `_meta.ui.domain` set for app submission-ready deployments
420- [ ] Org verification and Owner-role confirmed
421- [ ] Submission artifacts prepared (metadata, logo/screenshots, privacy policy URL, support contact, test prompts/responses, localization info)
422- [ ] If auth required: review-safe demo credentials included and login path tested end-to-end
423- [ ] Submitted for review in Platform dashboard; publish only after approval
424
425## References
426
427| File | When to Load |
428|------|-------------|
429| `references/app-archetypes.md` | Classifying requests into supported app shapes before choosing examples or scaffolds |
430| `references/apps-sdk-docs-workflow.md` | Doc queries, page targets, and code-generation checklist for the docs-first workflow |
431| `references/interactive-state-sync-patterns.md` | Long-lived widget state, repeated interactions, or component-initiated tool calls (games, boards, maps, dashboards, editors) |
432| `references/repo-contract-and-validation.md` | Generating or reviewing a repo against the minimum working repo contract and validation ladder |
433| `references/search-fetch-standard.md` | Connector-like, data-only, sync-oriented apps, or apps meant to work with company knowledge or deep research |
434| `references/upstream-example-workflow.md` | Starting a greenfield app or deciding whether to adapt an upstream example or use the local fallback scaffold |
435| `references/window-openai-patterns.md` | ChatGPT-specific widget behavior or translating repo examples that use wrapper-specific `app.*` helpers |
436| `scripts/scaffold_node_ext_apps.mjs` | Minimal Node + `@modelcontextprotocol/ext-apps` fallback starter scaffold (vanilla HTML widget) |