By · Founder, Stacktree · Last updated
blog · MCP

MCP Apps made HTML the official UI of agents. Where does that HTML live?

The first official MCP extension standardizes how servers ship interactive HTML and how hosts render it: Claude, ChatGPT, VS Code, and Goose all speak it now. The spec is precise about emitting HTML and deliberately silent about where it lives once the conversation ends. Both halves matter.

Get started free

What did MCP Apps standardize?

MCP Apps (SEP-1865), Final since January 26, 2026, is the first official extension to the Model Context Protocol. It gives every MCP server one way to ship an interactive HTML interface: predeclare a template as a ui:// resource, let the host render it in a sandboxed iframe, and communicate over MCP's own JSON-RPC. It standardizes agents emitting HTML inside a conversation. It defines nothing about hosting, sharing, or persisting that HTML beyond the session; that part still needs a publish step.

What MCP Apps actually standardizes

Before MCP Apps, rich UI in agent chat was a fork in the road. MCP-UI, the community project by Ido Salomon and Liad Yosef, proved servers could ship interactive interfaces and collected real adopters doing it. OpenAI's Apps SDK, launched in November 2025, did the same inside ChatGPT with its own conventions. Two working approaches, two sets of host behaviors, and every server author maintaining adapters for both.

SEP-1865 unified them into one open standard, proposed on November 21, 2025 and shipped as the first official MCP extension on January 26, 2026. The mechanics are small enough to state completely:

  • UI templates are predeclared resources. A server exposes its interface under the ui:// URI scheme, with a MIME type of text/html;profile=mcp-app. Predeclaring, rather than embedding HTML in tool results, lets hosts prefetch, cache, and security-review templates before anything runs.
  • Tools opt in through metadata. A tool that wants a visual result sets _meta.ui.resourceUri to point at its template. If the host supports the extension, it renders the tool result through that template.
  • Rendering is sandboxed by mandate. UI content MUST render in a sandboxed iframe. The host constructs the Content Security Policy from domains the server declares up front, and the default, when nothing is declared, denies all network access. A host may tighten that; it must never loosen it past the declaration.
  • The iframe speaks MCP. Communication between the embedded page and the host is JSON-RPC over postMessage, the same base protocol as the rest of MCP. A button click that triggers a tool call goes through the host's ordinary audit and consent path.
  • It is an extension, not core. Client and server negotiate it under the identifier io.modelcontextprotocol/ui. The 2026-07-28 specification formalizes the extensions framework it ships under.

Adoption settled the question of whether this is the standard. Claude (web and desktop), Goose, and VS Code Insiders rendered MCP Apps on launch day, ChatGPT within the week, and the official repository now lists Postman, MCPJam, and the mcp-use inspector alongside them. The SDK, @modelcontextprotocol/ext-apps, is at v1.7.4.

Why HTML won

The SEP's rationale section is worth reading for how plainly it makes the case. HTML got picked as the only MVP content type because it is universally supported, has the simplest security story (a standard iframe sandbox), and covers most observed use cases. Alternatives like remote DOM and external URLs, both of which MCP-UI had explored, were deferred.

That decision matched where the community already was. When agents produce something visual, a dashboard, a calculator, a report, a prototype, the output format is a self-contained HTML file. Our own publishing data says the same: 99% of agent-published pages are a single self-contained file. MCP Apps did not invent HTML as the agent deliverable; it made it official, with a profile string and a security model attached.

The lifecycle of a rendered app

The spec is explicit about the whole life of a render, and it is short:

  1. The host discovers the template through tool metadata and fetches it with an ordinary resources/read. Hosts MAY prefetch and cache templates for performance.
  2. The tool runs; the host renders the result through the template in the sandboxed iframe. The page and the host exchange JSON-RPC over postMessage.
  3. When the host is done, for any reason, it sends a ui/resource-teardown notification. The spec's phrasing: the host MUST send it "before tearing down the UI resource, for any reason, including user action, resource re-allocation, etc."
  4. The iframe is destroyed.

Note what is in the host's control: everything. When the render appears, how long it lives, when it is torn down. The server ships a template and answers calls; it never holds the rendered artifact.

Where the HTML lives, and where it does not

There are two pieces of HTML in an MCP App, and neither is a page on the web.

The template lives on the MCP server, addressable only as a ui:// resource, which is a scheme that exists so hosts can tell UI resources apart from other MCP resources. It is not a URL a browser can open. You cannot send someone ui://weather-dashboard.

The render, the thing your user actually looks at, lives in a sandboxed iframe inside one host, inside one conversation, until teardown. It has no address at all. There is no mechanism in the extension for a render to outlive its session, be opened by someone outside the host, or exist on a second device.

So when the person watching the conversation says the ordinary next thing, and they do, the questions from r/ClaudeAI are literal ("how do I share large html files", "share it with colleagues", "send a page to a client"), the extension has no answer. Not because it is incomplete, but because that was never its job.

Out of scope, on purpose

To be fair to the authors: the silence on hosting is a good design decision, not a gap they missed. The security model works precisely because scope is bounded. A host can review a predeclared template, build a CSP from declared domains, and audit every message, because everything happens inside its walls. External URLs were considered for the MVP and deferred, partly on those same review grounds. Persistence and sharing would drag in identity, access control, and storage, none of which belong in a UI rendering extension.

The result is a clean division of labor. MCP Apps answers: how does interactive HTML get into the conversation, safely. It leaves open: how does HTML get out of the conversation, to the client, the colleague, the Slack channel, the Monday meeting.

The other half: a publish step

Getting HTML out of the session is a different primitive with different requirements. The artifact needs a real URL. The URL needs access control, because agent output routinely carries real data: a password, an email-domain gate, an expiry. It needs to survive the conversation that made it, and to be replaceable in place when the agent iterates. And the publishing verb has to be callable from inside the conversation, which in 2026 means over MCP.

That is the publish primitive, and it is what Stacktree is. The same MCP client that renders an app can call publish_html and hand back a private, unguessable URL; update_site replaces the content at the same URL when the agent re-runs. Gates, expiry, and burn-after-read are set by the agent in the same conversation, and the page can sit on your own domain when it is a client deliverable.

The two compose rather than compete. An MCP App is a render surface: interactive, sandboxed, session-scoped, exactly right for UI that belongs to the conversation. A publish primitive is a home: addressed, gated, persistent, exactly right for output that has to travel. A weather server wants the first. A report your client reads on Thursday wants the second. Plenty of servers will want both.

If you maintain an MCP server

A practical split, from running a publish primitive in production:

  • If your output is interactive and conversational, a picker, a configurator, a live chart the user manipulates mid-task, ship an MCP App. The extension is final, the SDK is mature, and every major host renders it. There is no reason to build a bespoke UI channel in 2026.
  • If your output is a deliverable, a report, a dashboard snapshot, a prototype, anything a human will forward to another human, give the agent a publish verb too. A render the host tears down cannot be the copy of record.
  • If you are not sure, watch what users do with the render. The moment someone screenshots your app to put it in an email, it wanted to be a URL.

MCP Apps settled the question of what agents emit when they emit UI: HTML, with a profile string and a sandbox. The next question is where that HTML lives once it matters to someone outside the session. That one has a settled answer too; it is just not in the spec.

FAQ

Frequent questions

What is MCP Apps? +
MCP Apps (SEP-1865) is the first official extension to the Model Context Protocol. It lets an MCP server ship an interactive HTML interface alongside its tools: the server predeclares a template as a ui:// resource, the host fetches it and renders it in a sandboxed iframe, and the embedded page talks back to the host over the same JSON-RPC protocol as every other MCP message. It has been Final since January 26, 2026.
Is MCP Apps part of the core MCP spec? +
No. It is an optional extension, negotiated between client and server under the identifier io.modelcontextprotocol/ui. The 2026-07-28 specification formalizes the extensions framework it ships under, but MCP Apps itself finalized separately on January 26, 2026 and already works in current hosts.
Which hosts support MCP Apps? +
At launch: Claude (web and desktop), Goose, and VS Code Insiders, with ChatGPT support arriving the same week. The official ext-apps repository also lists Postman, MCPJam, and the mcp-use inspector. The list keeps growing; the repository README is the current source of truth.
Can I share an MCP App outside the conversation? +
Not through the extension itself. The rendered app lives in a sandboxed iframe inside the host, and the host destroys it after sending a ui/resource-teardown notification. The spec defines no URL for the render, no viewer outside the host, and no persistence beyond the session. To put the HTML in front of someone who was not in the conversation, the agent has to publish it somewhere that returns a link.
What happens to an MCP App when the session ends? +
The host sends ui/resource-teardown and destroys the iframe. The template resource still exists on the MCP server, but the rendered instance, including any state the viewer created inside it, is gone. Nothing in the extension gives a render a life beyond the host session.
How do I send a page an agent made to a client or colleague? +
Publish the HTML to a host that hands back a link. With Stacktree, the same MCP client that renders apps can call publish_html and get a private, unguessable URL, then add a password or an email-domain gate before you send it. That covers the common asks: share it with colleagues, send a page to a client, share with a client without a login, all without making it public.
Keep reading

Related guides

References

Sources and further reading

Give your agent the publish half.

Stacktree is an MCP-native publish primitive: private, unguessable URLs for the HTML your agent emits. Install it in Claude Code, Codex, or Cursor in one command.

Sign up free →