← library
promptexample · tested🤖 AI agentsv1 · published 2026-10-15 · updated 2026-11-03

MCP App with an interactive UI

A tool that renders an interactive view (chart, form, table) inside chat clients via the MCP Apps extension.

By Uplift Duo

Run it as a prompt

Paste this into any AI agent, or fetch it: curl -s https://uplift.page/api/v1/prompts/mcp-app-interactive-ui/raw

prompt.md
# MCP App with an interactive UI

Ship one MCP tool whose ui:// view renders in at least 2 hosts, as a single-file bundle under 300 KB gzipped, first paint under 500 ms after the tool result, with every data change going through a server tool.

## Recommended stack

- **@modelcontextprotocol/ext-apps 2.x** - registerAppTool/registerAppResource on the server, App class in the view. Official SDK for the MCP Apps extension (spec 2026-01-26, stable), supported by ChatGPT, Claude, VS Code, Goose, and others.
- **@modelcontextprotocol/server 2.x** - MCP server. ext-apps 2.x is built on the v2 SDK line.
- **Vite 8 + vite-plugin-singlefile 2.x** - Bundles the view into one HTML file. Hosts load the view in a sandboxed iframe, so everything must be inline.
- **ext-apps basic-host example** - Local host for testing. Renders the view at localhost:8080 without connecting a real chat client.

## Build steps

1. Pick a tool whose result is easier to see than read: a chart, a filterable table, a map, or a form to confirm parameters before a write.
2. Server: registerAppTool with `_meta: { ui: { resourceUri: "ui://<tool>/app.html" } }`, and registerAppResource returning dist/app.html with RESOURCE_MIME_TYPE. Also return a short text summary for hosts without UI support.
3. View: `new App({ name, version })`, set `app.ontoolresult` before `app.connect()`, render the data, and wire buttons to `app.callServerTool({ name, arguments })`.
4. Bundle with Vite and viteSingleFile; no remote scripts, fonts, or images.
5. Match the host theme (useHostStyles in React, or CSS variables) and test light and dark.
6. Serve the MCP server over Streamable HTTP (e.g. http://localhost:3001/mcp), then `git clone https://github.com/modelcontextprotocol/ext-apps && cd ext-apps/examples/basic-host && npm install && npm start`, open http://localhost:8080, call the tool and click through every action.
7. Connect to 2 real hosts and confirm the view and the text fallback both behave.

## Watch out for

- Setting handlers after connect() misses the initial tool result, leaving a blank view.
- The sandboxed iframe blocks remote scripts, fonts and images unless the resource declares their origins in _meta.ui.csp (resourceDomains / connectDomains); inline everything by default.
- Mutations done only in the view are invisible to the model and skip auth checks. Route every change through a server tool.
- No text fallback means hosts without MCP Apps support show nothing useful.

## Definition of done

- basic-host renders the view and a button refreshes data through a server tool.
- The view renders in 2 real hosts.
- `gzip -c dist/app.html | wc -c` is under 300,000.
- A host without UI support still returns a useful text answer.

The full prompt

Ship one MCP tool whose ui:// view renders in at least 2 hosts, as a single-file bundle under 300 KB gzipped, first paint under 500 ms after the tool result, with every data change going through a server tool.

Recommended stack

  • @modelcontextprotocol/ext-apps 2.x - registerAppTool/registerAppResource on the server, App class in the view. Official SDK for the MCP Apps extension (spec 2026-01-26, stable), supported by ChatGPT, Claude, VS Code, Goose, and others.
  • @modelcontextprotocol/server 2.x - MCP server. ext-apps 2.x is built on the v2 SDK line.
  • Vite 8 + vite-plugin-singlefile 2.x - Bundles the view into one HTML file. Hosts load the view in a sandboxed iframe, so everything must be inline.
  • ext-apps basic-host example - Local host for testing. Renders the view at localhost:8080 without connecting a real chat client.

Build steps

  1. Pick a tool whose result is easier to see than read: a chart, a filterable table, a map, or a form to confirm parameters before a write.
  2. Server: registerAppTool with _meta: { ui: { resourceUri: "ui://<tool>/app.html" } }, and registerAppResource returning dist/app.html with RESOURCE_MIME_TYPE. Also return a short text summary for hosts without UI support.
  3. View: new App({ name, version }), set app.ontoolresult before app.connect(), render the data, and wire buttons to app.callServerTool({ name, arguments }).
  4. Bundle with Vite and viteSingleFile; no remote scripts, fonts, or images.
  5. Match the host theme (useHostStyles in React, or CSS variables) and test light and dark.
  6. Serve the MCP server over Streamable HTTP (e.g. http://localhost:3001/mcp), then git clone https://github.com/modelcontextprotocol/ext-apps && cd ext-apps/examples/basic-host && npm install && npm start, open http://localhost:8080, call the tool and click through every action.
  7. Connect to 2 real hosts and confirm the view and the text fallback both behave.

Watch out for

  • Setting handlers after connect() misses the initial tool result, leaving a blank view.
  • The sandboxed iframe blocks remote scripts, fonts and images unless the resource declares their origins in _meta.ui.csp (resourceDomains / connectDomains); inline everything by default.
  • Mutations done only in the view are invisible to the model and skip auth checks. Route every change through a server tool.
  • No text fallback means hosts without MCP Apps support show nothing useful.

Definition of done

  • basic-host renders the view and a button refreshes data through a server tool.
  • The view renders in 2 real hosts.
  • gzip -c dist/app.html | wc -c is under 300,000.
  • A host without UI support still returns a useful text answer.

Served from the uplift.page library and refreshed within 5 minutes of every update.