MCP Apps Explained: Interactive UIs Rendered Inside Chat, Standardized as SEP-1865
MCP Apps (SEP-1865, Final): a server declares a ui:// resource, and hosts like Claude or VS Code Copilot render the interactive HTML inside the conversation. Mechanism, security model, SDK code, and host support.
How to
Install dependencies
npm install @modelcontextprotocol/ext-apps @modelcontextprotocol/sdk; add vite with vite-plugin-singlefile on the build side to bundle the UI into a single HTML file.
Register on the server
Use registerAppTool for a tool carrying _meta.ui.resourceUri, and registerAppResource to serve the ui:// resource with the exported RESOURCE_MIME_TYPE.
Wire up the UI
Create an App instance, call connect() to establish host communication, receive tool results with ontoolresult, and invoke server tools with callServerTool on user interaction.
Verify locally
After npm run build and npm run serve, point the official basic-host test host at your local /mcp endpoint via the SERVERS environment variable, or expose the server through a cloudflared tunnel and add it to Claude as a custom connector.
MCP Apps is an official extension to the Model Context Protocol (SEP-1865, Extensions Track, status Final): an MCP server declares a ui:// resource in its tool description, and the host renders that interactive HTML inside the conversation in a sandboxed iframe — dashboards, forms, and visualizations that live in the chat itself and can call server tools in return. This article is a section-by-section reading of the official overview, the SEP-1865 text, and the official build guide, all fetched on 2026-09-14. Code examples are quoted from the guide (documentation-checked, not machine-verified), and only claims reproducible from those sources are included.
1. The problem MCP Apps solves
The official overview opens with the limitation: text responses can only go so far — sometimes users need to interact with data, not just read about it. Traditional MCP tools return text, images, or structured data that the host displays; MCP Apps extends the pattern so a tool declares a reference to an interactive UI that the host renders in place.
The obvious question is why not just build a web app and send a link. The docs give four reasons:
| Dimension | Standalone web app | MCP App |
|---|---|---|
| Context | Users switch tabs, lose their place, forget which thread had the dashboard | The UI sits in the conversation that produced it |
| Data flow | Needs its own API, authentication, and state management | Reuses existing MCP patterns: the app calls any tool on the server, and the host can push fresh results in |
| Capability integration | Every app builds and maintains direct integrations (e.g., email providers) | The app can request an outcome ("schedule this meeting") and the host routes it through capabilities the user already connected, subject to consent |
| Security | Depends on the app's own implementation | Runs in a host-controlled sandboxed iframe; cannot access the parent page, steal cookies, or escape the container |
The docs are equally clear about the boundary: if your use case does not benefit from these properties, a regular web app might be simpler. MCP Apps earn their keep when tight integration with the LLM conversation matters.
2. How MCP Apps work: ui:// resources, tool metadata, sandboxed rendering
The official overview frames the core pattern as a combination of two MCP primitives: a tool that declares a UI resource in its description, plus a UI resource that renders data as an interactive HTML interface. When the model decides to call a tool that supports MCP Apps, four things happen:
- UI preloading: the tool description includes a
_meta.ui.resourceUrifield pointing at aui://resource. The host can preload it before the tool is even called, which enables features like streaming tool inputs to the app. - Resource fetch: the host fetches the UI resource from the server. It is an HTML page, often bundled with its JavaScript and CSS for simplicity; apps can also load external scripts from origins specified in
_meta.ui.csp. - Sandboxed rendering: web hosts typically render the HTML inside a sandboxed iframe within the conversation. The resource's
_meta.uiobject can carrypermissionsto request additional capabilities (microphone, camera) andcspto control which external origins the app may load from. - Bidirectional communication: the app and host talk over a JSON-RPC protocol that forms its own dialect of MCP — some requests and notifications are shared with the core protocol (for example
tools/call), some are similar (ui/initialize), and most are new methods with aui/prefix. The app can request tool calls, send messages, update the model's context, and receive data from the host.
A typical exchange, in the official diagram's terms: the user asks to see analytics, the model calls the tool, the server returns a result, the host pushes it to the app; the user clicks and drills down inside the app; the app issues a tools/call request, the host forwards it, fresh data comes back and the UI updates in place; and the app can send a context update back to the model.
3. Three design decisions recorded in SEP-1865
SEP-1865 (created 2025-11-21, status Final, nine authors, Extensions Track) records the trade-offs made during standardization. Three are worth reading closely:
- Predeclared resources, not embedded ones or resource links: UI is modeled as predeclared
ui://resources referenced by tools via metadata. Hosts can prefetch templates before tool execution, presentation is separated from data (helping caching), and UI resources can be security-reviewed before rendering. Rejected alternatives: embedded resources (the then-current MCP-UI approach — convenient for servers, but with gaps in performance optimization and the UI review process) and resource links (same gaps). - Reuse MCP's JSON-RPC, not a custom protocol: this reuses existing type definitions and SDK infrastructure, and JSON-RPC brings timeouts and error handling for free. Rejected alternatives: a custom message protocol (MCP-UI's tool/intent/prompt types translate to a subset of the proposed JSON-RPC messages) and a global API object (requires host-specific injection and cannot work with external iframe sources).
- HTML-only MVP: HTML is universally supported, has the simplest security model (a standard iframe sandbox), allows screenshot and preview generation, and covers most observed use cases. External URLs were deferred over concerns about model visibility, screenshots, and the review process — they may effectively arrive via the new
externalIframescapability.
The SEP's motivation section also explains where the standard came from: the community project MCP-UI demonstrated the viability and value of UI resources and bidirectional communication, with adopters including Postman, HuggingFace, Shopify, Goose, and ElevenLabs; OpenAI's Apps SDK, launched in November 2025 with MCP as its backbone, further validated demand for rich UIs inside conversational AI. MCP Apps unifies both lineages into a single open standard, fixing the fragmentation where servers could not rely on UI support, hosts behaved slightly differently, security and audit patterns were inconsistent, and developers maintained separate implementations. As an extension it is optional and backwards-compatible, negotiated explicitly through the extension capabilities mechanism.
4. Build an MCP App (code from the official guide)
The build guide asks for Node.js 18 or higher and recommends familiarity with MCP tools and resources, since MCP Apps combine both primitives. Dependencies:
npm install @modelcontextprotocol/ext-apps @modelcontextprotocol/sdk
npm install -D typescript vite vite-plugin-singlefile express cors @types/express @types/cors tsx
The ext-apps package provides both server-side helpers (registering tools and resources) and the client-side App class for UI-to-host communication. Vite with vite-plugin-singlefile bundles the UI into a single HTML file here purely for convenience — it is optional, and you can use any bundler or serve unbundled files once CSP and CORS are configured.
The server does two things: register a tool that includes the _meta.ui.resourceUri field, and register a resource handler that serves the bundled HTML. The guide's complete server file (server.ts):
// server.ts
console.log("Starting MCP App server...");
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import {
registerAppTool,
registerAppResource,
RESOURCE_MIME_TYPE,
} from "@modelcontextprotocol/ext-apps/server";
import cors from "cors";
import express from "express";
import fs from "node:fs/promises";
import path from "node:path";
const server = new McpServer({
name: "My MCP App Server",
version: "1.0.0",
});
// The ui:// scheme tells hosts this is an MCP App resource.
// The path structure is arbitrary; organize it however makes sense for your app.
const resourceUri = "ui://get-time/mcp-app.html";
// Register the tool that returns the current time
registerAppTool(
server,
"get-time",
{
title: "Get Time",
description: "Returns the current server time.",
inputSchema: {},
_meta: { ui: { resourceUri } },
},
async () => {
const time = new Date().toISOString();
return {
content: [{ type: "text", text: time }],
};
},
);
// Register the resource that serves the bundled HTML
registerAppResource(
server,
resourceUri,
resourceUri,
{ mimeType: RESOURCE_MIME_TYPE },
async () => {
const html = await fs.readFile(
path.join(import.meta.dirname, "dist", "mcp-app.html"),
"utf-8",
);
return {
contents: [
{ uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html },
],
};
},
);
// Expose the MCP server over HTTP
const expressApp = express();
expressApp.use(cors());
expressApp.use(express.json());
expressApp.post("/mcp", async (req, res) => {
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
enableJsonResponse: true,
});
res.on("close", () => transport.close());
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
});
expressApp.listen(3001, (err) => {
if (err) {
console.error("Error starting server:", err);
process.exit(1);
}
console.log("Server listening on http://localhost:3001/mcp");
});
Per the guide's own breakdown: resourceUri uses the ui:// scheme to tell hosts this is an MCP App resource, with an arbitrary path structure; registerAppTool registers the tool carrying _meta.ui.resourceUri — when the host calls it, the UI is fetched and rendered and the tool result is passed to it; registerAppResource serves the bundled HTML when the host requests the UI resource; and the Express server exposes MCP over HTTP on port 3001 at /mcp.
The UI side is an HTML page plus a TypeScript module using the App class to talk to the host (src/mcp-app.ts):
// src/mcp-app.ts
import { App } from "@modelcontextprotocol/ext-apps";
const serverTimeEl = document.getElementById("server-time")!;
const getTimeBtn = document.getElementById("get-time-btn")!;
const app = new App({ name: "Get Time App", version: "1.0.0" });
// Establish communication with the host
app.connect();
// Handle the initial tool result pushed by the host
app.ontoolresult = (result) => {
const time = result.content?.find((c) => c.type === "text")?.text;
serverTimeEl.textContent = time ?? "[ERROR]";
};
// Proactively call tools when users interact with the UI
getTimeBtn.addEventListener("click", async () => {
const result = await app.callServerTool({
name: "get-time",
arguments: {},
});
const time = result.content?.find((c) => c.type === "text")?.text;
serverTimeEl.textContent = time ?? "[ERROR]";
});
Three points, in the guide's words: app.connect() establishes communication with the host — call it once when your app initializes; app.ontoolresult fires when the host pushes a tool result (for example when the tool is first called and the UI renders); app.callServerTool() lets the app proactively call tools — each call is a round-trip to the server, so design the UI to handle latency gracefully. The App class also provides methods for logging, opening URLs, and updating the model's context with structured data from your app. Frameworks are not binding: the protocol is standard web primitives, and the official examples include starter templates for React, Vue, Svelte, Preact, Solid, and vanilla JavaScript.
5. The security model: sandbox, CSP, auditable traffic
The security model deserves its own section because it answers why a host can safely render third-party UI:
- Iframe sandbox isolation: the app cannot access the parent window's DOM, read the host's cookies or localStorage, navigate the parent page, or execute scripts in the parent context.
- postMessage-only communication: the host controls which capabilities the app gets — for example, restricting which tools an app may call, or disabling the
sendOpenLinkcapability. - Deny-by-default CSP: the build guide states the UI resource will be rendered in a secure iframe with deny-by-default CSP configuration; external resources must be declared explicitly.
- SEP threat-model mitigations: restricted sandbox permissions, predeclared templates that hosts can review before rendering, fully loggable (and therefore auditable) JSON-RPC traffic, and optional explicit user consent for UI-initiated tool calls.
For the broader threat landscape (Confused Deputy, Token Passthrough, SSRF, and the other official attack vectors), pair this with the site's MCP security guide — MCP Apps' sandbox addresses the layer where untrusted content enters host rendering, which complements rather than replaces server-side mitigations.
6. Testing and host support
The guide offers two test paths. The first is the official basic-host: clone the ext-apps repository, install dependencies under examples/basic-host, point the SERVERS environment variable at your server, run npm start, and open localhost port 8080 to pick a tool, call it, and watch your app render in a sandboxed iframe. The second is a real host, Claude: expose the local server with a cloudflared tunnel and add the generated URL as a custom connector in Claude (the guide notes custom connectors are available on paid Claude plans).
npx cloudflared tunnel --url http://localhost:3001
As of 2026-09-14, the official site lists 8 hosts supporting MCP Apps:
| Host | Notes |
|---|---|
| Claude (web) / Claude Desktop | The guide's demo path: a cloudflared tunnel plus a custom connector |
| VS Code GitHub Copilot | Rendered inside the editor |
| Microsoft 365 Copilot | Productivity-suite scenarios |
| Goose | Listed among MCP-UI adopters in the SEP's motivation |
| Postman | Also an MCP-UI adopter |
| MCPJam | Listed as an MCP client on the protocol homepage |
| Archestra.AI | Included in the official support list |
To add MCP Apps support to your own host, the guide offers two routes: the community @mcp-ui/client React components for rendering and interacting with app views, or the official SDK's AppBridge module, which handles rendering apps in sandboxed iframes, message passing, tool-call proxying, and security policy enforcement — the basic-host example shows the integration.
Two anchors for engineering activity (checked 2026-09-14): the latest npm release of @modelcontextprotocol/ext-apps is 2.0.0, published 2026-09-08; the ext-apps repository was last pushed 2026-09-09, and the build guide notes the extension is under active development.
7. Where MCP Apps sits: one piece of the extensions system
MCP Apps is not an isolated proposal but part of the MCP extensions system (the Extensions Track defined by SEP-2133). The official extensions directory also lists: Tasks (SEP-2663, asynchronous task execution for long-running MCP operations), Skills (SEP-2640, discovering and reading Agent Skills from MCP servers), OAuth Client Credentials (machine-to-machine authentication), and Enterprise-Managed Authorization (centralized access control via enterprise identity providers) — with per-client implementation status tracked on the official client-matrix page. Readers interested in how features move between the core spec and the extensions track can compare with the site's coverage of client-feature status.
The docs also make building MCP Apps with an AI coding agent a first-class path: the create-mcp-app skill bundles architecture guidance, best practices, and working examples; Claude Code can install it from the plugin marketplace, and other agents can get it via the Vercel Skills CLI or a manual copy — the skills-directory table covers VS Code / GitHub Copilot, Gemini CLI, Cline, Goose, Cursor, and Codex (~/.codex/skills/). For this site's readers, that is a direct intersection between Codex and the MCP ecosystem.
The official examples directory offers 15 runnable examples across five categories: 3D and visualization (a CesiumJS globe, Three.js scenes, shader effects), data exploration (cohort heatmaps, customer segmentation, a wiki explorer), business applications (scenario modeling, budget allocation), media (PDF, video, sheet music, text-to-speech), and utilities (QR codes, system monitoring, speech-to-text).
8. What this means for readers
Keeping claims inside what is verifiable today: the MCP homepage (2026-09-14) lists ChatGPT among the AI assistants that support MCP — that is protocol-level connectivity. The same day's MCP Apps host list (the 8 hosts above) does not include ChatGPT; these two statements must not be conflated. The most direct OpenAI-side connection is the fact recorded in SEP-1865's own text: the Apps SDK uses MCP as its backbone, and its architecture significantly informed this specification. Beyond that, Codex supports a skills directory where the official create-mcp-app skill can be installed — another intersection between OpenAI's toolchain and the MCP Apps workflow. The site's world state now records the key MCP Apps facts (updated 2026-09-14).
9. Next steps
- The protocol itself, and the tools/resources primitives: Model Context Protocol: how MCP works and how to build on it
- Build an MCP server from scratch (the base for the server-side pattern here): Build Your Own MCP Server: From Zero to Published
- The other side of the extensions track — features being born and retired in the core spec: MCP Client Features Today: Elicitation Stays, Roots and Sampling Deprecated (SEP-2577)
Key points
- Status and positioning: MCP Apps is the interactive-UI standard on the MCP Extensions Track; SEP-1865 is Final and was created 2025-11-21 (checked on modelcontextprotocol.io, 2026-09-14). The specification text lives in the separate modelcontextprotocol/ext-apps repository (a 2026-01-26 version plus a continuously updated draft)
- Four-step mechanism: the tool description carries _meta.ui.resourceUri (hosts may preload the UI before the tool is called), the host fetches the ui:// resource (an HTML page, often bundled with JS and CSS), renders it in a sandboxed iframe, and the app communicates bidirectionally over postMessage in a JSON-RPC dialect (tools/call is shared with core MCP, ui/initialize is similar, most methods are new with a ui/ prefix)
- Four official advantages over a standalone web app: context stays inside the conversation, bidirectional data flow reuses existing MCP patterns instead of a bespoke API, the app can delegate actions to capabilities the host has already connected (subject to user consent), and the sandboxed iframe isolates untrusted UI
- Security model: the iframe sandbox blocks access to the parent DOM, cookies, and localStorage; CSP is deny-by-default; _meta.ui.permissions can request capabilities like microphone or camera and _meta.ui.csp controls allowed external origins; all communication is auditable JSON-RPC, and UI-initiated tool calls can require explicit user consent
- Official SDK @modelcontextprotocol/ext-apps (latest npm release 2.0.0, published 2026-09-08): server-side registerAppTool / registerAppResource / RESOURCE_MIME_TYPE, UI-side App class (connect / ontoolresult / callServerTool); hosts can adopt it via the AppBridge module or the community @mcp-ui/client React components
- Host support (8 hosts listed on the official site, 2026-09-14): Claude, Claude Desktop, VS Code GitHub Copilot, Microsoft 365 Copilot, Goose, Postman, MCPJam, and Archestra.AI; the standard unifies the community MCP-UI project with OpenAI's Apps SDK
Frequently asked questions
Official references
- DocsMCP Apps official overview (modelcontextprotocol.io; source for mechanism, security model, and host support)
- DocsSEP-1865: MCP Apps - Interactive User Interfaces for MCP (status Final; design decisions and rejected alternatives)
- DocsBuild an MCP App, the official guide (source of the code examples in this article)
- Docsmodelcontextprotocol/ext-apps repository (specification text and SDK source; verified via api.github.com and raw on 2026-09-14)
Related articles
Codex CLI MCP Servers: The Full mcp_servers Config Field Guide
Codex CLI wires MCP servers via [mcp_servers] in config.toml: command/args/env for stdio, url/bearer_token/http_headers for remote, plus timeouts, tool filters and OAuth.
Read articleThe Model Hardware Standard, explained: how MHS lets AI agents safely operate physical devices
A close reading of Anthropic's August 27 research preview of the Model Hardware Standard: standardized drivers, three control mechanisms including MCP, results from six research partners, and eight vendors building support.
Read articleMCP Client Features Today: Elicitation Stays, Roots and Sampling Deprecated (SEP-2577)
The 2026-07-28 spec reshuffled MCP client features: Roots and Sampling deprecated (SEP-2577, retained 12+ months) while Elicitation remains with a new URL mode -- status, deprecation context, and migration paths, clause by clause.
Read articleSubscribe to GPTMap Weekly
One email every Monday: curated OpenAI updates, deep dives, and best practices. No ads, unsubscribe anytime.
Submitting opens Buttondown in a new tab to confirm your subscription.