MCP

MCP Apps

Interactive, app-like surfaces exposed through MCP.

Most MCP tools return text or JSON for a model to read. MCP Apps are the emerging MCP extension (@modelcontextprotocol/ext-apps) that lets a tool call instead return an interactive, HTML-based UI: rendered by the host in a sandboxed iframe, so a human watching the conversation (or the model itself, via updateModelContext) sees something real instead of a JSON blob.

Handoff's three MCP Apps today

Three tools return an interactive surface rather than JSON, each bundled from its own client entry point by scripts/build-mcp-apps.mjs:

ToolApp resourceWhat it renders
handoff_preview_componentui://handoff/component-previewThe actual built component, live, with responsive width controls.
handoff_browse_tokensui://handoff/token-paletteA palette of color swatches, type specimens and the spacing scale; clicking a token hands it back to the model.
handoff_browse_componentsui://handoff/component-galleryA searchable card grid of the component catalog; select a component to hand it back, or open it in the registry.

Component preview

handoff_preview_component renders the actual built component, live, with responsive width controls:

  1. The tool looks up the component and its available preview keys (falling back to "generic" or the first available preview).
  2. It registers a ui://handoff/component-preview resource, an HTML shell with a toolbar and an inner <iframe>.
  3. That inner iframe points at the registry's own built preview HTML (/api/component/<id>/<id>-<previewKey>.html), the same rendering path the UI surface uses, not a re-implementation.
  4. The tool result carries _meta.ui.resourceUri pointing at that resource, so a host that understands MCP Apps renders it inline instead of just showing the JSON.

This tool is registry-mode only, it needs a real component row and a built preview HTML file, neither of which exist in workspace mode.

Build process

Each app's browser-side code lives at src/app/lib/mcp/apps/<name>.client.ts (component-preview, token-palette, component-gallery). scripts/build-mcp-apps.mjs bundles each with esbuild (minified, ESM, browser target) and base64-encodes the result into a generated <name>.bundle.ts, the server inlines that string directly into the resource HTML at request time. There's no separate static host or CDN involved; the whole app ships inside the MCP response. Regenerate the bundle after editing the client file; it's committed so next build/typecheck sees it without needing an extra build step in CI.

Client support & fallback

MCP Apps require a host that implements the ui:// resource type, resources/read, and the app iframe sandbox (Claude's clients are the primary target). Hosts that don't support MCP Apps still get a usable result: handoff_preview_component's handler always returns a plain content: [{ type: 'text', ... }] block alongside the structuredContent and the app registration, so the tool degrades to "here's the preview URL and metadata as JSON" rather than failing outright.

On this page