Skip to main content

create-mcp-app

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", or needs guidance on MCP Apps SDK patterns, UI-resource registration, MCP App lifecycle, or host integration. Provides guidance for building MCP Apps with interactive UIs.

معلومات المصدر

المستودع
sammcj/agentic-coding
آخر نشاط في المصدر
١١ فبراير ٢٠٢٦ في ٠٠:٠٣
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
١٦٢
التفرعات
٢٤

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
name
create-mcp-app
description
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", or needs guidance on MCP Apps SDK patterns, UI-resource registration, MCP App lifecycle, or host integration. Provides guidance for building MCP Apps with interactive UIs.
# Create MCP App 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. ## Core Concept: Tool + Resource Every MCP App requires two parts linked together: 1. **Tool** - Called by the LLM/host, returns data 2. **Resource** - Serves the bundled HTML UI that displays the data 3. **Link** - The tool's `_meta.ui.resourceUri` references the resource ``` Host calls tool → Server returns result → Host renders resource UI → UI receives result ``` ## Quick Start Decision Tree ### Framework Selection | 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 | Note that if the user prefers to write MCP Servers in Golang or Rust you should ask the user if they have a specific framework in mind for that language and if not help them search for a suitable framework / library that supports MCP Apps. ### Project Context **Adding to existing MCP server:** - Import `registerAppTool`, `registerAppResource` from SDK - Add tool registration with `_meta.ui.resourceUri` - Add resource registration serving bundled HTML **Creating new MCP server:** - Set up server with transport (stdio or HTTP) - Register tools and resources - Configure build system with `vite-plugin-singlefile` ## Getting Reference Code Clone the SDK repository for working examples and API documentation: ```bash git clone --branch "v$(npm view @modelcontextprotocol/ext-apps version)" --depth 1 https://github.com/modelcontextprotocol/ext-apps.git /tmp/mcp-ext-apps ``` ### Framework Templates 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: - Complete `server.ts` with `registerAppTool` and `registerAppResource` - Client-side app with all lifecycle handlers - `vite.config.ts` with `vite-plugin-singlefile` - `package.json` with all required dependencies - `.gitignore` excluding `node_modules/` and `dist/` ### API Reference (Source Files) Read JSDoc documentation directly from `/tmp/mcp-ext-apps/src/`: | File | Contents | |------|----------| | `src/app.ts` | `App` class, handlers (`ontoolinput`, `ontoolresult`, `onhostcontextchanged`, `onteardown`), lifecycle | | `src/server/index.ts` | `registerAppTool`, `registerAppResource`, tool visibility options | | `src/spec.types.ts` | All type definitions: `McpUiHostContext`, CSS variable keys, display modes | | `src/styles.ts` | `applyDocumentTheme`, `applyHostStyleVariables`, `applyHostFonts` | | `src/react/useApp.tsx` | `useApp` hook for React apps | | `src/react/useHostStyles.ts` | `useHostStyles`, `useHostStyleVariables`, `useHostFonts` hooks | ### Advanced Examples | Example | Pattern Demonstrated | |---------|---------------------| | `examples/shadertoy-server/` | **Streaming partial input** + visibility-based pause/play (best practice for large inputs) | | `examples/wiki-explorer-server/` | `callServerTool` for interactive data fetching | | `examples/system-monitor-server/` | Polling pattern with interval management | | `examples/video-resource-server/` | Binary/blob resources | | `examples/sheet-music-server/` | `ontoolinput` - processing tool args before execution completes | | `examples/threejs-server/` | `ontoolinputpartial` - streaming/progressive rendering | | `examples/map-server/` | `updateModelContext` - keeping model informed of UI state | | `examples/transcript-server/` | `updateModelContext` + `sendMessage` - background context updates + user-initiated messages | | `examples/basic-host/` | Reference host implementation using `AppBridge` | ## Critical Implementation Notes ### Adding Dependencies Use `npm install` to add dependencies rather than manually writing version numbers: ```bash npm install @modelcontextprotocol/ext-apps @modelcontextprotocol/sdk zod ``` This lets npm resolve the latest compatible versions. Never specify version numbers from memory. ### TypeScript Server Execution Use `tsx` as a devDependency for running TypeScript server files: ```bash npm install -D tsx ``` ```json "scripts": { "serve": "tsx server.ts" } ``` Note: The SDK examples use `bun` but generated projects should use `tsx` for broader compatibility. ### Handler Registration Order Register ALL handlers BEFORE calling `app.connect()`: ```typescript const 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 {}; }; // Then connect await app.connect(); ``` ### Tool Visibility Control who can access tools via `_meta.ui.visibility`: ```typescript // Default: visible to both model and app _meta: { ui: { resourceUri, visibility: ["model", "app"] } } // UI-only (hidden from model) - for refresh buttons, form submissions _meta: { ui: { resourceUri, visibility: ["app"] } } // Model-only (app cannot call) _meta: { ui: { resourceUri, visibility: ["model"] } } ``` ### Host Styling Integration **Vanilla JS** - Use helper functions: ```typescript import { applyDocumentTheme, applyHostStyleVariables, applyHostFonts } from "@modelcontextprotocol/ext-apps"; app.onhostcontextchanged = (ctx) => { if (ctx.theme) applyDocumentTheme(ctx.theme); if (ctx.styles?.variables) applyHostStyleVariables(ctx.styles.variables); if (ctx.styles?.css?.fonts) applyHostFonts(ctx.styles.css.fonts); }; ``` **React** - Use hooks: ```typescript import { useApp, useHostStyles } from "@modelcontextprotocol/ext-apps/react"; const { app } = useApp({ appInfo, capabilities, onAppCreated }); useHostStyles(app); // Injects CSS variables to document, making var(--*) available ``` **Using variables in CSS** - After applying, use `var()`: ```css .container { background: var(--color-background-secondary); color: var(--color-text-primary); font-family: var(--font-sans); border-radius: var(--border-radius-md); } .code { font-family: var(--font-mono); font-size: var(--font-text-sm-size); line-height: var(--font-text-sm-line-height); color: var(--color-text-secondary); } .heading { font-size: var(--font-heading-lg-size); font-weight: var(--font-weight-semibold); } ``` Key variable groups: `--color-background-*`, `--color-text-*`, `--color-border-*`, `--font-sans`, `--font-mono`, `--font-text-*-size`, `--font-heading-*-size`, `--border-radius-*`. See `src/spec.types.ts` for full list. ### Safe Area Handling Always respect `safeAreaInsets`: ```typescript app.onhostcontextchanged = (ctx) => { if (ctx.safeAreaInsets) { const { top, right, bottom, left } = ctx.safeAreaInsets; document.body.style.padding = `${top}px ${right}px ${bottom}px ${left}px`; } }; ``` ### Streaming Partial Input For large tool inputs, use `ontoolinputpartial` to show progress during LLM generation. The partial JSON is healed (always valid), enabling progressive UI updates. **Spec:** [ui/notifications/tool-input-partial](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/2026-01-26/apps.mdx#streaming-tool-input) ```typescript app.ontoolinputpartial = (params) => { const args = params.arguments; // Healed partial JSON - always valid, fields appear as generated // Use args directly for progressive rendering }; app.ontoolinput = (params) => { // Final complete input - switch from preview to full render }; ``` **Use cases:** | Pattern | Example | |---------|---------| | Code preview | Show streaming code in `<pre>`, render on complete (`examples/shadertoy-server/`) | | Progressive form | Fill form fields as they stream in | | Live chart | Add data points to chart as array grows | | Partial render | Render incomplete structured data (tables, lists, trees) | **Simple pattern (code preview):** ```typescript app.ontoolinputpartial = (params) => { codePreview.textContent = params.arguments?.code ?? ""; codePreview.style.display = "block"; canvas.style.display = "none"; }; app.ontoolinput = (params) => { codePreview.style.display = "none"; canvas.style.display = "block"; render(params.arguments); }; ``` ### Visibility-Based Resource Management Pause expensive operations (animations, WebGL, polling) when view scrolls out of viewport: ```typescript const observer = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { animation.play(); // or: startPolling(), shaderToy.play() } else { animation.pause(); // or: stopPolling(), shaderToy.pause() } }); }); observer.observe(document.querySelector(".main")); ``` ### Fullscreen Mode Request fullscreen via `app.requestDisplayMode()`. Check availability in host context: ```typescript let currentMode: "inline" | "fullscreen" = "inline"; app.onhostcontextchanged = (ctx) => { // Check if fullscreen available if (ctx.availableDisplayModes?.includes("fullscreen")) { fullscreenBtn.style.display = "block"; } // Track current mode if (ctx.displayMode) { currentMode = ctx.displayMode; container.classList.toggle("fullscreen", currentMode === "fullscreen"); } }; async function toggleFullscreen() { const newMode = currentMode === "fullscreen" ? "inline" : "fullscreen"; const result = await app.requestDisplayMode({ mode: newMode }); currentMode = result.mode; } ``` **CSS pattern** - Remove border radius in fullscreen: ```css .main { border-radius: var(--border-radius-lg); overflow: hidden; } .main.fullscreen { border-radius: 0; } ``` See `examples/shadertoy-server/` for complete implementation. ## Common Mistakes to Avoid 1. **Handlers after connect()** - Register ALL handlers BEFORE calling `app.connect()` 2. **Missing single-file bundling** - Must use `vite-plugin-singlefile` 3. **Forgetting resource registration** - Both tool AND resource must be registered 4. **Missing resourceUri link** - Tool must have `_meta.ui.resourceUri` 5. **Ignoring safe area insets** - Always handle `ctx.safeAreaInsets` 6. **No text fallback** - Always provide `content` array for non-UI hosts
عرض على GitHub
ملف SKILL.md هذا كبير جدا، لذلك يعرض SkillsMP القسم الاول فقط هنا. عرض على GitHub