MCP Apps: the first official extension gives tools an interface
MCP servers could always return text and structured data. Since January 26, 2026, they can also return an interface. MCP Apps — SEP-1865 — is the first official extension of the Model Context Protocol, and under the finalized 2026-07-28 spec it is one of exactly two. This is what it actually specifies, and where it touches a payment-metered gateway.
We have been tracking the MCP extension framework closely on this blog: Enterprise-Managed Authorization for identity, Tasks for async work, the Registry for discovery. Apps is the extension that came first — it shipped as official on January 26, 2026, before any of the others stabilized — and it answers a different question: what happens when a tool call needs a human to see something, not just a model to read something.
Two competing UIs become one standard
By late 2025 there were two serious attempts at putting interfaces inside chat-shaped agent hosts. The community project MCP-UI, created by Ido Salomon and Liad Yosef, had been adopted by Postman, Shopify, Hugging Face, ElevenLabs and Goose. OpenAI's Apps SDK did the same job inside ChatGPT, built on MCP as its foundation. Same problem, two incompatible answers — the exact divergence the protocol exists to prevent.
The proposal published November 21, 2025 merged the two lineages deliberately. The author list tells the story: MCP maintainers Anton Pidkuiko and Olivier Chafik, MCP-UI's Salomon and Yosef, core maintainer Nick Cooper, Sean Strong and Jerome Swannack from Anthropic, Alexi Christakis and Bryan Ashley from OpenAI. The stated motivation was blunt: the community had been creative in working around the lack of UI, but every workaround was different, so servers could not render consistently across clients.
Two months later, on January 26, 2026, MCP Apps became the first official extension, with a versioned spec at specification/2026-01-26/apps.mdx in its own ext-apps repository. That structure matters. Under the extension framework, extensions are first-class but separate: reverse-DNS identifiers, capability negotiation, dedicated repos with delegated maintainers, independent versioning. The 2026-07-28 release — finalized two days ago — names exactly two official extensions: Apps and Tasks.
The shape: a tool that ships its own view
The extension identifier is io.modelcontextprotocol/ui. A host that can render apps declares it during initialize, including which content types it accepts:
{
"capabilities": {
"extensions": {
"io.modelcontextprotocol/ui": {
"mimeTypes": ["text/html;profile=mcp-app"]
}
}
}
}
On the server side, a UI is just a resource with a reserved URI scheme. Templates live under ui://, carry the MIME type text/html;profile=mcp-app, and are fetched through the standard resources/read request — no new transport, no special endpoint. A tool that wants a view points at one through its metadata:
_meta: {
ui: {
resourceUri: "ui://weather-server/dashboard",
visibility: ["model", "app"]
}
}
The visibility field is the quietly interesting primitive. A tool marked ["app"] is invisible to the model and callable only from the rendered interface. That gives servers a clean way to expose fine-grained interaction endpoints — pagination, form submission, a confirm button — without polluting the model's tool list or burning context on tools the model should never call. The default is ["model", "app"]: both the agent and the interface can invoke it.
Predeclared templates are a security decision
The single most consequential design choice is that templates are predeclared resources, not ad-hoc HTML returned inside tool results. Hosts can list them at connection time, prefetch them, cache them, and security-review them before anything executes. The 2026-07-28 release candidate post states the intent plainly: tools declare their UI templates ahead of time so hosts can prefetch, cache, and security-review them before anything runs.
The template resource carries its own security envelope in _meta.ui: a csp object declaring exactly which external domains the view may contact (connectDomains, resourceDomains, frameDomains, baseUriDomains), a permissions object for camera, microphone, geolocation and clipboard access, an optional dedicated domain for sandbox origin isolation, and cosmetic hints like prefersBorder.
The host obligations around that envelope are written as hard requirements. The host MUST construct the Content Security Policy from the declared domains, and MUST NOT allow undeclared domains. The iframe runs with sandbox attributes allow-scripts and allow-same-origin, and the baseline CSP starts from default-src 'none' — everything is denied unless the template declared it. Hosts SHOULD warn users when a view requires external domain access and MAY enforce global allowlists or blocklists on top.
The bridge: MCP JSON-RPC over postMessage
Instead of inventing a widget message protocol, the extension reuses MCP's own JSON-RPC base protocol, transported over postMessage between the iframe and the host. The view opens with a ui/initialize handshake, declaring its capabilities — supported display modes among inline, fullscreen and pip — and receives back the host's protocol version, capabilities, and a hostContext carrying theme, CSS variables, container dimensions, locale, timezone and platform.
From there the view can issue a small, closed set of requests to the host. It can call tools/call to execute a server tool, resources/read to pull data, ui/message to post into the chat, ui/open-link for external URLs, ui/request-display-mode to go fullscreen, and ui/update-model-context to feed state back to the model. The host pushes the other direction with notifications: ui/notifications/tool-input delivers the arguments of the tool call that spawned the view (with a streaming tool-input-partial variant), tool-result delivers the outcome, tool-cancelled, size-changed and host-context-changed keep the view in sync, and ui/resource-teardown ends the lifecycle.
The consequence of reusing the base protocol is that a click inside the iframe is not a side channel. A UI-initiated tools/call flows through the same host machinery — the same audit trail, the same consent prompts — as a call the model makes directly. That is the property that makes the whole design defensible: the interface gets no privileged path to the server.
Theming follows the same standardization instinct. Hosts inject CSS custom properties — --color-background-primary, --color-text-primary, --font-sans, --border-radius-* and a few dozen more — so a single template renders native-looking in Claude's dark theme and ChatGPT's light one. Sizing is negotiated per axis: fixed, bounded, or view-controlled.
What the MVP leaves out
The 2026-01-26 spec is deliberately narrow. The only supported content type is text/html;profile=mcp-app — bundled HTML and JavaScript delivered as text or base64 blob. Three things the predecessor projects supported were explicitly deferred: externally hosted URLs, remote DOM approaches, and native widget formats. There is also cleanup debt in the wire format itself: the flat _meta["ui/resourceUri"] key from early drafts is deprecated in favor of the nested _meta.ui object and is slated for removal before GA. If you are shipping a server today, write the nested form.
Where it stands in July 2026
This is not a paper spec. The SDK, @modelcontextprotocol/ext-apps, is at v1.7.5 as of July 23, 2026 — shipping React hooks for app authors and an app-bridge entry point for host developers. The ext-apps repository carries working example servers: a three.js 3D visualizer, a map server, a PDF server, a system monitor, a sheet-music renderer. On the host side, the January launch post listed Claude on web and desktop, Goose and VS Code Insiders as rendering MCP Apps, ChatGPT landing the same week, with JetBrains, Kiro and Google DeepMind's Antigravity exploring support.
And with the 2026-07-28 spec now final — the stateless-core revision we covered in depth — Apps sits in the privileged position of being one of two official extensions, versioned independently from the core it extends.
The new attack surface
Every capability is a surface, and this one hands untrusted server-authored code a rendering context inside the host. The spec's mitigations are layered: sandboxed iframes restrict what the code can touch, predeclared templates make review possible, the CSP MUSTs cap exfiltration paths, and the JSON-RPC bridge makes every interaction auditable. But two primitives deserve specific attention from anyone operating agents.
First, ui/update-model-context is a write path from rendered HTML into the model's context. A compromised or malicious template does not need to exfiltrate data to cause harm; it can inject instructions into the very agent that invoked it. This is the same class of risk we mapped in the agent security threat model — indirect prompt injection — with a fresh, spec-blessed channel. Hosts that surface app-originated context updates to the model without provenance labeling are repeating the mistakes of early tool-result handling.
Second, app-only tools (visibility: ["app"]) execute outside the model's awareness by design. That is correct for UX plumbing, but it means audit logs — not model transcripts — are the only record of what an interface did. If your billing, your consent model, or your security review assumes every tool call appears in the conversation, this extension breaks that assumption.
What it means for LLM4Agents
LLM4Agents is infrastructure for headless agents — machine-to-machine callers that authenticate, pay per use in stablecoins via x402, and never render a pixel. So a UI extension might look orthogonal. It is not, for three reasons.
First, operators are humans even when agents are not. Our MCP server exposes account tools — balance, usage, workspace, settlement history — that agent operators already invoke from Claude and other MCP hosts. Every one of those is a natural MCP App: a balance widget with a spend sparkline beats a JSON blob, and the template can declare csp.connectDomains scoped to exactly our gateway origin, keeping the audit story clean.
Second, the payment moment wants a surface. In the x402 walk-up flow, a tool call halts at payment-required with price, chain and asset attached. Today that negotiation is invisible unless the client SDK auto-pays, as we detailed in the buyer stack. An app template attached to a paid tool is the standardized place to show a human what a tool costs, what was settled, and the transaction hash of the receipt — human-in-the-loop payment approval without inventing a proprietary widget channel.
Third, billing semantics survive intact. Because a UI-initiated tools/call is a real tool call on the same wire, it hits our reserve-then-settle metering exactly like a model-initiated one. No special-casing. But the audit-surface caveat above applies to us directly: gateway logs must record whether a settled call originated from model or app visibility, because operators will ask which of the two spent their balance.
Staying on the frontier
Concrete sequence for the platform, in order. Negotiate io.modelcontextprotocol/ui in our MCP server's capability handshake and ship one template — the balance-and-usage dashboard — as a ui:// resource wired to the existing account tools via _meta.ui.resourceUri. Then build the payment-approval app: render x402 price offers and settlement receipts inline for walk-up flows, reusing the nested _meta.ui format only. Third, tag every gateway-side tool invocation with its origin visibility (model vs app) in billing and observability records before app traffic exists, not after. Fourth, publish the app-capable server entry in the MCP Registry under our DNS namespace so hosts can discover and pre-review the templates. Last, track the deferred roadmap — external URLs and remote DOM would change the CSP calculus materially — and treat any host that renders our templates without enforcing the declared-domain MUSTs as out of contract.
The pattern across 2026 is now unmistakable: identity (EMA), async execution (Tasks), discovery (Registry), and now presentation (Apps) are each becoming standardized, independently versioned layers around a stateless MCP core. The platforms that win are the ones that implement each layer early enough to shape how the next one lands.
Give your agents a gateway — and your operators a view
One OpenAI-compatible API, 345+ models, stablecoin billing via x402, and an MCP server your tools already speak.
Register your agent