diff --git a/apps/mcp/README.md b/apps/mcp/README.md index c4a6c43..d3346f3 100644 --- a/apps/mcp/README.md +++ b/apps/mcp/README.md @@ -120,6 +120,8 @@ Then start the HTTP server with `pnpm dev`. Wraps an HTML fragment with the complete OpenGenerativeUI design system. +In [MCP Apps](https://github.com/modelcontextprotocol/ext-apps) hosts such as Claude Desktop, the tool is linked to the `ui://open-generative-ui/document.html` view, so the fragment renders inline in the conversation. The view applies the design system and the host's theme, runs the fragment's scripts in order, and maps `sendPrompt(text)` and `openLink(url)` to the host. The tool result is then a one-line confirmation, which keeps the document out of the model's context. Clients that do not advertise the `io.modelcontextprotocol/ui` extension receive the complete document as shown below. + **Input:** ```typescript { diff --git a/apps/mcp/package.json b/apps/mcp/package.json index 5dd31a3..6d10310 100644 --- a/apps/mcp/package.json +++ b/apps/mcp/package.json @@ -23,7 +23,7 @@ "license": "MIT", "dependencies": { "@hono/node-server": "^1", - "@modelcontextprotocol/sdk": "^1.25.3", + "@modelcontextprotocol/sdk": "^1.32.1", "@repo/design-system": "workspace:*", "hono": "^4", "zod": "^3.23" diff --git a/apps/mcp/src/renderer.ts b/apps/mcp/src/renderer.ts index 8e1400b..81327fa 100644 --- a/apps/mcp/src/renderer.ts +++ b/apps/mcp/src/renderer.ts @@ -3,6 +3,21 @@ import { THEME_CSS, SVG_CLASSES_CSS, FORM_STYLES_CSS } from "@repo/design-system"; +// Origins generated UI may load scripts and assets from, in both rendering paths. +export const CDN_ORIGINS = [ + "https://cdnjs.cloudflare.com", + "https://esm.sh", + "https://cdn.jsdelivr.net", + "https://unpkg.com", +]; + +export const MCP_APP_MIME_TYPE = "text/html;profile=mcp-app"; + +// ponytail: the view loads the MCP Apps SDK from jsDelivr when it opens, so rendering +// needs network access; inline app-with-deps.js into the view if offline hosts matter. +const EXT_APPS_SDK_URL = + "https://cdn.jsdelivr.net/npm/@modelcontextprotocol/ext-apps@2.0.3/dist/src/app-with-deps.js"; + const BRIDGE_JS = ` window.sendPrompt = function(text) { window.parent.postMessage({ type: 'send-prompt', text: text }, '*'); @@ -37,11 +52,7 @@ export function assembleDocument(html: string): string { `; } + +// MCP Apps view for assemble_document. The host renders it in a sandboxed iframe +// and delivers the tool arguments, so the fragment arrives as args.html. +export const MCP_APP_VIEW_HTML = ` + + + + + + + + +
+ + +`; diff --git a/apps/mcp/src/server.ts b/apps/mcp/src/server.ts index a2caf8a..ab210d3 100644 --- a/apps/mcp/src/server.ts +++ b/apps/mcp/src/server.ts @@ -4,7 +4,15 @@ import { } from "@modelcontextprotocol/sdk/server/mcp.js"; import * as z from "zod"; import { listSkills, loadSkill } from "./skills.js"; -import { assembleDocument } from "./renderer.js"; +import { + assembleDocument, + CDN_ORIGINS, + MCP_APP_MIME_TYPE, + MCP_APP_VIEW_HTML, +} from "./renderer.js"; + +const VIEW_URI = "ui://open-generative-ui/document.html"; +const UI_EXTENSION = "io.modelcontextprotocol/ui"; export function createMcpServer(): McpServer { const server = new McpServer({ @@ -97,13 +105,39 @@ export function createMcpServer(): McpServer { }), ); - // Tool: assemble complete HTML document with design system + // MCP Apps view: hosts with the ui extension render assemble_document inline + server.registerResource( + "document-view", + VIEW_URI, + { + description: "Renders assemble_document fragments inline in MCP Apps hosts", + mimeType: MCP_APP_MIME_TYPE, + }, + async () => ({ + contents: [ + { + uri: VIEW_URI, + mimeType: MCP_APP_MIME_TYPE, + text: MCP_APP_VIEW_HTML, + _meta: { + ui: { + prefersBorder: false, + csp: { resourceDomains: CDN_ORIGINS, connectDomains: CDN_ORIGINS }, + }, + }, + }, + ], + }), + ); + + // Tool: render (MCP Apps hosts) or assemble (other clients) a design-system document server.registerTool( "assemble_document", { description: - "Wraps HTML with Open Intelligent UI theme CSS, SVG classes, form styles, and bridge JS. " + - "Returns a complete iframe-ready HTML document.", + "Renders an HTML fragment for the user with Open Intelligent UI theme CSS variables, " + + "SVG c-* classes, form styles, and sendPrompt(text) / openLink(url) globals. " + + "MCP Apps hosts show it inline; other clients get the complete iframe-ready HTML document.", inputSchema: { title: z.string().describe("Short title for the visualization"), description: z @@ -115,10 +149,19 @@ export function createMcpServer(): McpServer { "Self-contained HTML fragment with inline