Type "Spotify, make a playlist for my party this Friday" into ChatGPT and something new happens: a Spotify surface renders inline, inside the conversation, and starts working with the context of what you just said. Type nothing about real estate and mention you're house-hunting, and ChatGPT may offer to pull up Zillow on its own. That's the pitch OpenAI made at DevDay on October 6, 2025, when it shipped apps in ChatGPT and the Apps SDK as a preview.
The marketing framing is "chat with apps." The engineering reality is more specific, and more interesting if you've been paying attention to protocols: the Apps SDK is the Model Context Protocol (MCP) with a UI layer welded on and a distribution channel attached. If you already run an MCP server, you are closer to shipping a ChatGPT app than you might think — and the parts that are genuinely new are worth understanding before you start.
It's MCP, plus two things
MCP is the open standard for connecting an LLM client to external tools and data. A server advertises tools with JSON Schema input/output contracts, the model calls them mid-conversation, and results flow back as structured content. None of that changes here. The Apps SDK is open source and built directly on MCP, which is why OpenAI can claim apps built with it "run anywhere that adopts this standard."
What the Apps SDK adds is two things a plain MCP tool server doesn't have:
- A component layer. A tool can point at an embedded resource — HTML — that ChatGPT renders as an interactive widget right in the chat. Your server now defines both the logic and the interface.
- A host that owns discovery. Users don't install your app and then call it. ChatGPT decides when to surface it: by name when a message starts with it, or as a suggestion when the conversation makes it relevant.
That second point is the mental shift. Your server stops being a passive tool endpoint and becomes the backend and frontend of an app whose entry point you no longer control.
What your server exposes
Concretely, an MCP server for a ChatGPT app exposes three things: tools (the callable contracts), structured content (the JSON a tool returns), and components (optional embedded HTML resources that represent the UI to render). Transport is Streamable HTTP — the recommended option over plain SSE — and auth rides on MCP's OAuth 2.1 support, so you're not inventing a session model.
The component is registered as a resource with a ui:// URI and a distinctive MIME type. The tool then references that resource in its metadata, and the returned data hydrates the widget in the browser through a window.openai bridge:
// 1. Register the UI as an MCP resource
server.registerResource("kanban-widget", "ui://widget/kanban.html", {}, async () => ({
contents: [{
uri: "ui://widget/kanban.html",
mimeType: "text/html+skybridge",
text: `<div id="root"></div><script type="module" src="https://app.example.com/widget.js"></script>`,
}],
}));
// 2. Bind a tool to that component
server.registerTool("list_tasks",
{
title: "List tasks",
inputSchema: { project: z.string() },
_meta: { "openai/outputTemplate": "ui://widget/kanban.html" },
},
async ({ project }) => ({
structuredContent: { tasks: await db.summarize(project) }, // model reads this + widget hydrates
content: [{ type: "text", text: `Loaded tasks for ${project}.` }],
_meta: { rows: await db.fullRows(project) }, // widget-only, hidden from the model
}));
In the browser, the widget reads window.openai.toolOutput to get that structured content and renders. The HTML runs inside a sandbox OpenAI calls Skybridge — hence the text/html+skybridge MIME type — which is why the metadata carries a domain and a CSP allowlist for the network calls your widget is permitted to make.
The three-way data split that trips people up
Look again at the tool's return value. It has three sibling fields, and getting the split wrong is the most common way to build an app that either leaks or misbehaves:
structuredContent — concise JSON the widget uses and the model reads. This is shared context.
content — optional plain-text or Markdown narration the model can speak back.
_meta — data forwarded to the component but never shown to the model.
The LLM gets conversational context. Your widget gets everything.
This matters for two reasons. One is token budget: a table with 500 rows doesn't belong in structuredContent, where it burns the model's context on every turn — it belongs in _meta, hydrating the widget directly. The other is privacy: anything you don't want the model reasoning over, repeating, or logging must ride in _meta, not structuredContent. There is no fourth option, so you have to decide, per field, who is allowed to see it. Treat that boundary as a security surface, not a formatting choice.
What you give up, and what it costs
The reach is real. Apps launched to logged-in users on Free, Go, Plus, and Pro plans — everywhere except the EEA, Switzerland, and the UK — with pilot partners including Booking.com, Canva, Coursera, Expedia, Figma, Spotify, and Zillow. That's a distribution surface most standalone products never touch.
The trade is control. You don't own the launch button; the host's ranking decides whether your app appears, which makes your tool descriptions and metadata a discovery-ranking problem, not just a correctness one. Monetization isn't in your hands yet either — OpenAI has signposted it through a forthcoming Agentic Commerce Protocol, a separate open standard for instant checkout inside the chat, rather than shipping billing in the SDK on day one. And a submission and review process gates what reaches everyone, so "it runs on my machine in developer mode" is the start of the road, not the end of it.
The takeaway
If you maintain an MCP server today, the move this week is small and concrete: pick your single most visual tool — one whose output is a list, a map, a preview, a chart — and give it a component. Register an HTML resource under a ui:// URI, bind it with openai/outputTemplate, and split your return value deliberately: shared summary into structuredContent, bulk and sensitive data into _meta. That one tool teaches you the whole platform. Everything else — auth, review, monetization, discovery ranking — is scaffolding around that data boundary, and the sooner you internalize which of your three fields the model can read, the fewer of your users' rows end up somewhere you didn't intend.
Sources: Introducing apps in ChatGPT and the new Apps SDK — OpenAI, How Apps SDK uses MCP — OpenAI Developers, Build your MCP server — OpenAI Developers