MCP Apps: Interactive UI Inside Claude and ChatGPT

MCP Apps is the official MCP extension that lets a server show an interactive page inside the conversation: a chart, a form, a board. What it is, its status, which hosts render it, how the sandbox keeps it contained, and the smallest working shape.

8 min read

MCP Apps is an official extension to the Model Context Protocol that lets an MCP server show interactive UI inside a conversation. The server publishes an HTML page as a resource with a ui:// address and the media type text/html;profile=mcp-app, and a tool points at that page in its metadata. When the model calls the tool, a host that supports the extension renders the page in a sandboxed iframe, hands it the tool’s result, and lets it call the server’s tools as the person clicks. The specification is stable, dated 2026-01-26, under the identifier io.modelcontextprotocol/ui. Claude on the web, desktop and mobile supports it, as do ChatGPT, VS Code, Cursor, Microsoft 365 Copilot, Goose and others. So yes, Claude Desktop renders MCP Apps, both from connectors and from local servers in its config file.

This is the protocol view, across hosts. Building an app for ChatGPT specifically, with its developer mode, sign-in and directory review, is in the ChatGPT Apps SDK.

How an MCP App works

The MCP Apps overview (opens in a new tab) describes it as two MCP primitives and a bridge:

  • A tool whose definition carries _meta.ui.resourceUri, pointing at a ui:// resource. Because the pointer is in the tool list, a host can fetch and even preload the page before the tool is ever called.
  • A UI resource: one HTML document, usually with its script and styles bundled in, returned from resources/read with the MCP App media type.
  • A bridge between page and host: JSON-RPC over postMessage. The page opens with ui/initialize; the host sends it the tool’s arguments and result as ui/notifications/tool-input and ui/notifications/tool-result; the page can call server tools with tools/call, post a message into the chat with ui/message, add data to the model’s context with ui/update-model-context, and ask the host to open a link with ui/open-link.

The page does not speak MCP to your server itself. Every tool call it makes goes through the host, over the same connection and sign-in the conversation already uses, and any other network request it makes is limited to origins the server declared. That is what lets a host render a third-party page without trusting it.

Status: an official, opt-in extension

MCP Apps went live on 26 January 2026 as the first official MCP extension. The announcement on the MCP blog (opens in a new tab) credits the earlier work of the community MCP-UI project and OpenAI’s Apps SDK, with Anthropic’s developers steering the proposal to production. The specification, SEP-1865, is marked Stable at 2026-01-26, and its repository also carries a draft of the next revision.

Extensions are not part of core conformance, and the extensions overview (opens in a new tab) says they are always off until both sides opt in. Under the 2026-07-28 revision, a client advertises support in the capabilities it sends with each request, listing the media types it can render, and a server advertises it in its server/discover response. When only one side supports it, the tool falls back to behaving like any other tool, which is why every UI tool must still return a useful text result.

Which hosts support it

  • Claude (web, desktop, iOS, Android and Cowork). Anthropic calls these interactive connectors, marked with an Interactive badge in the directory. Its help page on interactive connectors (opens in a new tab) says they run in sandboxed iframes with strict Content Security Policies and use the permissions you granted when you connected, and that Team and Enterprise owners can switch off interactive tool calls without disabling the connector.
  • Claude Desktop with local servers. Anthropic’s getting-started guide runs the example apps as local npx servers from claude_desktop_config.json. Claude asks before displaying an app; you choose Allow, or Always allow for a server you trust.
  • ChatGPT. OpenAI’s guide to adding UI to an MCP server (opens in a new tab) builds on the standard, with _meta.ui.resourceUri as the field to use, and adds window.openai extras such as file uploads and checkout only for what the standard does not cover.
  • VS Code. Chat renders MCP Apps inline when a server offers them; support arrived in January 2026, in Insiders first.
  • Cursor. Its MCP documentation lists MCP Apps, with tools still working normally where UI is not supported.
  • Others. The protocol’s community-maintained extension matrix also lists Microsoft 365 Copilot, Goose, Postman, MCPJam, Archestra.AI and PostHog Code.
  • Claude Code. It is a terminal. Its MCP documentation treats UI resources as pages for a host application to render and leaves them out of @ suggestions; rendering them is not documented. Codex and Gemini CLI do not document MCP Apps either.

Support for every MCP feature client by client, not only this one, is in which apps support MCP.

The security model

  • Sandboxed iframe. The page cannot reach the host’s DOM, cookies or storage, cannot navigate the host page, and can only send messages the host chooses to act on. Web hosts use a double iframe, with a sandbox proxy on a different origin from the host.
  • Deny-by-default network access. With no declaration, the host’s Content Security Policy blocks every outside connection. A resource lists the origins it needs in _meta.ui.csp: connectDomains for fetch and WebSocket, resourceDomains for scripts, styles, images and fonts, frameDomains for nested frames. The host may tighten this but must not allow anything undeclared.
  • Declared permissions. Camera, microphone, geolocation and clipboard writes must be asked for in _meta.ui.permissions; the host decides whether to grant them.
  • Reviewable templates. Pages are declared up front rather than generated inside a tool result, so a host can inspect, hash or block them before any tool runs.
  • Visibility. A tool marked visibility: ["app"] is hidden from the model and callable only from the page, for things like a refresh button. The host must refuse a page’s call to a tool that is not visible to apps.

None of this replaces your server’s own checks. A button in the page calls your tool as the signed-in person, so authorise every call on the server exactly as you would one from the model. The wider risks are in MCP security risks.

A minimal example shape

The official SDK is @modelcontextprotocol/ext-apps: server helpers that write the metadata for you, and an App class for the page. This was type-checked against version 2.0.0 of that package with version 2 of the TypeScript server SDK, @modelcontextprotocol/server. The server side registers one tool and one page:

server.ts
import { McpServer } from "@modelcontextprotocol/server";
import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE }
  from "@modelcontextprotocol/ext-apps/server";
import { z } from "zod";

const server = new McpServer({ name: "board", version: "1.0.0" });
const uri = "ui://board/lanes.html";

registerAppTool(server, "show_board", {
  title: "Show board",
  description: "Show open tasks as a board the user can scan. Also returns them as text.",
  inputSchema: z.object({ lane: z.string().optional() }),
  _meta: { ui: { resourceUri: uri } },
}, async ({ lane }) => {
  const tasks = await loadTasks(lane);
  return {
    content: [{ type: "text", text: tasks.map((t) => `${t.ref} ${t.title} (${t.lane})`).join("\n") }],
    structuredContent: { tasks },
  };
});

registerAppResource(server, "Board view", uri, { mimeType: RESOURCE_MIME_TYPE }, async () => ({
  contents: [{ uri, mimeType: RESOURCE_MIME_TYPE, text: boardHtml }], // one bundled HTML file
}));

The page registers its handlers, then connects. The SDK detects the host and picks the transport:

app.ts (bundled into boardHtml)
import { App } from "@modelcontextprotocol/ext-apps";

const app = new App({ name: "Board view", version: "1.0.0" });

app.ontoolresult = (result) => render(result.structuredContent);
await app.connect();

// A refresh button asks the server again, through the host.
refreshButton.onclick = async () => {
  const result = await app.callServerTool({ name: "show_board", arguments: {} });
  render(result.structuredContent);
};
  • The text in content is the fallback, and the specification says tools must return meaningful content even when a UI is available. A host without MCP Apps, or a terminal, still gets a usable answer.
  • Bundle the page into one HTML file, or declare every outside origin in _meta.ui.csp; anything undeclared is blocked.
  • Hosts set their own format for the page’s sandbox origin, _meta.ui.domain. Claude’s getting-started guide (opens in a new tab) derives it from a SHA-256 hash of your server URL and gives the command to compute it.
  • Serve it like any other server, over Streamable HTTP or stdio, and test it in a host that renders apps. The steps for building and testing a server are in how to build an MCP server and the MCP Inspector.

MCP Apps and the ChatGPT Apps SDK

They are no longer two ways of doing the same thing. OpenAI’s Apps SDK was one of the starting points for the standard, and ChatGPT now implements MCP Apps, keeping its older openai/outputTemplate field as an alias and window.openai for ChatGPT-only features. Anthropic’s guide links a migration reference for moving an Apps SDK app to the MCP Apps SDK. Build on the standard and one page runs in Claude, ChatGPT and VS Code; reach for a host’s own extras only where you need them, and detect the capability rather than the host’s name.

When to build one, and when not

  • Worth it: exploring data a person wants to filter or drill into, a form with many interdependent options, previewing media, reviewing a list item by item with buttons, a live status view.
  • Not worth it: anything a paragraph or a table answers. Text works in every client, costs nothing to maintain, and is what the model reads anyway.
  • Remember the extra surface. A page is web code to secure, test and keep compatible with several hosts. Start with good tool results and add a page where people clearly need to see or touch the data.

A tools-only server still works everywhere

fenbs, a task board where people and AI assistants are members with roles, runs a tools-only MCP server at https://fenbs.ai/api/mcp: every result is text, so it works the same in Claude, ChatGPT, VS Code, Cursor and a terminal. People look at the board itself, with its lanes To Do, Next Up, In Progress and Completed; the assistant reads and changes the same tasks through tools, within the role and scopes it was given, and every change it makes is recorded in History under its name. If you are building an MCP App, a board is also a practical place to keep its host test matrix and the fixes each host needed.

Related

What each MCP building block is for: MCP tools, resources and prompts. Claude’s connector types, including interactive ones: Claude connectors explained. Connecting an assistant to fenbs: MCP docs.

Questions people ask.

Does Claude Desktop support MCP Apps?

Yes. Claude Desktop renders MCP Apps inline, both from connectors marked Interactive and from local servers added in claude_desktop_config.json. It asks for permission before displaying an app, and you can allow it once or always for a server you trust. Claude on the web, mobile and Cowork support them too.

Is MCP Apps the same as the ChatGPT Apps SDK?

Not the same, but closely related. MCP Apps is the open MCP extension, built partly from OpenAI’s Apps SDK and the MCP-UI project. ChatGPT now implements it, so an app built on the standard can run in ChatGPT, Claude and VS Code, with ChatGPT-only extras available through window.openai.

Does Claude Code support MCP Apps?

Claude Code runs in a terminal, and its documentation does not describe rendering MCP Apps. It still calls the tools behind an app and reads their text results, which is why every UI tool should return a useful text answer as well.

Are MCP Apps safe to use?

They are designed to be contained. The page runs in a sandboxed iframe with no access to the host page, its network access is blocked unless the server declares each outside domain, and every action it takes goes through the host as a tool call. The server behind it still needs the same trust and authorisation as any other connector.

Start with one thing.

There is nothing to set up first. Write one line and you’ve started.