MCP Apps vs the OpenAI Apps SDK
MCP Apps is the open standard for interactive UI that an MCP server returns. The OpenAI Apps SDK is how you build apps for ChatGPT, and ChatGPT implements the MCP Apps standard, so the server side and the ui/* bridge are the same in both. What's ChatGPT-only is the window.openai object, the openai/* metadata, the file and checkout APIs and the submission process. Build on the MCP Apps keys first, then add window.openai calls behind feature checks.
How they compare
Both use an MCP server for tools and the UI resource. The differences are in what the view can do and where it runs.
| MCP Apps | OpenAI Apps SDK (apps in ChatGPT) | |
|---|---|---|
| What it is | An open extension to MCP, identified as io.modelcontextprotocol/ui | OpenAI's developer docs for building MCP-based apps that run in ChatGPT, plus ChatGPT-specific features |
| Status | Stable, spec revision 2026-01-26 | Documented by OpenAI. ChatGPT implements the MCP Apps standard |
| Where the view runs | Any host that implements the extension | ChatGPT, and other MCP Apps hosts for the shared parts |
| Tool-to-view link | _meta.ui.resourceUri | _meta.ui.resourceUri, plus _meta["openai/outputTemplate"] as a ChatGPT alias |
| View MIME type | text/html;profile=mcp-app | text/html;profile=mcp-app. Older Apps SDK builds used text/html+skybridge |
| View-to-host calls | ui/* JSON-RPC over postMessage | The same bridge, plus window.openai |
The MCP Apps overview lists Claude, Claude Desktop, VS Code GitHub Copilot, Microsoft 365 Copilot, Goose, Postman, MCPJam and Archestra.AI as clients that support it. OpenAI's docs add ChatGPT to that list. The host compatibility guide tracks which hosts do what.
What's shared
OpenAI's docs say to use the MCP Apps field or method whenever the shared spec covers the job, and to use the window.openai alias only for the rest. This is the mapping they publish:
| Goal | MCP Apps standard | ChatGPT alias |
|---|---|---|
| Link a tool to a UI resource | _meta.ui.resourceUri | _meta["openai/outputTemplate"] |
| Receive tool input | ui/initialize and ui/notifications/tool-input | window.openai.toolInput |
| Receive tool results | ui/notifications/tool-result | window.openai.toolOutput |
| Call a tool from the view | tools/call | window.openai.callTool |
| Send a follow-up message | ui/message | window.openai.sendFollowUpMessage |
The ChatGPT aliases still work, so existing apps don't need to change. New code should use the standard column.
Visibility is shared too. _meta.ui.visibility defaults to ["model", "app"], so a tool can be hidden from the model and still callable by the view.
What's ChatGPT-only
These features are in window.openai or in ChatGPT's metadata. The MCP Apps migration reference marks several of them as not implemented in the MCP Apps SDK yet.
- Checkout.
window.openai.requestCheckoutopens ChatGPT's payment sheet. OpenAI currently limits checkout approval to physical-goods plugins, and the embedded payment sheet is in private beta. - File handling.
window.openai.uploadFile,selectFilesandgetFileDownloadUrl, plus_meta["openai/fileParams"]to let ChatGPT pass files to a tool. - Modals and close.
window.openai.requestModalandrequestClose. - Widget state.
window.openai.widgetStateandsetWidgetState. The shared way to tell the model about a selection isui/update-model-context. - Tool status text.
_meta["openai/toolInvocation/invoking"]andinvoked. - Submission rules. A plugin with UI needs a dedicated
_meta.ui.domain. A public submission needs a stable public HTTPS endpoint. Secure MCP Tunnel alone doesn't count.
The reverse also holds. MCP Apps has features that the Apps SDK doesn't document. These include _meta.ui.permissions for camera, microphone, geolocation and clipboard, ui/notifications/tool-input-partial for streaming arguments, and ui/notifications/tool-cancelled.
Questions that decide it
- Does the app need to run outside ChatGPT? If yes, build the view on the MCP Apps keys and the
ui/*bridge. Those run in every host that implements the standard. - Does it need a ChatGPT-only feature? Checkout, the file library, modals or
widgetStateneedwindow.openai. Use it for those parts only. - Are you submitting to ChatGPT? Then you need the public HTTPS endpoint, the
_meta.ui.domainvalue and the review steps in OpenAI's docs. - Are you porting an existing Apps SDK app? The MCP Apps repo has a migration reference with before and after code. Start there.
What changed recently
- November 2025. OpenAI launched the Apps SDK. The MCP Apps spec says OpenAI helped shape the standard from ChatGPT apps.
- January 2026. MCP Apps (SEP-1865) reached the Stable status, spec revision 2026-01-26.
- January 2026. The ext-apps repository opened an issue that maps the differences between the Apps SDK and MCP Apps. It is still open.
- September 2026.
@modelcontextprotocol/ext-appsreleased 2.0.0 on September 8 and 2.0.3 on September 25. The 2.x line uses the 2.x@modelcontextprotocol/serverpackage. - Still a draft. Pull request #172 would add an OpenAI transport to the MCP Apps SDK. It isn't in the 2.0.3 package, so don't rely on it yet.
OpenAI's examples still show the older @modelcontextprotocol/sdk package. That's the v1 line, and the current npm version is 1.32.1. The example below uses the current packages.
Target both
Build the view on the MCP Apps standard first, then add ChatGPT features where you need them.
- Declare the view with the standard keys. Use
registerAppToolandregisterAppResourcefrom the ext-apps server helpers. They set the MIME type and the_meta.uifields for you. - Keep the tools useful without the view. OpenAI recommends this so the model can finish the task in clients that don't draw UI.
- Add
window.openaifeatures behind a check. OpenAI recommends testing for the capability, not the host name:
const openai = typeof window !== "undefined" ? window.openai : undefined;
if (openai?.requestModal) {
await openai.requestModal({ template: "ui://dice/history.html" });
}
- Test the server and the view separately. The Inspector can check the server, as shown below. Check the view in ChatGPT and in at least one other MCP Apps host.
A dice tool that targets both
This server has one tool, roll_dice, and one view. It uses only the MCP Apps helpers, so it runs the same way in ChatGPT and other MCP Apps hosts.
import { McpServer } from "@modelcontextprotocol/server";
import { serveStdio } from "@modelcontextprotocol/server/stdio";
import {
RESOURCE_MIME_TYPE,
registerAppResource,
registerAppTool,
} from "@modelcontextprotocol/ext-apps/server";
import { z } from "zod";
const VIEW_URI = "ui://dice/view.html";
const VIEW_HTML = `<!doctype html>
<html>
<body>
<h1>Dice</h1>
<p>The roll appears here.</p>
</body>
</html>`;
function createServer(): McpServer {
const server = new McpServer({ name: "dice-app", version: "1.0.0" });
registerAppTool(
server,
"roll_dice",
{
title: "Roll dice",
description: "Roll an N-sided die and show the result in a view.",
inputSchema: z.object({
sides: z.number().int().min(2).max(100).describe("Number of sides on the die"),
}),
outputSchema: z.object({
sides: z.number().int(),
value: z.number().int(),
}),
annotations: {
readOnlyHint: true,
openWorldHint: false,
destructiveHint: false,
},
_meta: { ui: { resourceUri: VIEW_URI } },
},
async ({ sides }) => {
const value = 1 + Math.floor(Math.random() * sides);
return {
content: [{ type: "text", text: `Rolled ${value} on a ${sides}-sided die.` }],
structuredContent: { sides, value },
};
},
);
registerAppResource(
server,
"Dice view",
VIEW_URI,
{ description: "Shows the result of a dice roll." },
async () => ({
contents: [{ uri: VIEW_URI, mimeType: RESOURCE_MIME_TYPE, text: VIEW_HTML }],
}),
);
return server;
}
serveStdio(createServer, {
onerror: (error) => console.error("Dice server error:", error),
});
registerAppTool writes the tool's _meta.ui.resourceUri. It also adds the older ui/resourceUri key, which you don't need to write yourself. registerAppResource registers the view with the text/html;profile=mcp-app MIME type. The view in this example is a placeholder. Writing its script, which runs the ui/initialize handshake and renders ui/notifications/tool-result, is covered in Build an MCP App.
Install and build it:
cd typescript
npm install
npm run build
Then check the tool list. The _meta block is the part that links the tool to the view:
npx -y @modelcontextprotocol/inspector@2.11.0 --cli node build/index.js --protocol-era modern --method tools/list
"_meta": {
"ui": {
"resourceUri": "ui://dice/view.html"
},
"ui/resourceUri": "ui://dice/view.html"
}
Run the same command with --protocol-era legacy to check older clients too. Then call the tool with --method tools/call --tool-name roll_dice --tool-arg sides=6. The result comes back as structuredContent, which is what the view reads.
FAQ
Does an MCP App run in ChatGPT?
Yes. OpenAI says ChatGPT implements the MCP Apps standard, so a view built on the standard keys and the ui/* bridge can run in ChatGPT and other MCP Apps hosts. Test it in each host you target, because the hosts differ in which features they support.
Is the OpenAI Apps SDK a different protocol from MCP?
No. The server side is MCP, with the same tools, resources and transports. The Apps SDK adds ChatGPT-specific metadata, the window.openai object and submission rules on top of that.
Do I have to choose one?
No. Build the view on the MCP Apps keys and bridge, then add the ChatGPT features behind feature checks. One server then serves both, and the ChatGPT-only code is skipped in other hosts.
Can I convert an existing ChatGPT app to MCP Apps?
Yes. The ext-apps repository has a migration reference that maps the Apps SDK names to their MCP Apps equivalents. It also lists the features that don't have an MCP Apps equivalent yet.
- What are MCP AppsHow the ui:// resources, tool metadata and sandboxed iframe fit together.
- Build an MCP AppWrite the view script that renders the tool result.
- MCP Apps host compatibilityWhich hosts support which parts of MCP Apps.
- Connect MCP servers to ChatGPTAdd your server to ChatGPT so you can test the view there.
- Debug an MCP AppFix the common failures: CSP, sandbox, missing resources and MIME types.