Skip to main content
Glama
iamneilroberts

voygent-mcp-app-demo

voygent-mcp-app-demo

A worked, runnable example of building interactive in-chat UI with MCP Apps, plus a field guide to the gotchas you only learn by shipping one.

MCP Apps (the io.modelcontextprotocol/ui extension, SEP-1865) lets an MCP server hand the host a sandboxed HTML widget instead of a wall of text. The model calls one tool and the user gets a real interface: option pickers, live totals, buttons that talk back to the model. This repo is a minimal working example of that pattern, distilled from a production app (Voygent's Folio Board, a whole-trip interactive board running in claude.ai today).

The Pizza Builder example running standalone

The example app is a Pizza Builder: pick size, crust, and toppings; watch the price update live; hit Place order and it hands the choice back to the model. The domain is intentionally simple so the MCP Apps mechanics stay in focus. Every pattern here maps directly to what the Folio Board uses for a 10-day trip.


What you'll learn

This repo demonstrates, in ~350 lines of commented source, every capability that makes an MCP App feel native:

Capability

How

Where

Render an interactive widget from a tool call

_meta.ui.resourceUri links a tool to a ui:// resource

src/server.ts, docs/02

Keep the model's context tiny

launcher returns a {orderId} ref; the widget fetches the rest via an app-only tool

docs/06

Let the user pick options without spending tokens

visibility: ["app"] tools the model never sees

src/server.ts

Hand a result back to the model

updateModelContext (stage) → sendMessage (trigger)

docs/05

Show external images

_meta.ui.csp.resourceDomains

docs/04

Download a file

app.downloadFile

src/widget/widget.ts

Go fullscreen

app.requestDisplayMode (gated on availableDisplayModes)

src/widget/widget.ts

Match the host's theme/fonts

applyHostStyleVariables / applyHostFonts

docs/03

See what the host actually granted you

getHostCapabilities() + a capability probe

docs/07

Also included: 10 gotchas covering URI caching, the silent updateModelContext, the red caution banner, the session-locked tool catalog, and more.


Related MCP server: MCPizza

Quickstart

git clone https://github.com/iamneilroberts/voygent-mcp-app-demo
cd voygent-mcp-app-demo
npm install
npm run build

See the widget immediately, no host required. The build produces a single self-contained dist/builder.html that falls back to mock data when opened directly:

# macOS
open dist/builder.html
# Linux
xdg-open dist/builder.html

Run it as a real MCP server:

npm start            # Streamable HTTP at http://localhost:3001/mcp
npm run start:stdio  # stdio, for Claude Desktop / MCP Inspector

Connect it to a host:

  • Claude Desktop: add to your MCP config:

    {
      "mcpServers": {
        "pizza": { "command": "node", "args": ["/abs/path/to/voygent-mcp-app-demo/dist/index.js", "--stdio"] }
      }
    }

    Then ask Claude: "build me a pizza" → the board renders inline.

  • MCP Inspector: npx @modelcontextprotocol/inspector node dist/index.js --stdio.


Architecture in one diagram

  ┌─────────┐  tool call   ┌──────────┐  postMessage   ┌──────────────┐
  │  Model  │ ───────────▶ │   Host   │ ◀────JSON-RPC──▶│  Widget      │
  │ (Claude)│ ◀─result/UI─ │(claude.ai│   (the bridge)  │ (iframe,     │
  └─────────┘              │ /Desktop)│                 │  sandboxed)  │
                           └────┬─────┘                 └──────┬───────┘
                                │  proxies app.callServerTool()│
                                ▼                              ▼
                           ┌─────────────────────────────────────┐
                           │   Your MCP server (src/server.ts)    │
                           │  build_pizza · pizza_state · pizza_pick │
                           └─────────────────────────────────────┘

The model launches the board once. After that the widget talks to your server directly through the host (app.callServerTool), so picking toppings costs zero model tokens. The widget hands control back to the model only when the user is done. Full walkthrough: docs/01.


Repo layout

src/
  server.ts          the MCP server: 1 launcher tool + 2 app-only tools + 1 UI resource
  data.ts            the toy domain (menu, orders, pricing)
  index.ts           transport wiring (Streamable HTTP + stdio)
  widget/
    widget.ts        the App: render, pick, place-order, download, fullscreen, theme
    widget.html      the shell (CSS + JS get inlined here at build time)
    styles.css
esbuild.mjs          bundles the widget into one self-contained HTML the server serves
docs/                01–08: the deep dives
CASE-STUDY.md        Voygent's Folio Board: the production app this was distilled from
media/               screenshots

The deep dives

  1. Architecture & lifecycle: the handshake, the bridge, the two-part registration.

  2. Declaring UI resources: ui://, the MIME type, _meta.ui.resourceUri, tool visibility.

  3. The host API: every app.* method, and the capabilities-vs-context distinction.

  4. CSP & imagery: why your image is blocked and how to declare the domains you need.

  5. Two-way comms: updateModelContext vs sendMessage, the caution banner, no progress tokens.

  6. The token economy: the ref-and-fetch pattern that kept a 9k-token payload out of the model's context (~98.5% smaller).

  7. Probing host capabilities: how to find out what a host actually grants, with real Claude Desktop results.

  8. Gotchas: 10 things to know before shipping.


Status of the findings

The capability claims in docs/07 come from running a probe app inside a live host. Claude Desktop (Claude/1.569.0, 2026-06-13) is empirically confirmed. Some claude.ai web cells are marked pending where we hadn't yet captured them in-host. They are labeled as pending, not guessed. Hosts evolve; re-probe before you rely on a specific cell.

Credits

Built by Neil Roberts, distilled from Voygent's Folio Board. MCP Apps spec: modelcontextprotocol/ext-apps. MIT licensed.

A
license - permissive license
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/iamneilroberts/voygent-mcp-app-demo'

If you have feedback or need assistance with the MCP directory API, please join our Discord server