Skip to main content

MCP Apps vs the OpenAI Apps SDK

Short answer

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.

Tested withMCP Apps spec 2026-01-26ext-apps (@modelcontextprotocol/ext-apps) 2.0.3@modelcontextprotocol/server 2.3.1MCP Inspector 2.11.0Checked 20 Oct 2026

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 AppsOpenAI Apps SDK (apps in ChatGPT)
What it isAn open extension to MCP, identified as io.modelcontextprotocol/uiOpenAI's developer docs for building MCP-based apps that run in ChatGPT, plus ChatGPT-specific features
StatusStable, spec revision 2026-01-26Documented by OpenAI. ChatGPT implements the MCP Apps standard
Where the view runsAny host that implements the extensionChatGPT, 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 typetext/html;profile=mcp-apptext/html;profile=mcp-app. Older Apps SDK builds used text/html+skybridge
View-to-host callsui/* JSON-RPC over postMessageThe 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:

GoalMCP Apps standardChatGPT alias
Link a tool to a UI resource_meta.ui.resourceUri_meta["openai/outputTemplate"]
Receive tool inputui/initialize and ui/notifications/tool-inputwindow.openai.toolInput
Receive tool resultsui/notifications/tool-resultwindow.openai.toolOutput
Call a tool from the viewtools/callwindow.openai.callTool
Send a follow-up messageui/messagewindow.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.requestCheckout opens 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, selectFiles and getFileDownloadUrl, plus _meta["openai/fileParams"] to let ChatGPT pass files to a tool.
  • Modals and close. window.openai.requestModal and requestClose.
  • Widget state. window.openai.widgetState and setWidgetState. The shared way to tell the model about a selection is ui/update-model-context.
  • Tool status text. _meta["openai/toolInvocation/invoking"] and invoked.
  • 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​

  1. 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.
  2. Does it need a ChatGPT-only feature? Checkout, the file library, modals or widgetState need window.openai. Use it for those parts only.
  3. Are you submitting to ChatGPT? Then you need the public HTTPS endpoint, the _meta.ui.domain value and the review steps in OpenAI's docs.
  4. 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-apps released 2.0.0 on September 8 and 2.0.3 on September 25. The 2.x line uses the 2.x @modelcontextprotocol/server package.
  • 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.

  1. Declare the view with the standard keys. Use registerAppTool and registerAppResource from the ext-apps server helpers. They set the MIME type and the _meta.ui fields for you.
  2. Keep the tools useful without the view. OpenAI recommends this so the model can finish the task in clients that don't draw UI.
  3. Add window.openai features 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" });
}
  1. 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.

src/index.ts
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.