Use when building MCP Apps that serve interactive UI from MCP servers. Covers the ui:// URI scheme, HTML rendering in sandboxed iframes, and bidirectional communication between UI and host. USE FOR: rich UI in agent conversations, interactive dashboards from MCP servers, sandboxed iframe rendering DO NOT USE FOR: basic tool responses without UI (use mcp), agent communication (use a2a), full web applications
Resources
4Install
npx skillscat add tyler-r-kendrick/agent-skills/mcp-apps Install via the SkillsCat registry.
MCP Apps
Overview
MCP Apps is an extension to the Model Context Protocol that lets MCP servers return rich, interactive user interfaces instead of plain text. When a tool declares a UI resource, the host renders it in a sandboxed iframe, and users interact with it directly in the conversation. Supported by Claude, VS Code, Goose, and Postman.
How It Works
MCP Server Host (Claude, VS Code)
│ │
│◄── tool call ─────────────────│
│── tool result + ui resource ─►│
│ │── render iframe ──► User
│◄── JSON-RPC messages ────────│◄── user interaction- A tool declares a
uifield referencing aui://resource - The host calls the tool and receives the result with a UI reference
- The host fetches the UI content and renders it in a sandboxed iframe
- The UI communicates with the host via JSON-RPC over
postMessage
Declaring UI in a Tool
{
"name": "render_dashboard",
"description": "Display an interactive dashboard",
"inputSchema": {
"type": "object",
"properties": {
"data": { "type": "object" }
}
},
"ui": {
"uri": "ui://dashboard",
"title": "Dashboard"
}
}UI Resource
The server registers a UI resource that returns HTML:
@mcp.resource("ui://dashboard")
def dashboard() -> str:
return """
<!DOCTYPE html>
<html>
<body>
<h1>Dashboard</h1>
<div id="chart"></div>
<script>
// Communicate with the host via JSON-RPC
window.addEventListener('message', (event) => {
const { method, params } = event.data;
if (method === 'initialize') {
renderChart(params.data);
}
});
</script>
</body>
</html>
"""URI Scheme
MCP Apps use the ui:// URI scheme:
ui://dashboard— predeclared UI template- Only
text/htmlcontent is supported in the initial specification - External URLs, remote DOM, and native widgets are deferred to future versions
Security Model
| Layer | Protection |
|---|---|
| Iframe sandbox | Restricted permissions (no top-level navigation, no same-origin access) |
| Predeclared templates | Hosts can review UI templates before rendering |
| Auditable messages | All UI-to-host communication goes through loggable JSON-RPC |
| Content restrictions | Only text/html from the MCP server, no external URLs |
Communication
The iframe and host communicate via postMessage with JSON-RPC:
// UI → Host: request data
window.parent.postMessage({
jsonrpc: "2.0",
method: "getData",
params: { query: "sales" },
id: 1
}, "*");
// Host → UI: send data
window.addEventListener("message", (event) => {
const response = event.data;
if (response.id === 1) {
updateDisplay(response.result);
}
});Best Practices
- Keep UI self-contained — all HTML, CSS, and JS in a single resource since external URLs are not supported.
- Use the JSON-RPC
postMessagebridge for all data exchange between UI and host. - Design for the iframe sandbox restrictions — no
localStorage, no cookies, no top-level navigation. - Provide a meaningful
titlein the UI declaration for accessibility. - Keep UIs lightweight — they render inline in a conversation, not as full-page apps.
- Fall back gracefully to text content if the host does not support MCP Apps.