A2UI vs MCP Apps: two standards for agent UI, and why you'll need both
There are now two ways for an AI agent to show you a UI. They solve the same problem completely differently.
MCP Apps (Anthropic/MCP) — the server ships HTML. The host renders it in a sandboxed iframe. Full power: React, D3, WebGL, anything. The server controls the pixels. A2UI (Google) — the server ships declarative JSON describing components. The client renders them natively using its own design system. No iframes. The client controls the pixels.Both are open standards. Both are shipping in production. They make fundamentally different tradeoffs.
MCP Apps: full control
// Server ships actual HTML
server.registerResource("ui://myapp/chart", {
mimeType: "text/html;profile=mcp-app",
content: `<html>
<canvas id="chart"></canvas>
<script>
const app = new App();
app.ontoolresult = (data) => drawChart(data);
document.getElementById("reset").onclick = () =>
app.callServerTool("reset_data", {});
</script>
<button id="reset">Reset</button>
</html>`,
});The iframe is sandboxed — can't access cookies, can't touch the parent page. But inside the sandbox, anything goes. A Figma viewer. A 3D model. A code editor. A map with real-time markers.
Actions work through app.callServerTool() — the iframe calls back to the MCP server via the host. Button click → JSON-RPC → server executes → result pipes back into the iframe.
A2UI: declarative components
[
{
"id": "deal-card",
"component": "Card",
"children": ["title", "value", "stage-btn"]
},
{
"id": "title",
"component": "Text",
"content": { "literalString": "Acme Corp" }
},
{
"id": "value",
"component": "Text",
"content": { "literalString": "$240,000" }
},
{
"id": "stage-btn",
"component": "Button",
"child": "btn-label",
"action": { "event": { "name": "advance_stage" } }
},
{
"id": "btn-label",
"component": "Text",
"content": { "literalString": "Advance Stage" }
}
]The client app maintains a catalog of trusted components — Card, Button, Text, Table, Chart. The agent can only use components from that catalog. The client renders them natively: SwiftUI on iOS, Flutter on Android, React on web.
Actions are events. A button press fires { "event": { "name": "advance_stage" } } to the host, which routes it wherever it needs to go.
When to use which
This isn't a competition. They're for different things.
| Use case | Pick |
|---|---|
| Dashboard with charts | MCP Apps |
| Simple status card with actions | A2UI |
| 3D model viewer | MCP Apps |
| Form with input fields | A2UI |
| Code editor / diff viewer | MCP Apps |
| Table with sorting | Either — A2UI is lighter, MCP Apps is more flexible |
| Mobile-first agent UI | A2UI (native rendering, no webview) |
| Data-heavy interactive tool | MCP Apps |
The pattern: if the component catalog covers it, use A2UI. If you need full control, use MCP Apps. Most agent UIs will be simple enough for A2UI — a card showing a number, a table with rows, a button that triggers an action. The complex cases (Figma viewer, notebook, map) need MCP Apps.
The gap nobody is filling
Right now you pick one. If you build an MCP App, it works in Claude and ChatGPT but looks foreign on a native mobile app. If you build A2UI, it looks great natively but won't render in clients that only support MCP Apps.
The missing piece is an abstraction that compiles to both. Define your widget once using high-level primitives — Card, Metric, Table, Button — and the build step outputs:
- MCP App HTML bundle for Claude/ChatGPT/VS Code
- A2UI JSON for native clients
- Native SwiftUI for iOS
- React component for web
Same widget definition, every surface. The simple cases use declarative components (A2UI path). The complex cases break out of the abstraction into full HTML (MCP Apps path).
Nobody ships this today. We're building it.
MCP Apps spec · A2UI spec · A2UI repo