OpenAI Apps SDK, plainly
Five-minute orientation to the OpenAI Apps SDK — what it is (a framework for building apps that run inside ChatGPT with rich UI), what it's not (Custom GPTs, GPT Actions, or the old plugins system), and how it uses MCP under the hood.
The thirty-second version#
The OpenAI Apps SDK lets you build apps that run inside ChatGPT — full-stack experiences with custom UI, auth, and network access that users interact with through a conversation. Under the hood: every app is an MCP server, plus a UI bundle that ChatGPT renders in a sandboxed iframe alongside the chat.
Three things to know:
- It’s MCP-native. Apps SDK was the early implementation of what’s now the open MCP Apps standard. Build once, run across MCP-Apps-compatible hosts.
- It’s different from Custom GPTs and the old plugins. Apps SDK is code-first, generates rich custom UI, and ships as MCP servers. §GPT.1 Custom GPTs are no-code chat assistants. The original 2023 ChatGPT Plugins system was deprecated in 2024.
- Distribution is gated today. Public apps go through OpenAI submission review. Approved apps land in the ChatGPT apps store, and OpenAI also creates a plugin version for Codex distribution. Self-serve publishing is on the roadmap (as of 15 May 2026).
The three components of a ChatGPT App#
Per the official docs, every Apps SDK app has three parts:
| Component | What it does |
|---|---|
| MCP server | Defines tools, enforces auth, returns structured data, points each tool to a UI bundle. |
| Widget / UI bundle | Renders inside ChatGPT’s iframe. Talks to the host via the MCP Apps bridge (JSON-RPC over postMessage). |
| The model | Decides when to call your tools, narrates the experience using the structured data your server returns. |
You build the first two. ChatGPT supplies the third and decides when your tools fire based on the metadata you provide.
The conversation loop#
User prompt
↓
ChatGPT model ──► tools/call ──► Your MCP server ──► Tool response
│ (structuredContent + _meta + content)
│ │
└────── narrates ◄──── widget iframe ◄──────────────────┘
(HTML template + MCP Apps bridge)
- User asks something.
- ChatGPT model decides one of your tools fits, calls it via
tools/call. - Your server runs the handler, returns
structuredContent(data the model + widget can read),_meta(UI metadata, CSP, domain hints), andcontent(the model-narrated summary). - ChatGPT loads the HTML template linked in the tool descriptor (served as
text/html;profile=mcp-app) and delivers tool inputs/results to the iframe over the MCP Apps bridge. - Widget renders. Model reads
structuredContentand narrates.
The widget can also call tools itself (tools/call), post follow-up messages (ui/message), and update context the model sees (ui/update-model-context).
A real example — a Kanban app#
You build:
- An MCP server (Node/Python) with one tool
kanban-boardthat returns the columns + tasks for a workspace. - A widget (React + Vite) bundled as
web/dist/kanban.{js,css}that renders a draggable Kanban board.
The server registers the widget as an MCP resource:
import {
registerAppResource,
RESOURCE_MIME_TYPE, // = "text/html;profile=mcp-app"
} from "@modelcontextprotocol/ext-apps/server";
registerAppResource(server, "kanban-widget", "ui://widget/kanban-board.html", {}, async () => ({
contents: [{
uri: "ui://widget/kanban-board.html",
mimeType: RESOURCE_MIME_TYPE,
text: `<div id="kanban-root"></div><style>${CSS}</style><script type="module">${JS}</script>`,
_meta: {
ui: {
prefersBorder: true,
domain: "https://kanban.example.com",
csp: {
connectDomains: ["https://api.kanban.example.com"],
resourceDomains: ["https://*.oaistatic.com"],
},
},
},
}],
}));
Then the tool itself:
registerAppTool(server, "kanban-board", {
title: "Show Kanban Board",
inputSchema: { workspace: z.string() },
outputSchema: { columns: z.array(/* ... */) },
_meta: { ui: { resourceUri: "ui://widget/kanban-board.html" } },
}, async ({ workspace }) => {
const columns = await fetchKanban(workspace);
return { structuredContent: { columns } };
});
A user asks ChatGPT “Show my Kanban.” The model decides kanban-board fits, calls the tool with { workspace: "..." }. Your server returns the columns; ChatGPT renders the widget inline, and the model narrates (“Here’s your board — 3 columns, 7 tasks”).
Why this matters#
The Apps SDK is OpenAI’s bet on conversations as the front end — instead of users opening a separate web app, the app appears inside their chat. Three implications:
- Distribution is OpenAI-owned. Your app lives in the ChatGPT apps store. Discovery, auth, and trust live with OpenAI. Good for reach; bad for control.
- The UX surface is constrained. Widgets render in a sandboxed iframe with a CSP. You declare
connectDomains,resourceDomains, optionalframeDomains. Browsers without these guarantees aren’t supported. - MCP is the wire format. If you’ve already built an MCP server (for Claude, for Cursor, for VS Code), most of the work is reusable. You add
text/html;profile=mcp-appresources and tool_meta.uihints.
How it compares#
| Apps SDK | Custom GPTs | GPT Actions | Original Plugins (deprecated) | |
|---|---|---|---|---|
| Shape | MCP server + widget | No-code editor | OpenAPI spec | OpenAPI spec |
| UI | Rich, custom HTML/JS | Chat-only | Chat-only | Chat-only |
| Code required | Yes | No | Optional | Yes |
| Distribution | ChatGPT apps store + Codex plugins | GPT Store | Tied to a Custom GPT | Removed 2024 |
| Auth | Yours (via MCP) | OpenAI handles | OAuth, API key | OAuth |
| Tier required | Apps store review (no user tier) | ChatGPT (Free can use public GPTs; building requires Plus / Team / Enterprise) | ChatGPT (Free can use public GPTs that include Actions; building requires Plus / Team / Enterprise) | n/a |
Sush’s honest take (sourced, not yet tried): the MCP standardisation is the most interesting bit. The same protocol that powers Claude’s tool catalogue powers ChatGPT’s apps. The cross-vendor portability is real at the protocol level — Apps SDK builds on the open MCP Apps standard — though today ChatGPT is the host with the most-complete bridge implementation, so portability claims should be tested case-by-case. The trade-off: distribution still flows through OpenAI’s review process.
What to do next#
- §APPS.2 MCP plugin for ChatGPT — how an MCP server becomes a ChatGPT-callable plugin
- §APPS.3 Widget rendering with
text/html;profile=mcp-app— the iframe contract, CSP rules, JSON-RPC bridge - Examples repo — start from a working app