smithery.ai

mcp-apps

Build MCP Apps with interactive UIs that render inside MCP-enabled hosts (Claude, ChatGPT, VS Code, Goose).

First seen Apr 7, 2026

Installation

$ npx skills add https://smithery.ai

Summary

  • Build MCP Apps with interactive UIs that render inside MCP-enabled hosts (Claude, ChatGPT, VS Code, Goose).
  • Use when asked to "create an MCP App", "add UI to an MCP tool", "build interactive MCP view", "scaffold MCP App", or when implementing tools that need rich interfaces like dashboards, forms, visualizations, or multi-step workflows.
  • Covers the @modelcontextprotocol/ext-apps SDK, tool+resource pattern, host styling, streaming input, and framework integration (React, Vanilla JS, Vue, Svelte).

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from smithery.ai · top by installs.

npx skills add https://smithery.ai

Browse all from smithery.ai

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Skill metadata

Parsed from SKILL.md frontmatter.

Declared agents claude-code goose

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 5,960 B
  • docs SUMMARY.md 516 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 1 installs

SKILL.md

MCP Apps

Build interactive UIs that run inside MCP-enabled hosts. An MCP App combines an MCP tool with an HTML resource.

Core Concept

Every MCP App requires two linked parts:

Host calls tool → Server returns result → Host renders resource UI → UI receives result
  1. Tool - Called by LLM/host, returns data
  2. Resource - Serves bundled HTML UI (via ui:// scheme)
  3. Link - Tool's _meta.ui.resourceUri references the resource

Get Reference Code

Clone the SDK for working examples:

git clone --branch "v$(npm view @modelcontextprotocol/ext-apps version)" --depth 1 https://github.com/modelcontextprotocol/ext-apps.git /tmp/mcp-ext-apps

Templates (/tmp/mcp-ext-apps/examples/)

Template Use For
basic-server-react/ React apps with useApp hook
basic-server-vanillajs/ Simple apps, no framework
basic-server-vue/ Vue apps
basic-server-svelte/ Svelte apps

API Reference (/tmp/mcp-ext-apps/src/)

File Contents
app.ts App class, handlers, lifecycle
server/index.ts registerAppTool, registerAppResource
react/useApp.tsx React hook
styles.ts Host styling helpers

Implementation Checklist

Server Side

  1. Install dependencies:

``bash npm install @modelcontextprotocol/ext-apps @modelcontextprotocol/sdk zod npm install -D tsx vite vite-plugin-singlefile ``

  1. Register tool with UI metadata:

```typescript import { registerAppTool, registerAppResource } from "@modelcontextprotocol/ext-apps/server";

registerAppTool(server, { name: "mytool", description: "Tool description", inputSchema: zodToJsonSchema(MyInputSchema), meta: { ui: { resourceUri: "ui://my-app/main", visibility: ["model", "app"] // or ["app"] for UI-only } }, handler: async (args) => ({ content: [{ type: "text", text: "Fallback for non-UI hosts" }], data: args // Passed to UI via ontoolresult }) });

registerAppResource(server, { uri: "ui://my-app/main", name: "My App UI", mimeType: "text/html", async handler() { return fs.readFileSync("./dist/mcp-app.html", "utf-8"); } }); ```

Client Side (UI)

Register handlers BEFORE connect():

import { App } from "@modelcontextprotocol/ext-apps";

const app = new App({ name: "My App", version: "1.0.0" });

// Tool input (args before execution)
app.ontoolinput = (params) => {
  console.log("Input:", params.arguments);
};

// Tool result (after execution)
app.ontoolresult = (result) => {
  renderUI(result.data);
};

// Host context changes (theme, safe area)
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`;
  }
};

// Cleanup
app.onteardown = async () => ({ state: {} });

await app.connect();

Build Config

Use vite-plugin-singlefile to bundle into one HTML file:

// vite.config.ts
import { defineConfig } from "vite";
import { viteSingleFile } from "vite-plugin-singlefile";

export default defineConfig({
  plugins: [viteSingleFile()],
  build: {
    outDir: "dist",
    rollupOptions: {
      input: "mcp-app.html"
    }
  }
});

Key Patterns

Host Styling

Use CSS variables from host context:

import { applyDocumentTheme, applyHostStyleVariables } from "@modelcontextprotocol/ext-apps";

app.onhostcontextchanged = (ctx) => {
  if (ctx.theme) applyDocumentTheme(ctx.theme);
  if (ctx.styles?.variables) applyHostStyleVariables(ctx.styles.variables);
};
.container {
  background: var(--color-background-secondary);
  color: var(--color-text-primary);
  font-family: var(--font-sans);
}

Streaming Partial Input

Show progress during LLM generation:

app.ontoolinputpartial = (params) => {
  // Healed partial JSON - always valid
  preview.textContent = JSON.stringify(params.arguments, null, 2);
};

app.ontoolinput = (params) => {
  // Final complete input
  render(params.arguments);
};

Call Server Tools from UI

const response = await app.callServerTool({
  name: "fetch_details",
  arguments: { id: "123" }
});

Update Model Context

Keep the model informed of UI state:

await app.updateModelContext({
  content: [{ type: "text", text: "User selected option B" }]
});

Tool Visibility

// Both model and app can call (default)
visibility: ["model", "app"]

// UI-only (hidden from model) - for refresh buttons, form submissions
visibility: ["app"]

// Model-only
visibility: ["model"]

Testing

Use the basic-host example:

# Terminal 1: Your server
npm run build && npm run serve

# Terminal 2: Test host
cd /tmp/mcp-ext-apps/examples/basic-host
npm install
SERVERS='["http://localhost:3001/mcp"]' npm run start
# Open http://localhost:8080

Common Mistakes

  1. Handlers after connect() - Register ALL handlers BEFORE app.connect()
  2. Missing vite-plugin-singlefile - Required for bundling
  3. No resourceUri link - Tool must have _meta.ui.resourceUri
  4. Ignoring safe area insets - Always handle ctx.safeAreaInsets
  5. Hardcoded styles - Use host CSS variables