MCP Apps support
MCP Apps is the protocol extension that lets MCP servers ship interactive UI alongside structured data, and lets MCP hosts render those UIs inline in their chat surface. Both mcp.ggui.ai and the OSS @ggui-ai/mcp-server implement the host-side and resource-side wire format so generative UIs appear directly in the chat instead of “click this link to open a browser tab”.
This page describes the protocol pieces ggui implements. For end-user setup, see Connect Claude Desktop.
What MCP Apps adds
Section titled “What MCP Apps adds”Without MCP Apps, an mcp tool that produces UI has to choose between:
- Returning structured data and hoping the host knows how to format it (no interactivity), or
- Returning a URL the user has to click out to (interactive but loses context — chat ↔ UI happens in two windows).
MCP Apps adds a third option: declare a UI resource alongside the tool result, the host sandboxes it in an iframe inside the chat, and a small WebSocket channel carries data both ways (host → UI for live updates, UI → server for actions).
What ggui ships
Section titled “What ggui ships”When the server is booted with mcpApps enabled, three things happen:
io.modelcontextprotocol/uiis advertised in the server’sinitializecapabilities (underexperimental). MCP-Apps-aware hosts read this and switch on inline rendering.ui://ggui/sessionis served as a resource viaresources/read. It’s a thin shell of HTML that loads the@ggui-ai/iframe-runtimebundle and connects to the WebSocket channel.- Every
ggui_pushresult carries_meta.ggui.bootstrap—wsUrl+ a short-TTLbootstrapToken+expiresAt+runtimeUrl. The iframe consumes the bootstrap, opens the WebSocket, and exchanges the bootstrap token for a longer-TTLsessionTokenfor reconnects.
Capability declaration
Section titled “Capability declaration”On initialize, ggui returns:
{ "capabilities": { "tools": { "listChanged": true }, "resources": { "subscribe": false, "listChanged": false }, "experimental": { "io.modelcontextprotocol/ui": {} } }}Hosts that recognise io.modelcontextprotocol/ui flip into inline-render mode. Hosts that don’t see it ignore the capability and fall back to opening renderUrl from the push result in an external browser.
Tool result shape
Section titled “Tool result shape”Every UI-producing tool (today: ggui_push) declares a meta-resource on the result so the host knows where to load the UI from:
{ "content": [ { "type": "text", "text": "Created session ses_abc123" } ], "_meta": { "ui": { "resource": "ui://ggui/session", "visibility": ["model"] }, "ggui": { "bootstrap": { "wsUrl": "wss://mcp.ggui.ai/ws", "renderBaseUrl": "https://mcp.ggui.ai/r/", "runtimeUrl": "https://mcp.ggui.ai/_ggui/iframe-runtime.js", "sessionId": "ses_abc123", "bootstrapToken": "BTKN_…", "expiresAt": 1735689600000 } } }}_meta.ui.visibility: ["model"] is the MCP Apps signal that this resource is a renderable UI surface. The host fetches ui://ggui/session once, sandboxes it in an iframe, and feeds it the _meta.ggui.bootstrap payload.
The shell at ui://ggui/session
Section titled “The shell at ui://ggui/session”Reading the resource returns a small HTML document — paper-themed, full-bleed, no chrome — whose only job is:
- Receive the bootstrap object from the host (via
postMessage). - Dynamically load
runtimeUrl(the iframe-runtime bundle). - Hand the bootstrap to the runtime, which opens the WebSocket and starts rendering.
The shell is intentionally minimal. The actual rendering logic — component resolution, contract validation, action dispatch — lives in @ggui-ai/iframe-runtime which the shell loads on demand. That keeps the shell payload tiny and lets the runtime version-bump independently.
Bootstrap token exchange
Section titled “Bootstrap token exchange”The bootstrap token is short-lived (default 60s) and single-use. The iframe trades it for a long-lived sessionToken on the first WebSocket subscribe frame, then uses the session token for reconnects. This means:
- Hosts can cache the resource document but the bootstrap is per-call (fresh token every push).
- An iframe that loses connection reconnects with its session token without re-fetching the resource or re-running OAuth.
- A leaked bootstrap token is useless 60 seconds later.
The bootstrap is HMAC-signed with a server-side bootstrapSecret. Multi-host deployments MUST pass a shared deterministic secret (typically from a secrets manager) so any pod accepts any other pod’s tokens.
OSS: enabling MCP Apps in your own server
Section titled “OSS: enabling MCP Apps in your own server”import { createGguiServer } from "@ggui-ai/mcp-server";
const server = createGguiServer({ // ... sessionChannel: true, // required — MCP Apps needs the WS channel mcpApps: { wsUrl: "wss://your-server.example.com/ws", renderBaseUrl: "https://your-server.example.com/r/", }, runtime: true, // serve the iframe-runtime bundle bootstrapSecret: process.env.BOOTSTRAP_SECRET, // required for multi-pod});What each option does:
sessionChannel: true— mounts the channel-3 WebSocket at/ws. MCP Apps requires it; the iframe has nowhere to connect to without one.mcpApps.wsUrl— the publicly-visible WebSocket URL written onto every bootstrap. Don’t passws://localhost:…if your server is internet-accessible — clients must be able to reach it.mcpApps.renderBaseUrl— the public origin used when generatingrenderUrlin push responses. Hosts that don’t speak MCP Apps fall back to opening this URL in a browser.runtime: true(default whenmcpAppsis on) — mounts the iframe-runtime bundle at/_ggui/iframe-runtime.js. Passruntime: { url: "https://your-cdn/…" }to point at an externally-hosted bundle.bootstrapSecret— HMAC secret. If you don’t pass one, the server mints a random secret at boot — fine for single-process dev, wrong for multi-pod (each pod would reject the others’ tokens).
mcpApps requires sessionChannel: true — the factory throws at construction if you enable one without the other.
Compatibility matrix
Section titled “Compatibility matrix”| Host | OAuth | MCP Apps | Notes |
|---|---|---|---|
| Claude Desktop | Yes | Yes | Inline rendering, full UX. (install) |
| claude.ai (web) | Yes | Yes | Same as Desktop. |
| Goose | Yes | Yes | Inline rendering in TUI mode varies by terminal. |
| VS Code Copilot | Yes | Yes | UI renders in a side panel. |
| Cursor | Yes | Partial | OAuth works, MCP Apps support depends on version. |
| Generic MCP runtime | No | No | Static Authorization: Bearer … + open renderUrl manually. |
If your host doesn’t yet implement MCP Apps, the underlying session still works — you just lose inline rendering. The push response always includes a fully-functional renderUrl you can open in any browser.
Reference
Section titled “Reference”- MCP Apps protocol: https://modelcontextprotocol.io/extensions/apps/overview
- ggui server factory:
@ggui-ai/mcp-server - Iframe runtime:
@ggui-ai/iframe-runtime