Install any skill in seconds. Free to start, no credit card required.
Get Started Free →This skill should be used when the user asks to "create an MCP App", "add a UI to an MCP tool", "build an interactive MCP View", "scaffold an MCP App", or needs guidance on MCP Apps SDK patterns, UI-resource registration, MCP App lifecycle, or host integration. Provides comprehensive guidance for building MCP Apps with interactive UIs.
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✗→✓ | ▲ Improved | 83% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 31% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 46% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 31% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 43% | 0% |
Build interactive UIs that run inside MCP-enabled hosts like Claude Desktop. An MCP App combines an MCP tool with an HTML resource to display rich, interactive content.
Every MCP App requires two parts linked together:
The tool's _meta.ui.resourceUri references the resource's URI.
Host calls tool → Host renders resource UI → Server returns result → UI receives result.
| Framework | SDK Support | Best For | |-----------|-------------|----------| | React | useApp hook provided | Teams familiar with React | | Vanilla JS | Manual lifecycle | Simple apps, no build complexity | | Vue/Svelte/Preact/Solid | Manual lifecycle | Framework preference |
Adding to existing MCP server:
registerAppTool, registerAppResource from SDK_meta.ui.resourceUriCreating new MCP server:
vite-plugin-singlefileClone the SDK repository for working examples and API documentation:
bashgit clone --branch "v$(npm view @modelcontextprotocol/ext-apps version)" --depth 1 https://github.com/modelcontextprotocol/ext-apps.git /tmp/mcp-ext-apps
Learn and adapt from /tmp/mcp-ext-apps/examples/basic-server-{framework}/:
| Template | Key Files | |----------|-----------| | basic-server-vanillajs/ | server.ts, src/mcp-app.ts, mcp-app.html | | basic-server-react/ | server.ts, src/mcp-app.tsx (uses useApp hook) | | basic-server-vue/ | server.ts, src/App.vue | | basic-server-svelte/ | server.ts, src/App.svelte | | basic-server-preact/ | server.ts, src/mcp-app.tsx | | basic-server-solid/ | server.ts, src/mcp-app.tsx |
Each template includes:
server.ts with registerAppTool and registerAppResourcemain.ts entry point with HTTP and stdio transport setupsrc/mcp-app.ts, src/mcp-app.tsx) with lifecycle handlerssrc/global.css with global styles and host style variable fallbacksvite.config.ts using vite-plugin-singlefilepackage.json with npm run scripts and required dependencies.gitignore excluding node_modules/ and dist/Read JSDoc documentation directly from /tmp/mcp-ext-apps/src/:
| File | Contents | |------|----------| | src/app.ts | App class, handlers (ontoolinput, ontoolresult, onhostcontextchanged, onteardown, etc.), lifecycle | | src/server/index.ts | registerAppTool, registerAppResource, helper functions | | src/spec.types.ts | All type definitions: McpUiHostContext, McpUiStyleVariableKey (CSS variable names), McpUiResourceCsp (CSP configuration), etc. | | src/styles.ts | applyDocumentTheme, applyHostStyleVariables, applyHostFonts | | src/react/useApp.tsx | useApp hook for React apps |
See /tmp/mcp-ext-apps/docs/patterns.md for detailed recipes:
visibility: ["app"], hiding tools from modelisError, informing model of failuresresources/read, blob field_meta.ui.csp, CORS, _meta.ui.domainrequestDisplayMode, display mode changesupdateModelContext, sendMessage, keeping model informedviewUUID, localStorage, state recoveryontoolinputpartial, progressive rendering/tmp/mcp-ext-apps/examples/basic-host/ shows one way an MCP Apps-capable host could be implemented. Real-world hosts like Claude Desktop are more sophisticated—use basic-host for local testing and protocol understanding, not as a guarantee of host behavior.
Always use npm install to add dependencies rather than manually writing version numbers:
bashnpm install @modelcontextprotocol/ext-apps @modelcontextprotocol/sdk zod express cors npm install -D typescript vite vite-plugin-singlefile concurrently cross-env @types/node @types/express @types/cors
This lets npm resolve the latest compatible versions. Never specify version numbers from memory.
Unless the user has specified otherwise, use tsx for running TypeScript server files. For example:
bashnpm install -D tsx npm pkg set scripts.dev="cross-env NODE_ENV=development concurrently 'cross-env INPUT=mcp-app.html vite build --watch' 'tsx --watch main.ts'"
> !NOTE] > The SDK examples use bun but generated projects should default to tsx for broader compatibility.
Register ALL handlers BEFORE calling app.connect():
typescriptconst app = new App({ name: "My App", version: "1.0.0" }); // Register handlers first app.ontoolinput = (params) => { /* handle input */ }; app.ontoolresult = (result) => { /* handle result */ }; app.onhostcontextchanged = (ctx) => { /* handle context */ }; app.onteardown = async () => { return {}; }; // etc. // Then connect await app.connect();
content array for non-UI hostslocalhost—require a CSP configuration_meta.ui.csp and _meta.ui.domain go in the contents[] objects returned by registerAppResource()'s read callback, not in registerAppResource()'s config objectapp.connect()ontoolinputpartial to show progress during input generationTest MCP Apps locally with the basic-host example:
bash# Terminal 1: Build and run your server npm run build && npm run serve # Terminal 2: Run basic-host (from cloned repo) cd /tmp/mcp-ext-apps/examples/basic-host npm install SERVERS='["http://localhost:3001/mcp"]' npm run start # Open http://localhost:8080
Configure SERVERS with a JSON array of your server URLs (default: http://localhost:3001/mcp).
Send debug logs to the host application (rather than just the iframe's dev console):
typescriptawait app.sendLog({ level: "info", data: "Debug message" }); await app.sendLog({ level: "error", data: { error: err.message } });
Other measured skills in the registry, with their headline benchmark lift.