# MCP Apps

> 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

- Skill: `tyler-r-kendrick/mcp-apps` (Agent Skill, multi-file: 12 files)
- Install (CLI): `npx skillmds add tyler-r-kendrick/mcp-apps`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tyler-r-kendrick/mcp-apps/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- License: MIT
- Author: Tyler-R-Kendrick (https://skillmd.com/u/tyler-r-kendrick)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/tyler-r-kendrick/mcp-apps

---


# 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
```

1. A tool declares a `ui` field referencing a `ui://` resource
2. The host calls the tool and receives the result with a UI reference
3. The host fetches the UI content and renders it in a sandboxed iframe
4. The UI communicates with the host via JSON-RPC over `postMessage`

## Declaring UI in a Tool
```json
{
  "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:
```python
@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/html` content 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:
```javascript
// 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 `postMessage` bridge 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 `title` in 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.

