digitarald/mcp-apps-playground/mcp-apps

Claude Code and agent skill by digitarald/mcp-apps-playground

Trust tier 2
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Build MCP Apps - interactive HTML UIs rendered in chat via the MCP Apps Extension (SEP-1865). Use when creating MCP tools that need rich UI beyond text, adding interactive forms/visualizations to MCP servers, configuring structuredContent for UI data passing, or debugging MCP UI rendering issues. Keywords: MCP Apps, SEP-1865, ui://, text/html;profile=mcp-app, structuredContent, postMessage, registerTool _meta.

Install

npx vskill@latest install digitarald/mcp-apps-playground/mcp-apps

SKILL.md

MCP Apps Development

MCP Apps let MCP tools render interactive HTML UIs in chat. The UI runs in a sandboxed iframe and communicates via JSON-RPC postMessage.

External References

  • Spec: https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx
  • Blog: https://blog.modelcontextprotocol.io/posts/2025-11-21-mcp-apps/
  • VS Code impl: https://github.com/microsoft/vscode (search chatMcpAppModel.ts)

Quick Start

1. Register UI Resource
typescript
server.resource(
"my-ui",
"ui://my-server/my-tool", // ui:// scheme required
{ mimeType: "text/html;profile=mcp-app" }, // profile required
async (uri) => ({
contents: [{
uri: uri.href,
mimeType: "text/html;profile=mcp-app", // must match
text: MY_UI_HTML()
}]
})
);
2. Register Tool with UI Link
typescript
server.registerTool("my_tool", {
inputSchema: { query: z.string() },
_meta: { ui: { resourceUri: "ui://my-server/my-tool" } }
}, async ({ query }) => ({
content: [{ type: "text", text: `Result: ${query}` }],
structuredContent: { query, result: "..." } // sent to UI
}));
3. UI Template (Static HTML)
typescript
export function MY_UI_HTML(): string {
return `<!DOCTYPE html>
<html><body>
<div id="content">Loading...</div>
<script>
const pending = new Map();
let nextId = 1;
function request(method, params) {
const id = nextId++;
return new Promise((resolve, reject) => {
pending.set(id, { resolve, reject });
window.parent.postMessage({ jsonrpc: '2.0', id, method, params }, '*');
});
}
window.addEventListener('message', (e) => {
const msg = e.data;
if (!msg?.jsonrpc) return;
// Handle responses
if (msg.id && pending.has(msg.id)) {
const { resolve, reject } = pending.get(msg.id);
pending.delete(msg.id);
msg.error ? reject(msg.error) : resolve(msg.result);
return;
}
// Handle notifications
if (msg.method === 'ui/notifications/tool-input') {
const { arguments: args } = msg.params;
document.getElementById('content').textContent = JSON.stringify(args);
}
});
// Initialize handshake
request('ui/initialize', { protocolVersion: '2025-11-21' }).then(() => {
window.parent.postMessage({ jsonrpc: '2.0', method: 'ui/notifications/initialized' }, '*');
});
<\/script>
</body></html>`;
}

Critical Requirements

RequirementWrongRight
MIME typetext/htmltext/html;profile=mcp-app
URI schemehttps://, file://ui://
Tool registrationserver.tool()server.registerTool() with _meta
Data passingTemplate parametersui/notifications/tool-input
External fetch/CDNJust call fetch()Declare domains in _meta.ui.csp
VS Code settingJust enableEnable chat.mcp.apps.enabled + restart

UI Lifecycle

UI → Host: ui/initialize (request)
Host → UI: response { hostContext }
UI → Host: ui/notifications/initialized (notification)
Host → UI: ui/notifications/tool-input { arguments, structuredContent }
Host → UI: ui/notifications/tool-result { content, structuredContent }

Critical: Complete ui/initialize handshake before expecting data.

Host Notifications (UI receives)

MethodWhenParams
ui/notifications/tool-inputTool args ready{ arguments }
ui/notifications/tool-input-partialStreamingPartial args
ui/notifications/tool-resultTool complete{ content, structuredContent }
ui/notifications/tool-cancelledCancelled{ reason }
ui/notifications/host-context-changedTheme/size changeContext object

UI Requests (UI sends)

MethodPurposeParams
ui/initializeStart handshake{ protocolVersion, capabilities }
ui/messageSend text to chat input{ content: [{ type: 'text', text }] }
tools/callCall another MCP tool{ name, arguments }
resources/readRead MCP resource{ uri }

Common Patterns

Theme Integration
javascript
function applyHostContext(ctx) {
if (ctx.theme) document.documentElement.style.colorScheme = ctx.theme;
if (ctx.styles?.variables) {
for (const [k, v] of Object.entries(ctx.styles.variables)) {
document.documentElement.style.setProperty(k, v);
}
}
}
Send Results to Chat
javascript
async function sendToChat(text) {
await request('ui/message', {
content: [{ type: 'text', text }]
});
}
XSS Prevention
javascript
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}

Advanced Patterns

For complex UI interactions beyond basic tool→UI data flow, load references/patterns.md. Topics include:

PatternWhen to use
Awaiting User InputTool needs to wait for user interaction before returning (forms, selections, confirmations)
Calling Other ToolsUI needs to invoke additional MCP tools
Resource MetadataBorder preferences, sizing hints
Loading External ResourcesConfigure CSP via _meta.ui.csp to allow external APIs, CDNs, fonts, embeds
Reference ExamplesProduction patterns from ext-apps repo (charts, graphs, React, vanilla JS)

Gotchas

  1. Templates are static - Don't pass data via template string interpolation. Data arrives via tool-input notification.
  1. VS Code capability detection - VS Code uses chat.mcp.apps.enabled setting, NOT protocol capability negotiation. Don't rely on extensions["io.modelcontextprotocol/ui"].
  1. Script escaping - Use <\/script> in template strings to avoid breaking the outer script.
  1. Rebuild + restart - After code changes: npm run build then restart MCP server in VS Code.
  1. MIME type duplication - Must set text/html;profile=mcp-app in BOTH resource options AND contents array. Missing either causes silent failure.
  1. External resources blocked by default - UI runs with strict CSP. To use external APIs/CDNs, you MUST declare allowed domains in _meta.ui.csp. See patterns.md.

Debugging

Check server log file (e.g., /tmp/mcp-server.log):

  • 📱 resources/read → VS Code fetched UI (working)
  • Only 🔧 Tool called → Setting not enabled or needs restart

See references/debugging.md for logging setup and references/troubleshooting.md for common issues.

File Structure

src/
index.ts # Server: tool + resource registration
ui/
my-tool.ts # Export function returning HTML string
Reference Files

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
61
Repository forks
12
7-day trend score
21

Stars and forks are for the whole GitHub repository, not this skill alone. Refreshed 27 Mar 2026, 7 months ago.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/digitarald/mcp-apps-playground/mcp-apps/badge)](https://verified-skill.com/skills/digitarald/mcp-apps-playground/mcp-apps)

Something wrong with this skill or its page? Tell us.