Topic: AI agents
MCP Apps Put UI in Chat, Not in Every Tool
MCP Apps can render dashboards, forms, and visual tools inside AI conversations. The useful boundary is simple: tools execute, views present, and hosts control trust.
Animated meme (expand/collapse)
MCP tools are good at returning text and structured data. They are less pleasant when a user needs to inspect 200 rows, tune six dependent options, drag items on a canvas, or approve a visual diff.
MCP Apps became the first official MCP extension in January 2026. A tool can now point to an interactive HTML resource that a compatible host renders inside the conversation. daily.dev described the pattern as dashboards, diff viewers, and configuration wizards backed by a JSON-RPC bridge.
That sounds like “put a web app in chat,” which is true but incomplete. The useful architecture is stricter:
The tool executes domain work, the View presents interaction, and the host controls the boundary between them.
Miss that separation and an MCP App becomes a fragile mini website with unclear state, duplicated APIs, and permissions nobody can explain.
MCP Apps are not generative UI
An MCP App does not normally ask the model to invent HTML for each response. Its interface is a predeclared resource owned and shipped by the MCP server.
The stable MCP Apps specification defines four main pieces:
- A tool declares
_meta.ui.resourceUri. - That URI uses the
ui://scheme and identifies an HTML resource. - The host reads the resource and renders it in a sandboxed iframe.
- The iframe and host exchange JSON-RPC messages over
postMessage.
A minimal tool declaration looks like this:
{
"name": "visualize_orders",
"description": "Show orders for a selected period",
"inputSchema": {
"type": "object",
"properties": {
"from": { "type": "string", "format": "date" },
"to": { "type": "string", "format": "date" }
}
},
"_meta": {
"ui": {
"resourceUri": "ui://orders/dashboard"
}
}
}
The server separately registers ui://orders/dashboard with the MCP Apps HTML MIME type. The official quickstart summarizes the pattern well: MCP App = Tool + UI Resource.
This separation matters. The dashboard is not the order service. It is one presentation of a tool result.
The host is a broker, not a decorative iframe wrapper
The host does more than insert an iframe into a chat message.
It discovers whether both sides support the io.modelcontextprotocol/ui extension, fetches the referenced resource, applies sandbox and Content Security Policy rules, passes tool results to the View, and proxies allowed calls back to the MCP server.
The resulting flow is:
Model or user
↓ chooses an operation
MCP tool on the server
↓ returns text / structured data
Host
↓ sends result through an audited bridge
Sandboxed View
↓ user filters, edits, confirms, or requests another action
Host
↓ validates and proxies an allowed tool call
MCP server
This gives each layer one job:
| Layer | Owns | Should not own |
|---|---|---|
| MCP tool | Validation, authorization, domain action, authoritative result | Layout and click state |
| View | Rendering, local interaction, accessible controls | Secrets or final authorization |
| Host | Capability negotiation, isolation, consent, message mediation | Product business rules |
If a button means “delete production data,” the View does not earn authority because it drew the button. The server must still validate the request, and the host may require user approval before forwarding it.
UI earns its cost when direct manipulation beats another prompt
The official overview highlights complex data exploration, configuration with dependent choices, visual creation, document review, and multi-step workflows. They share one property: the user needs to manipulate or inspect state, not merely read a sentence.
Good candidates include:
- A chart where users filter, compare, and drill into records.
- A deployment form where later fields depend on environment and region.
- A PDF or code diff where users approve specific sections.
- A canvas, map, media player, or timeline where position carries meaning.
- A batch-review queue with next, previous, accept, and reject actions.
Weak candidates include:
- Returning the current time.
- Reporting whether a deployment succeeded.
- Running one search and showing three short matches.
- Asking for a simple yes/no confirmation.
- Displaying prose that remains easier to copy, quote, and search as text.
The second list does not become better because it has cards, gradients, and a loading spinner. A normal MCP tool is faster to build, easier to test, and works in more hosts.
Animated meme (expand/collapse)
Design the text fallback before the iframe
MCP Apps is an optional extension. Host support varies, and the stable specification requires capability negotiation rather than assuming every client can render a View.
The graceful-degradation rule is unusually practical: UI-enabled tools should still return meaningful text content. If a host does not support Apps, the tool behaves as a standard tool instead of failing with “this result requires a widget.”
For an order dashboard, the result might contain:
{
"content": [
{
"type": "text",
"text": "42 orders, NT$183,400 total; 3 require review."
}
],
"structuredContent": {
"count": 42,
"total": 183400,
"needsReview": 3,
"orders": []
}
}
The text gives the model and unsupported clients a useful answer. Structured data gives the View something stable to render. Neither should require parsing prose back into application state.
This also makes testing cheaper: verify the tool contract first, then verify the View against known results.
A sandbox reduces reach; it does not make unknown code trustworthy
MCP Apps run in sandboxed iframes. The host prevents the View from reading the parent DOM, cookies, and storage, while communication passes through auditable messages. That is a meaningful boundary, not a security slogan.
Still, the server ships executable HTML, CSS, and JavaScript. The specification’s security model expects hosts to review predeclared resources, validate messages, and enforce CSP.
The practical defaults are:
- Declare only required
connectDomainsandresourceDomains; omitted domains stay blocked. - Request camera, microphone, geolocation, or clipboard permission only when the feature genuinely needs it.
- Keep credentials and authorization decisions on the server.
- Treat every View-initiated tool call as untrusted input.
- Keep destructive actions visible and consentable at the host boundary.
The spec also supports app-only tools through UI visibility metadata. A refresh or local form-submit helper can remain callable by the View without filling the model’s tool list. That is useful, but it does not bypass server authorization.
Keep authoritative state on the server
The stable January 2026 specification lists state persistence and restoration as future work. Real clients also differ in which App capabilities they expose. Recent Reddit implementation discussions describe developers synchronizing iframe state through server-side storage because host behavior is not uniform.
That suggests a boring rule: the View may cache interaction state, but the server owns recoverable state.
If closing and reopening the conversation destroys an important decision, it was never safe to keep that decision only inside iframe memory. Save the domain change through a tool, then let the View rebuild from a fresh result.
This boundary also makes one MCP server usable through text-only clients, future hosts, and a standalone web interface. UI becomes replaceable; business state does not.
Build in this order
My minimal path for adding MCP Apps to an existing server is:
- Make the underlying MCP tool useful without a View.
- Identify one interaction that currently takes several prompts or loses visual context.
- Return both concise text and stable structured data.
- Add one
ui://resource for that interaction. - Keep View-side calls narrow; hide purely UI helpers from the model when appropriate.
- Declare the smallest CSP and permission set.
- Test a supported host and the text-only fallback.
- Test keyboard use, narrow containers, loading, empty, and error states.
- Log and review every View-initiated mutation like any other API call.
Do not begin by converting the entire product. One tool with one high-friction workflow is enough to discover whether the embedded UI earns its maintenance cost.
A June Reddit thread on MCP Apps adoption captures both sides: some developers see strong results for graphs and confirmations, while others report inconsistent behavior across surfaces. That is not a reason to dismiss the standard. It is a reason to preserve fallback paths and test the hosts your users actually use.
Conclusion: add a View, not another source of truth
MCP Apps solve a real interface problem. Text is poor at direct manipulation, and repeated prompts are a clumsy substitute for filtering a table, reviewing a document, or editing a visual object.
The extension works best when its boundaries stay boring:
- Tools execute and validate.
- Views render and collect interaction.
- Hosts isolate, mediate, and request consent.
- Servers retain authoritative state.
- Text fallback remains useful.
Use MCP Apps when interaction carries meaning. Keep ordinary tools ordinary when a sentence already completes the job.
External references
- MCP Blog: MCP Apps — Bringing UI Capabilities to MCP Clients
- MCP Apps overview
- SEP-1865: Stable MCP Apps specification
- Official MCP Apps quickstart
- daily.dev: MCP Apps are the Future of Agentic Workflows
- Reddit: State of MCP Apps as of March 2026
- Reddit: Why haven’t MCP Apps gone viral the way MCP and Skills did?