Installation
npx skills add b-open-io/prompts --skill mcp-apps 10
Installs
MCP Apps
MCP Apps is the first official MCP extension (spec 2026-01-26, co-authored by Anthropic and OpenAI). It enables interactive HTML UIs rendered in sandboxed iframes inside MCP hosts. Extension ID: io.modelcontextprotocol/ui. npm package: @modelcontextprotocol/ext-apps.
MCP Apps bridge the gap between LLM tool calls and rich visual interfaces — the model sees text, users see interactive UIs.
Quick Start
The fastest path is the official create-mcp-app skill from the ext-apps repo:
npx skills add modelcontextprotocol/ext-apps --skill create-mcp-appThen ask the agent: "Create an MCP App that displays a color picker." For manual setup, see references/build-guide.md.
Architecture
Three layers:
- Server — Exposes tools and
ui://resources. Tools declare a_meta.ui.resourceUripointing to the UI. Resources serve HTML viaRESOURCE_MIME_TYPE. - Host — The MCP client (Claude Desktop, ChatGPT, VS Code Copilot). Renders iframes, proxies tool calls from the View, enforces the security sandbox.
- View — The HTML app running inside the sandboxed iframe. Uses the
Appclass from@modelcontextprotocol/ext-appsto communicate with the Host.
The View is intentionally thin. All tool calls go through the Host proxy — the View never reaches the network or the MCP server directly.
Server Pattern
Install the package:
npm install @modelcontextprotocol/ext-appsRegister tools and resources using the helper functions:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import {
registerAppTool,
registerAppResource,
RESOURCE_MIME_TYPE,
} from "@modelcontextprotocol/ext-apps/server";
import { readFile } from "fs/promises";
const server = new McpServer({ name: "my-app", version: "1.0.0" });
// Register a tool that exposes a UI
registerAppTool(
server,
"my-tool",
{
description: "Does something useful",
inputSchema: { type: "object", properties: {} },
_meta: {
ui: { resourceUri: "ui://myapp/index.html" },
},
},
async (args) => ({
content: [{ type: "text", text: "Model sees this text" }],
structuredContent: { data: "UI gets this rich data" },
})
);
// Register the HTML resource
registerAppResource(
server,
"ui://myapp/index.html",
"ui://myapp/index.html",
{ mimeType: RESOURCE_MIME_TYPE },
async () => ({
contents: [
{
uri: "ui://myapp/index.html",
mimeType: RESOURCE_MIME_TYPE,
text: await readFile("dist/index.html", "utf-8"),
},
],
})
);ui:// resources use the MIME type text/html;profile=mcp-app. They must be predeclared in the server manifest — dynamic resource generation is not permitted (security requirement for pre-scanning).
View Pattern
The View is the HTML app. Install the client package:
npm install @modelcontextprotocol/ext-appsimport { App } from "@modelcontextprotocol/ext-apps";
const app = new App({ name: "My App", version: "1.0.0" });
// CRITICAL: Set handlers BEFORE calling connect()
app.ontoolresult = (result) => {
// result.structuredContent has rich data for the UI
// result.content has text (what model sees)
renderData(result.structuredContent ?? result.content);
};
app.onhostcontextchanged = (ctx) => {
// Apply host theme, locale, timezone
applyTheme(ctx.theme);
};
// Connect after handlers are set
await app.connect();Set ontoolresult before or immediately after connect(). The initial tool result is buffered, so either order works, but setting handlers first is safer to avoid race conditions.
Lifecycle
- Discovery — Host reads server manifest, finds
io.modelcontextprotocol/uiin experimental capabilities. - Init — Host sends
ui/initialize. Server responds with supported UI version. - Data — Model calls tool → Host forwards
ui/notifications/tool-inputto View → Tool executes → Host forwardsui/notifications/tool-resultto View. - Interactive — View calls tools via
app.callServerTool(). Host proxies them. Results flow back viaontoolresult. - Teardown — Host sends
ui/notifications/resource-teardownwhen the iframe is destroyed.
Tool Visibility
Control which audience sees each tool:
_meta: {
ui: {
resourceUri: "ui://myapp/index.html",
visibility: ["app"], // UI-only, hidden from the model
}
}| Visibility | Default | Behavior |
|---|---|---|
["model", "app"] |
Yes | Both model and UI can call the tool |
["app"] |
No | UI-only tool, hidden from LLM |
["model"] |
No | LLM-only, View cannot call it |
Use ["app"] for tools that only make sense as UI interactions (pagination, sorting, drill-down).
Build
MCP App Views must be compiled to a single self-contained HTML file. Use Vite with vite-plugin-singlefile:
bun add -d vite vite-plugin-singlefile// vite.config.ts
import { defineConfig } from "vite";
import { viteSingleFile } from "vite-plugin-singlefile";
export default defineConfig({
plugins: [viteSingleFile()],
build: {
rollupOptions: { input: "src/views/mcp-app.html" },
outDir: "dist",
emptyOutDir: false,
},
});Any framework works: React, Vue, Svelte, Preact, Solid, or vanilla JS/HTML. The View is just HTML — no special runtime.
CRITICAL: Why bundling is mandatory
Views render inside srcdoc iframes. This means:
- Bare module imports fail —
import { App } from "@modelcontextprotocol/ext-apps"cannot resolve without a bundler - CDN
<script src="">tags fail — external script tags don't work in srcdoc iframes - ALL dependencies must be inlined — JS, CSS, everything bundled into one HTML file
- Install deps as npm packages (e.g.,
bun add leaflet), import them in your view TS file, and let Vite bundle them
Tool result viewUUID
Tool results MUST include _meta.viewUUID for the host to create a UI instance:
return {
content: [{ type: "text", text: "Summary for the model" }],
structuredContent: { data: richData },
_meta: { viewUUID: randomUUID() },
};Theming
The Host provides context via app.onhostcontextchanged:
interface HostContext {
theme: "light" | "dark" | "system";
locale: string; // e.g. "en-US"
timezone: string; // e.g. "America/New_York"
displayMode: "inline" | "fullscreen" | "pip";
containerDimensions: { width: number; height: number };
platform: "desktop" | "web" | "mobile";
}CSS variables provided by the Host sandbox:
:root {
--color-background-primary: /* host bg */;
--color-text-primary: /* host text */;
--color-border: /* host border */;
--color-accent: /* host accent */;
}Always include default values — not all hosts provide all CSS variables:
body {
background: var(--color-background-primary, #ffffff);
color: var(--color-text-primary, #000000);
}Display Modes
| Mode | Use Case |
|---|---|
inline |
Default. Embedded in the chat thread. Good for results, cards, small visualizations. |
fullscreen |
Editors, dashboards, complex tools. Occupies the full panel. |
pip |
Picture-in-picture. Persistent widget that survives scrolling (calendars, timers, music players). |
Declare the preferred display mode in _meta.ui:
_meta: {
ui: {
resourceUri: "ui://myapp/index.html",
displayMode: "fullscreen",
}
}Progressive Enhancement
Tools degrade gracefully on hosts without UI support. Always populate both content (text for the model) and structuredContent (rich data for the View):
async (args) => ({
content: [
{ type: "text", text: `Found ${results.length} items: ${summary}` }
],
structuredContent: { items: results, total: results.length },
})Non-UI hosts display content. UI hosts pass structuredContent to the View. This is the key design principle: MCP Apps are an enhancement, not a replacement.
Capability Negotiation
Declare the extension in the server capabilities:
const server = new McpServer({
name: "my-app",
version: "1.0.0",
capabilities: {
experimental: {
"io.modelcontextprotocol/ui": { version: "0.1" },
},
},
});Hosts that do not support MCP Apps ignore this capability and fall back to standard tool behavior.
Reference Files
Detailed protocol and integration documentation:
references/protocol.md— JSON-RPC methods, capability negotiation, message schemasreferences/security.md— Sandbox model, CSP, permissions, audit loggingreferences/patterns.md— App-only tools, streaming, multi-tool calls, state managementreferences/host-integration.md— AppBridge, @mcp-ui/client, AppRenderer, AppFramereferences/client-matrix.md— Host support (points to canonical source at modelcontextprotocol.io)references/build-guide.md— Complete project setup, configuration files, testing with Claude and basic-hostreferences/draft-spec-details.md— Draft spec additions: new CSS variables (70+), container dimensions, sandbox proxy, device capabilities, Vercel deployment
Installs
Security Audit
View Source
b-open-io/prompts
More from this source
Power your AI Agents with
the best open-source models.
Drop-in OpenAI-compatible API. No data leaves Europe.
Explore Inference APIGLM
GLM 5
$1.00 / $3.20
per M tokens
Kimi
Kimi K2.5
$0.60 / $2.80
per M tokens
MiniMax
MiniMax M2.5
$0.30 / $1.20
per M tokens
Qwen
Qwen3.5 122B
$0.40 / $3.00
per M tokens
How to use this skill
Install mcp-apps by running npx skills add b-open-io/prompts --skill mcp-apps in your project directory. Run the install command above in your project directory. The skill file will be downloaded from GitHub and placed in your project.
No configuration needed. Your AI agent (Claude Code, Cursor, Windsurf, etc.) automatically detects installed skills and uses them as context when generating code.
The skill enhances your agent's understanding of mcp-apps, helping it follow established patterns, avoid common mistakes, and produce production-ready output.
What you get
Skills are plain-text instruction files — not executable code. They encode expert knowledge about frameworks, languages, or tools that your AI agent reads to improve its output. This means zero runtime overhead, no dependency conflicts, and full transparency: you can read and review every instruction before installing.
Compatibility
This skill works with any AI coding agent that supports the skills.sh format, including Claude Code (Anthropic), Cursor, Windsurf, Cline, Aider, and other tools that read project-level context files. Skills are framework-agnostic at the transport level — the content inside determines which language or framework it applies to.
Chat with 100+ AI Models in one App.
Use Claude, ChatGPT, Gemini alongside with EU-Hosted Models like Deepseek, GLM-5, Kimi K2.5 and many more.