skills/mcp-apps/SKILL.md
Build MCP Apps - interactive UI components that render inside AI hosts like Claude, ChatGPT, and VSCode. This skill should be used when creating tools with rich UI, building dashboards or forms for AI interactions, or adding visual interfaces to MCP servers.
npx skillsauth add aussiegingersnap/cursor-skills mcp-appsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Build interactive UI applications that render directly inside MCP hosts like Claude Desktop, ChatGPT, and VSCode. MCP Apps extend the Model Context Protocol to return rich interfaces instead of plain text.
MCP Apps let tools return rich, interactive interfaces instead of plain text. When a tool declares a UI resource, the host renders it in a sandboxed iframe, and users interact with it directly in the conversation.
Key benefits over regular web apps:
┌─────────────────────────────────────────────────────────────┐
│ MCP Host (Claude, VSCode) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Sandboxed Iframe │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ Your MCP App UI │ │ │
│ │ │ (React, Vue, Svelte, or vanilla) │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ postMessage │ │
│ │ │ │ │
│ └─────────────────────────┼────────────────────────────┘ │
│ │ │
│ JSON-RPC │
│ │ │
│ ┌─────────────────────────┼────────────────────────────┐ │
│ │ MCP Server │ │
│ │ • Tools with _meta.ui.resourceUri │ │
│ │ • UI Resources (bundled HTML) │ │
│ └───────────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────┘
_meta.ui.resourceUri pointing to a ui:// resource# Scaffold a new MCP App project
python scripts/init_mcp_app.py my-mcp-app --path ./projects
This creates a complete project structure with server, UI, and configuration.
npm install @modelcontextprotocol/ext-apps @modelcontextprotocol/sdk
npm install -D typescript vite vite-plugin-singlefile express cors @types/express @types/cors tsx
my-mcp-app/
├── package.json
├── tsconfig.json
├── vite.config.ts
├── server.ts # MCP server
├── mcp-app.html # UI entry point
└── src/
└── mcp-app.ts # UI logic
See references/boilerplate.md for complete file contents.
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server";
const server = new McpServer({
name: "My MCP App Server",
version: "1.0.0",
});
// The ui:// scheme tells hosts this is an MCP App resource
const resourceUri = "ui://my-tool/app.html";
// Register the tool with UI metadata
registerAppTool(
server,
"my-tool",
{
title: "My Tool",
description: "A tool with interactive UI",
inputSchema: {
type: "object",
properties: {
query: { type: "string" }
}
},
_meta: {
ui: { resourceUri }
}
},
async (args) => {
// Tool logic here
return {
content: [{ type: "text", text: JSON.stringify(args) }]
};
}
);
// Register the UI resource
registerAppResource(
server,
resourceUri,
resourceUri,
{ mimeType: RESOURCE_MIME_TYPE },
async () => {
const html = await fs.readFile("dist/mcp-app.html", "utf-8");
return {
contents: [{ uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html }]
};
}
);
import express from "express";
import cors from "cors";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
const app = express();
app.use(cors());
app.use(express.json());
app.post("/mcp", async (req, res) => {
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
enableJsonResponse: true,
});
res.on("close", () => transport.close());
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
});
app.listen(3001, () => {
console.log("Server listening on http://localhost:3001/mcp");
});
import { App } from "@modelcontextprotocol/ext-apps";
const app = new App({ name: "My App", version: "1.0.0" });
// Connect to host
await app.connect();
// Receive initial tool result
app.ontoolresult = (result) => {
const data = result.content?.find((c) => c.type === "text")?.text;
console.log("Received:", data);
renderUI(data);
};
// Call server tools from UI
async function fetchData() {
const result = await app.callServerTool({
name: "fetch-data",
arguments: { query: "example" },
});
return result.content?.find((c) => c.type === "text")?.text;
}
// Update model context
async function notifyModel(info: string) {
await app.updateModelContext({
content: [{ type: "text", text: info }],
});
}
// Log for debugging
app.log("info", "App initialized");
// Open links in user's browser
app.openLink("https://example.com");
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>My MCP App</title>
<style>
body { font-family: system-ui, sans-serif; padding: 1rem; }
/* Add your styles */
</style>
</head>
<body>
<div id="app">Loading...</div>
<script type="module" src="/src/mcp-app.ts"></script>
</body>
</html>
import { defineConfig } from "vite";
import { viteSingleFile } from "vite-plugin-singlefile";
export default defineConfig({
plugins: [viteSingleFile()],
build: {
outDir: "dist",
rollupOptions: {
input: process.env.INPUT,
},
},
});
{
"type": "module",
"scripts": {
"build": "INPUT=mcp-app.html vite build",
"serve": "npx tsx server.ts",
"dev": "npm run build && npm run serve"
}
}
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"outDir": "dist"
},
"include": ["*.ts", "src/**/*.ts"]
}
Build and start your server:
npm run build && npm run serve
Expose locally with cloudflared:
npx cloudflared tunnel --url http://localhost:3001
Add as custom connector in Claude:
Chat with Claude and trigger your tool.
The ext-apps repo includes a test host:
git clone https://github.com/modelcontextprotocol/ext-apps
cd ext-apps/examples/basic-host
npm install
SERVERS='["http://localhost:3001/mcp"]' npm start
Navigate to http://localhost:8080 to test.
MCP Apps run in sandboxed iframes with:
registerAppTool(server, "camera-tool", {
// ...
_meta: {
ui: {
resourceUri: "ui://camera/app.html",
permissions: ["camera", "microphone"], // Request additional permissions
csp: ["https://cdn.example.com"] // Allow external resources
}
}
});
MCP Apps work with any web framework. The ext-apps repo has starters for:
See references/patterns.md for framework-specific patterns.
app.ontoolresult = (result) => {
renderForm(result.content);
};
async function handleSubmit(formData: FormData) {
const result = await app.callServerTool({
name: "submit-form",
arguments: Object.fromEntries(formData),
});
if (result.isError) {
showError(result.content);
} else {
showSuccess(result.content);
await app.updateModelContext({
content: [{ type: "text", text: "User submitted form successfully" }],
});
}
}
let intervalId: number;
app.ontoolresult = async () => {
// Start polling for updates
intervalId = setInterval(async () => {
const result = await app.callServerTool({
name: "get-metrics",
arguments: {},
});
updateDashboard(result.content);
}, 5000);
};
// Clean up on disconnect
window.addEventListener("beforeunload", () => {
clearInterval(intervalId);
});
let currentStep = 0;
const steps = ["configure", "review", "confirm"];
async function nextStep(data: unknown) {
const result = await app.callServerTool({
name: `workflow-${steps[currentStep]}`,
arguments: data,
});
currentStep++;
if (currentStep < steps.length) {
renderStep(currentStep, result.content);
} else {
await app.updateModelContext({
content: [{ type: "text", text: "Workflow completed" }],
});
renderComplete(result.content);
}
}
_meta.ui.resourceUri in descriptionapp.connect() is called before other operationsreferences/boilerplate.md - Complete project setup codereferences/patterns.md - Common UI patterns and examplestools
# Versioning Skill Semantic versioning automation based on conventional commits. Automatically manages version bumps, changelogs, and git tags using `standard-version`. ## When to Use - Before releasing a new version - When preparing a deployment - To generate/update CHANGELOG.md - When the user asks about version management - Setting up versioning for a new project ## Prerequisites - Conventional commits enforced (recommended: lefthook) - Node.js project with package.json ## Setup (One-Ti
tools
Theme generation with tweakcn for shadcn/ui and Magic UI animations. Use when setting up project themes, customizing color schemes, adding dark mode, or integrating animated components.
tools
shadcn/studio component library with MCP integration, theme generation, and block patterns. This skill should be used when building UI with shadcn components, selecting dashboard layouts, or generating landing pages. Canonical source for all shadcn-based work.
development
Enforce a precise, minimal design system inspired by Linear, Notion, and Stripe. Use this skill when building dashboards, admin interfaces, or any UI that needs Jony Ive-level precision - clean, modern, minimalist with taste. Every pixel matters.