mountly-mcp 4.1.0 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -155,6 +155,40 @@ npx mountly-mcp add settings --framework react
155
155
 
156
156
  [GLOSSARY.md](./GLOSSARY.md). Protocol: [`@modelcontextprotocol/ext-apps`](https://www.npmjs.com/package/@modelcontextprotocol/ext-apps).
157
157
 
158
+ ## Compose from teams' elements
159
+
160
+ `mountly-mcp/compose` lets the agent build a page from many teams' custom
161
+ elements, using a [`mountly-compose`](../mountly-compose/README.md) catalog read
162
+ from their `custom-elements.json`:
163
+
164
+ ```ts
165
+ import { loadCatalog } from "mountly-compose";
166
+ import { registerComposeApp } from "mountly-mcp/compose";
167
+
168
+ registerComposeApp(server, {
169
+ catalog: await loadCatalog("https://cdn.acme.com/registry.json", { actions }),
170
+ imports: {
171
+ "mountly-compose": "https://cdn.acme.com/mountly-compose.js",
172
+ "@modelcontextprotocol/ext-apps": "https://cdn.acme.com/ext-apps/app-with-deps.js",
173
+ react: "…",
174
+ "react/jsx-runtime": "…",
175
+ "react-dom/client": "…",
176
+ "mountly-react": "…",
177
+ "mountly/embed": "…",
178
+ },
179
+ });
180
+ ```
181
+
182
+ The catalog becomes `show_page`'s description, and the agent composes the page.
183
+ The handler validates the page and repairs small mistakes. When a page needs
184
+ more than a repair, the handler returns the reasons as a tool error for the
185
+ agent to fix. The `ui://` view renders with `mountly-compose` and loads each
186
+ team's embed on first use. Its CSP allows the origins in `imports` and the
187
+ teams' embed origins. The view runs at a `null` origin, so the servers behind
188
+ those URLs send `Access-Control-Allow-Origin`. When the user acts in a widget,
189
+ the view sends the agent its next turn with the current page. The
190
+ [`ai-compose`](../../docs/examples/ai-compose) example runs this in `mcp/`.
191
+
158
192
  ## Advanced: json-render
159
193
 
160
194
  `mountly-mcp/json-render` is optional catalog-driven UI. Start with `create` /
@@ -162,18 +196,19 @@ npx mountly-mcp add settings --framework react
162
196
 
163
197
  ## Exports
164
198
 
165
- | Entry | Contents |
166
- | ------------------------- | ------------------------------------ |
167
- | `mountly-mcp` | `runBridge`, `publishMcpView`, types |
168
- | `mountly-mcp/react` | `createMcpView` + hooks |
169
- | `mountly-mcp/vue` | `createMcpView` + composables |
170
- | `mountly-mcp/svelte` | `createMcpView` (props) |
171
- | `mountly-mcp/vite` | `mountlyMcpViews()` |
172
- | `mountly-mcp/artifact` | Manifest APIs |
173
- | `mountly-mcp/server` | `registerMcpApps` |
174
- | `mountly-mcp/dev` | Local host helpers |
175
- | `mountly-mcp/testing` | `verifyMcpApps` |
176
- | `mountly-mcp/json-render` | Generative path |
199
+ | Entry | Contents |
200
+ | ------------------------- | --------------------------------------- |
201
+ | `mountly-mcp` | `runBridge`, `publishMcpView`, types |
202
+ | `mountly-mcp/react` | `createMcpView` + hooks |
203
+ | `mountly-mcp/vue` | `createMcpView` + composables |
204
+ | `mountly-mcp/svelte` | `createMcpView` (props) |
205
+ | `mountly-mcp/vite` | `mountlyMcpViews()` |
206
+ | `mountly-mcp/artifact` | Manifest APIs |
207
+ | `mountly-mcp/server` | `registerMcpApps` |
208
+ | `mountly-mcp/dev` | Local host helpers |
209
+ | `mountly-mcp/testing` | `verifyMcpApps` |
210
+ | `mountly-mcp/json-render` | Generative path |
211
+ | `mountly-mcp/compose` | `registerComposeApp`, `composeViewHtml` |
177
212
 
178
213
  ## See also
179
214
 
@@ -579,4 +579,4 @@ export {
579
579
  startDevHost,
580
580
  sandboxProxyHtml
581
581
  };
582
- //# sourceMappingURL=chunk-KWVGW5Q7.js.map
582
+ //# sourceMappingURL=chunk-GN2IQYV3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/dev/index.ts"],"sourcesContent":["/**\n * A local MCP Apps host, so a View can be developed without installing a\n * real host and restarting it on every change.\n *\n * It is a genuine host, not a preview shim: two origins as the spec requires\n * (§8.4), a sandbox proxy that injects the sidecar's CSP and permissions, the\n * full `ui/initialize` handshake, and tool-input/tool-result delivery. A View\n * that works here works in Claude, and one that reaches for `eval` fails here\n * for the same reason it would fail there.\n *\n * ```ts\n * const host = await startDevHost({ htmlPath: \"dist/view.html\", fixtures });\n * console.log(host.hostUrl);\n * ```\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { createServer, type Server } from \"node:http\";\nimport { fileURLToPath } from \"node:url\";\nimport type { McpResourceDeclaration } from \"../types.js\";\nimport { escapeInlineScript, serializeInlineScriptValue } from \"../html.js\";\n\nexport interface DevHostOptions {\n /** Built View HTML. Its `.meta.json` sidecar is read alongside. */\n htmlPath: string;\n /**\n * Named samples, each becoming a button in the host chrome.\n *\n * Without `callTool` the value is delivered straight through as\n * `structuredContent`. With `callTool` it is the tool's *arguments*, and\n * what the view receives is whatever the real tool returns.\n */\n fixtures?: Record<string, unknown>;\n /**\n * Run a real tool. Supplied when the developer points `dev` at their server,\n * and used both for the fixture buttons and for `tools/call` requests the\n * view makes itself — so app-only tools behave as they will in production.\n */\n callTool?: (name: string, args: unknown) => Promise<unknown>;\n /** Arguments delivered as `ui/notifications/tool-input`. */\n toolInput?: Record<string, unknown>;\n /** Shown in the host chrome. Defaults to the sidecar's resource name. */\n toolName?: string;\n /** Defaults to 5179, incrementing until a free port is found. */\n hostPort?: number;\n /** Defaults to `hostPort + 1`, incrementing until a free port is found. */\n sandboxPort?: number;\n}\n\nexport interface DevHost {\n hostUrl: string;\n sandboxUrl: string;\n /** Tell connected browsers to reload — call after a rebuild. */\n reload(): void;\n close(): Promise<void>;\n}\n\nfunction escapeHtml(value: string): string {\n return value.replace(\n /[&<>\"']/g,\n (character) =>\n ({ \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", '\"': \"&quot;\", \"'\": \"&#39;\" })[character]!,\n );\n}\n\nconst runtimeCache = new Map<string, Promise<string>>();\n\n/**\n * Load one of the browser-side runtimes (`host-entry`, `sandbox-entry`) as a\n * string to inline into a page.\n *\n * Installed, the built IIFE is a declared package export, so ask the module\n * resolver for it. Walking from `import.meta.url` breaks the moment a bundler\n * hoists this module into a shared chunk at a different depth — which is what\n * tsup does, leaving it looking in dist/ while the file sits in dist/dev/.\n */\nfunction loadRuntime(name: \"host-entry\" | \"sandbox-entry\"): Promise<string> {\n const cached = runtimeCache.get(name);\n if (cached) return cached;\n const loading = (async () => {\n try {\n return await readFile(\n fileURLToPath(import.meta.resolve(`mountly-mcp/dev/${name}.js`)),\n \"utf8\",\n );\n } catch {\n // Running from source in this repo, where nothing is built yet.\n }\n const entry = fileURLToPath(new URL(`./${name}.ts`, import.meta.url));\n if (!existsSync(entry)) {\n throw new Error(\n `mountly-mcp dev: ${name} runtime missing. Expected the packaged 'mountly-mcp/dev/${name}.js' export, or ${entry} when running from source.`,\n );\n }\n const { build } = await import(\"vite\");\n const result = await build({\n configFile: false,\n logLevel: \"silent\",\n build: {\n write: false,\n target: \"es2022\",\n minify: false,\n lib: { entry, formats: [\"iife\"], name: \"__mountlyMcpDevRuntime\" },\n },\n });\n const outputs = Array.isArray(result) ? result : [result];\n const chunk = outputs\n .flatMap((output) => (\"output\" in output ? output.output : []))\n .find((item) => item.type === \"chunk\");\n if (!chunk || chunk.type !== \"chunk\")\n throw new Error(`mountly-mcp dev: failed to bundle ${name}`);\n return chunk.code;\n })();\n runtimeCache.set(name, loading);\n return loading;\n}\n\nfunction listenOnFreePort(server: Server, port: number): Promise<number> {\n return new Promise((resolvePort, reject) => {\n const attempt = (candidate: number): void => {\n // Listeners are paired and torn down together: `listen()`'s callback form\n // leaves a stale one attached across retries, which then resolves with\n // the port that was already taken.\n const onError = (error: NodeJS.ErrnoException): void => {\n server.removeListener(\"listening\", onListening);\n // A linear probe is enough for a dev tool on a developer's laptop.\n if (error.code === \"EADDRINUSE\" && candidate < port + 50) attempt(candidate + 1);\n else reject(error);\n };\n const onListening = (): void => {\n server.removeListener(\"error\", onError);\n const address = server.address();\n resolvePort(typeof address === \"object\" && address !== null ? address.port : candidate);\n };\n server.once(\"error\", onError);\n server.once(\"listening\", onListening);\n server.listen(candidate);\n };\n attempt(port);\n });\n}\n\nexport async function startDevHost(options: DevHostOptions): Promise<DevHost> {\n const { htmlPath, fixtures = {}, toolInput = {}, callTool } = options;\n\n const metaPath = `${htmlPath}.meta.json`;\n const initialMeta = JSON.parse(await readFile(metaPath, \"utf8\")) as McpResourceDeclaration;\n const toolName = options.toolName ?? initialMeta.name;\n const [hostRuntime, sandboxRuntime] = await Promise.all([\n loadRuntime(\"host-entry\"),\n loadRuntime(\"sandbox-entry\"),\n ]);\n\n // Bumped on reload(); the browser polls it and reloads when it changes. The\n // View HTML is read per request rather than baked into the page, so a\n // rebuild is picked up with no server restart.\n let version = 1;\n\n // The two servers reference each other's origin, and each only needs the\n // other's once a request arrives — so the sandbox reads this after the host\n // has bound its port.\n let hostOrigin = \"\";\n\n const sandboxServer = createServer((_req, res) => {\n res.writeHead(200, { \"Content-Type\": \"text/html; charset=utf-8\", \"Cache-Control\": \"no-store\" });\n res.end(sandboxProxyShell(hostOrigin, sandboxRuntime));\n });\n const sandboxPort = await listenOnFreePort(\n sandboxServer,\n options.sandboxPort ?? (options.hostPort ?? 5179) + 1,\n );\n const sandboxOrigin = `http://localhost:${sandboxPort}`;\n\n const hostServer = createServer(async (req, res) => {\n const path = new URL(req.url ?? \"/\", \"http://localhost\").pathname;\n const send = (status: number, type: string, body: string | Buffer): void => {\n res.writeHead(status, { \"Content-Type\": type, \"Cache-Control\": \"no-store\" });\n res.end(body);\n };\n try {\n if (path === \"/call\" && req.method === \"POST\") {\n if (!callTool) return send(400, \"text/plain\", \"no server connected\");\n const body = await new Promise<string>((done, fail) => {\n let raw = \"\";\n req.on(\"data\", (chunk) => (raw += chunk));\n req.on(\"end\", () => done(raw));\n req.on(\"error\", fail);\n });\n const { name, arguments: args } = JSON.parse(body) as { name: string; arguments?: unknown };\n // Tool failures are the view's problem to render, not the dev server's\n // to crash on, so they come back as a normal result.\n const result = await callTool(name, args).catch((error: unknown) => ({\n isError: true,\n content: [{ type: \"text\", text: error instanceof Error ? error.message : String(error) }],\n }));\n return send(200, \"application/json\", JSON.stringify(result));\n }\n if (path === \"/\") return send(200, \"text/html; charset=utf-8\", hostPage());\n if (path === \"/host.js\") return send(200, \"text/javascript; charset=utf-8\", hostRuntime);\n if (path === \"/version\") return send(200, \"application/json\", String(version));\n if (path === \"/view.html\")\n return send(200, \"text/plain; charset=utf-8\", await readFile(htmlPath, \"utf8\"));\n if (path === \"/view.meta.json\")\n return send(200, \"application/json\", await readFile(metaPath, \"utf8\"));\n send(404, \"text/plain\", \"not found\");\n } catch (error) {\n // A rebuild in flight can briefly leave the file missing; say so rather\n // than crashing the dev server.\n send(500, \"text/plain\", error instanceof Error ? error.message : String(error));\n }\n });\n let hostPort: number;\n try {\n hostPort = await listenOnFreePort(hostServer, options.hostPort ?? 5179);\n } catch (caught) {\n await new Promise<void>((done) => sandboxServer.close(() => done()));\n throw caught;\n }\n hostOrigin = `http://localhost:${hostPort}`;\n\n function hostPage(): string {\n return devHostHtml({\n toolName,\n sandboxOrigin,\n fixtures,\n toolInput,\n uri: initialMeta.uri,\n hasServer: callTool !== undefined,\n });\n }\n\n return {\n hostUrl: hostOrigin,\n sandboxUrl: sandboxOrigin,\n reload() {\n version += 1;\n },\n async close() {\n await Promise.all([\n new Promise<void>((done) => hostServer.close(() => done())),\n new Promise<void>((done) => sandboxServer.close(() => done())),\n ]);\n },\n };\n}\n\ninterface HostPageOptions {\n toolName: string;\n sandboxOrigin: string;\n fixtures: Record<string, unknown>;\n toolInput: Record<string, unknown>;\n uri: string;\n hasServer: boolean;\n}\n\nfunction devHostHtml(options: HostPageOptions): string {\n const { toolName, sandboxOrigin, fixtures, toolInput, uri, hasServer } = options;\n const buttons = Object.keys(fixtures)\n .map(\n (name, index) =>\n `<button class=\"chip\" type=\"button\" data-fixture=\"${escapeHtml(name)}\"${index === 0 ? ' data-active=\"true\"' : \"\"}>${escapeHtml(name)}</button>`,\n )\n .join(\"\\n\");\n const firstFixture = Object.keys(fixtures)[0] ?? \"sample\";\n\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n<title>${escapeHtml(toolName)} · Mountly MCP</title>\n<style>\n :root {\n color-scheme: light;\n --bg: #f5f5f5;\n --bg-chat: #efefef;\n --fg: #171717;\n --muted: #737373;\n --accent: #0f766e;\n --accent-soft: #ccfbf1;\n --border: #e5e5e5;\n --surface: #ffffff;\n --user-bg: #171717;\n --user-fg: #fafafa;\n --assistant-fg: #171717;\n --composer: #ffffff;\n --shadow: 0 1px 2px rgb(0 0 0 / 4%), 0 8px 24px rgb(0 0 0 / 6%);\n }\n :root[data-theme=\"dark\"] {\n color-scheme: dark;\n --bg: #0a0a0a;\n --bg-chat: #111111;\n --fg: #f5f5f5;\n --muted: #a3a3a3;\n --accent: #2dd4bf;\n --accent-soft: #134e4a;\n --border: #262626;\n --surface: #171717;\n --user-bg: #e5e5e5;\n --user-fg: #0a0a0a;\n --assistant-fg: #f5f5f5;\n --composer: #171717;\n --shadow: 0 1px 2px rgb(0 0 0 / 35%), 0 12px 32px rgb(0 0 0 / 35%);\n }\n * { box-sizing: border-box; }\n html, body { height: 100%; }\n body {\n margin: 0;\n background: var(--bg);\n color: var(--fg);\n font: 15px/1.5 \"Segoe UI\", \"Helvetica Neue\", system-ui, sans-serif;\n }\n .app {\n display: grid;\n grid-template-rows: auto 1fr auto;\n min-height: 100%;\n max-width: 880px;\n margin: 0 auto;\n }\n .topbar {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px 18px;\n border-bottom: 1px solid var(--border);\n background: color-mix(in srgb, var(--surface) 92%, transparent);\n backdrop-filter: blur(10px);\n position: sticky;\n top: 0;\n z-index: 3;\n }\n .model {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n }\n .avatar {\n width: 28px;\n height: 28px;\n border-radius: 8px;\n background:\n linear-gradient(135deg, var(--accent-soft), transparent 60%),\n var(--surface);\n border: 1px solid var(--border);\n flex: 0 0 auto;\n }\n .model h1 {\n margin: 0;\n font-size: 14px;\n font-weight: 600;\n letter-spacing: -0.01em;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n .model .sub {\n color: var(--muted);\n font-size: 12px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n .spacer { flex: 1; }\n .icon-btn {\n appearance: none;\n border: 1px solid var(--border);\n background: var(--surface);\n color: var(--fg);\n width: 34px;\n height: 34px;\n border-radius: 10px;\n font: inherit;\n cursor: pointer;\n }\n .icon-btn:hover { border-color: var(--accent); color: var(--accent); }\n .chat {\n background:\n radial-gradient(900px 420px at 50% -8%, var(--accent-soft), transparent 55%),\n var(--bg-chat);\n padding: 28px 18px 18px;\n display: flex;\n flex-direction: column;\n gap: 22px;\n overflow: auto;\n }\n .turn {\n display: grid;\n grid-template-columns: 32px 1fr;\n gap: 12px;\n align-items: start;\n max-width: 720px;\n width: 100%;\n }\n .turn.user {\n margin-left: auto;\n grid-template-columns: 1fr 32px;\n }\n .turn.user .who { order: 2; }\n .turn.user .body { order: 1; text-align: right; }\n .who {\n width: 32px;\n height: 32px;\n border-radius: 999px;\n display: grid;\n place-items: center;\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.04em;\n border: 1px solid var(--border);\n background: var(--surface);\n color: var(--muted);\n }\n .turn.user .who {\n background: var(--user-bg);\n color: var(--user-fg);\n border-color: transparent;\n }\n .bubble {\n display: inline-block;\n text-align: left;\n background: var(--surface);\n border: 1px solid var(--border);\n border-radius: 18px;\n padding: 12px 14px;\n box-shadow: var(--shadow);\n color: var(--assistant-fg);\n }\n .turn.user .bubble {\n background: var(--user-bg);\n color: var(--user-fg);\n border-color: transparent;\n }\n .bubble p { margin: 0; }\n .artifact {\n margin-top: 10px;\n border: 1px solid var(--border);\n border-radius: 16px;\n overflow: hidden;\n background: var(--surface);\n box-shadow: var(--shadow);\n text-align: left;\n }\n .artifact-bar {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 12px;\n border-bottom: 1px solid var(--border);\n background: color-mix(in srgb, var(--bg-chat) 55%, var(--surface));\n color: var(--muted);\n font-size: 12px;\n }\n .artifact-bar .dot {\n width: 7px;\n height: 7px;\n border-radius: 50%;\n background: var(--accent);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);\n }\n iframe {\n display: block;\n width: 100%;\n height: 420px;\n border: 0;\n background: #fff;\n }\n .composer {\n border-top: 1px solid var(--border);\n background: color-mix(in srgb, var(--composer) 94%, transparent);\n backdrop-filter: blur(10px);\n padding: 12px 18px 16px;\n position: sticky;\n bottom: 0;\n }\n .composer-box {\n border: 1px solid var(--border);\n background: var(--surface);\n border-radius: 18px;\n padding: 10px 12px 12px;\n box-shadow: var(--shadow);\n }\n .composer-hint {\n color: var(--muted);\n font-size: 13px;\n margin: 0 0 10px;\n }\n .samples {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n align-items: center;\n }\n .samples .label {\n color: var(--muted);\n font-size: 12px;\n margin-right: 2px;\n }\n #fixtures { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }\n .chip {\n appearance: none;\n border: 1px solid var(--border);\n background: color-mix(in srgb, var(--bg-chat) 40%, var(--surface));\n color: var(--fg);\n padding: 7px 12px;\n border-radius: 999px;\n font: inherit;\n font-size: 13px;\n cursor: pointer;\n }\n .chip:hover { border-color: var(--accent); color: var(--accent); }\n .chip[data-active=\"true\"] {\n background: var(--accent);\n color: #fff;\n border-color: var(--accent);\n }\n :root[data-theme=\"dark\"] .chip[data-active=\"true\"] { color: #042f2e; }\n details.debug {\n margin-top: 10px;\n border: 1px dashed var(--border);\n border-radius: 14px;\n background: transparent;\n overflow: hidden;\n }\n details.debug > summary {\n cursor: pointer;\n list-style: none;\n padding: 10px 12px;\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n color: var(--muted);\n }\n details.debug > summary::-webkit-details-marker { display: none; }\n .debug-body {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 0;\n border-top: 1px solid var(--border);\n }\n .panel { padding: 12px; min-width: 0; }\n .panel + .panel { border-left: 1px solid var(--border); }\n .panel h2 {\n margin: 0 0 8px;\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n color: var(--muted);\n }\n pre {\n margin: 0;\n padding: 10px;\n border-radius: 8px;\n background: color-mix(in srgb, var(--bg-chat) 80%, transparent);\n font: 12px/1.45 ui-monospace, Menlo, Consolas, monospace;\n overflow: auto;\n max-height: 180px;\n }\n .log {\n font: 11px/1.5 ui-monospace, Menlo, Consolas, monospace;\n color: var(--muted);\n white-space: pre-wrap;\n max-height: 180px;\n overflow: auto;\n }\n @media (max-width: 720px) {\n .debug-body { grid-template-columns: 1fr; }\n .panel + .panel { border-left: 0; border-top: 1px solid var(--border); }\n .turn, .turn.user { grid-template-columns: 28px 1fr; }\n .turn.user .who { order: 0; }\n .turn.user .body { order: 0; text-align: left; }\n }\n</style>\n</head>\n<body>\n<div class=\"app\">\n <header class=\"topbar\">\n <div class=\"model\">\n <div class=\"avatar\" aria-hidden=\"true\"></div>\n <div>\n <h1>Mountly · ${escapeHtml(toolName)}</h1>\n <div class=\"sub\">${escapeHtml(uri)}</div>\n </div>\n </div>\n <span class=\"spacer\"></span>\n <button class=\"icon-btn\" id=\"theme\" type=\"button\" title=\"Toggle theme\" aria-label=\"Toggle theme\">◐</button>\n <button class=\"icon-btn\" id=\"teardown\" type=\"button\" title=\"Teardown\" aria-label=\"Teardown\">×</button>\n </header>\n\n <main class=\"chat\">\n <div class=\"turn user\">\n <div class=\"who\" aria-hidden=\"true\">You</div>\n <div class=\"body\">\n <div class=\"bubble\">\n <p>Call <strong>${escapeHtml(toolName)}</strong> with sample “${escapeHtml(firstFixture)}”.</p>\n </div>\n </div>\n </div>\n\n <div class=\"turn assistant\">\n <div class=\"who\" aria-hidden=\"true\">AI</div>\n <div class=\"body\">\n <div class=\"bubble\">\n <p>Here’s the interactive result from that tool.</p>\n <div class=\"artifact\">\n <div class=\"artifact-bar\"><span class=\"dot\" aria-hidden=\"true\"></span> MCP App · inline</div>\n <iframe id=\"sandbox\" sandbox=\"allow-scripts allow-same-origin\" title=\"mountly-mcp sandbox proxy\"></iframe>\n </div>\n </div>\n </div>\n </div>\n </main>\n\n <footer class=\"composer\">\n <div class=\"composer-box\">\n <p class=\"composer-hint\">Ask another sample — same as switching fixtures in a real host.</p>\n <div class=\"samples\">\n <span class=\"label\">Samples</span>\n <div id=\"fixtures\">${buttons || '<span class=\"label\">No fixtures — add mcp.fixtures.json</span>'}</div>\n </div>\n <details class=\"debug\">\n <summary>Protocol debug</summary>\n <div class=\"debug-body\">\n <section class=\"panel\">\n <h2>Last structuredContent</h2>\n <pre id=\"payload\">(none yet)</pre>\n </section>\n <section class=\"panel\">\n <h2>Channel log</h2>\n <div class=\"log\" id=\"log\"></div>\n </section>\n </div>\n </details>\n </div>\n </footer>\n</div>\n<script>\nglobalThis.__mountlyMcpDevHost__ = ${serializeInlineScriptValue({\n toolName,\n sandboxOrigin,\n fixtures,\n toolInput,\n uri,\n hasServer,\n sandboxPath: \"/sandbox-proxy.html\",\n })};\nconst HAS_SERVER = ${hasServer ? \"true\" : \"false\"};\n</script>\n<script src=\"/host.js\"></script>\n</body>\n</html>\n`;\n}\n/**\n * The proxy document: a shell around the compiled sandbox runtime.\n *\n * Kept separate from {@link sandboxProxyHtml} so the dev server can load the\n * runtime once at startup and still stamp in the host origin per request,\n * which it only learns after binding a port.\n */\nfunction sandboxProxyShell(hostOrigin: string, runtime: string): string {\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<title>mountly-mcp sandbox proxy</title>\n<style>\n html, body { margin: 0; padding: 0; width: 100%; height: 100%; background: transparent; }\n #inner { width: 100%; height: 100%; border: 0; background: white; }\n</style>\n</head>\n<body>\n<script>globalThis.__mountlyMcpSandbox__ = ${serializeInlineScriptValue({ hostOrigin })};</script>\n<script>${escapeInlineScript(runtime)}</script>\n</body>\n</html>\n`;\n}\n\n/**\n * The MCP Apps sandbox proxy document (spec §8.4), ready to serve from an\n * origin distinct from the host page.\n *\n * Exported because this is the security boundary — it builds the CSP from the\n * sidecar and decides what the view may execute and reach. Anything standing\n * up a host, including the example harnesses in this repo, should serve this\n * rather than keep a copy that drifts from it.\n */\nexport async function sandboxProxyHtml(hostOrigin: string): Promise<string> {\n return sandboxProxyShell(hostOrigin, await loadRuntime(\"sandbox-entry\"));\n}\n\nexport { connectMcpServer, type ConnectedMcpServer } from \"./connect-server.js\";\n"],"mappings":"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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,2BAuTL,WAAW,QAAQ,CAAC;AAAA,2BACjB,WAAW,GAAG,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,4BAad,WAAW,QAAQ,CAAC,+BAA0B,WAAW,YAAY,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,6BAwBrE,WAAW,qEAAgE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qCAmBnE,2BAA2B;AAAA,IAC5D;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,aAAa;AAAA,EACf,CAAC,CAAC;AAAA,qBACiB,YAAY,SAAS,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAMjD;AAQA,SAAS,kBAAkB,YAAoB,SAAyB;AACtE,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,6CAWoC,2BAA2B,EAAE,WAAW,CAAC,CAAC;AAAA,UAC7E,mBAAmB,OAAO,CAAC;AAAA;AAAA;AAAA;AAIrC;AAWA,eAAsB,iBAAiB,YAAqC;AAC1E,SAAO,kBAAkB,YAAY,MAAM,YAAY,eAAe,CAAC;AACzE;","names":[]}
package/dist/cli.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  } from "./chunk-EOWAWZH2.js";
6
6
  import {
7
7
  startDevHost
8
- } from "./chunk-KWVGW5Q7.js";
8
+ } from "./chunk-GN2IQYV3.js";
9
9
  import "./chunk-OWAXBNZA.js";
10
10
  import "./chunk-YJA4H6IB.js";
11
11
 
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Show a page composed from many teams' custom elements inside an MCP host.
3
+ *
4
+ * The agent is the composer: `show_page` carries the catalog's prompt as its
5
+ * description, the agent calls it with a tree, the server validates (or
6
+ * repairs) it, and the `ui://` view renders it with `mountly-compose` — each
7
+ * team's code loading on first use. When the user acts in a widget, the view
8
+ * sends the agent its next turn, with the current page, so it edits rather than
9
+ * starts over.
10
+ */
11
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
12
+ import type { Catalog } from "mountly-compose";
13
+ export interface ComposeViewOptions {
14
+ /**
15
+ * The view's import map. Must map `mountly-compose` and
16
+ * `@modelcontextprotocol/ext-apps` (its `app-with-deps` build), plus
17
+ * whatever the teams' peer embeds import: `react`, `react/jsx-runtime`,
18
+ * `react-dom/client`, `mountly-react`, `mountly/embed`.
19
+ */
20
+ imports: Record<string, string>;
21
+ /** The page's own CSS: host layout (`ui-stack`, `ui-grid`, `ui-note`) and design tokens. */
22
+ styles?: string;
23
+ /** Document title. */
24
+ title?: string;
25
+ }
26
+ export interface RegisterComposeAppOptions extends ComposeViewOptions {
27
+ catalog: Catalog;
28
+ /** MCP tool name. Default `show_page`. */
29
+ name?: string;
30
+ /** `ui://` resource for the view. */
31
+ resourceUri?: string;
32
+ /** Appended to the tool description: what the agent may fill ids from, house rules. */
33
+ instructions?: string;
34
+ }
35
+ /** The view HTML: an import map, the host's CSS, and a module that renders tool results. */
36
+ export declare function composeViewHtml(options: ComposeViewOptions): string;
37
+ /**
38
+ * Register the `show_page` tool and its `ui://` view on an MCP server.
39
+ *
40
+ * The input is `{ page }`, typed loosely on purpose: a strict schema would make
41
+ * the SDK reject a near-miss before the handler could repair it. The handler
42
+ * validates against the catalog, strips small mistakes, and otherwise returns
43
+ * the reasons as a tool error the agent can act on.
44
+ */
45
+ export declare function registerComposeApp(server: McpServer, options: RegisterComposeAppOptions): {
46
+ tool: string;
47
+ resourceUri: string;
48
+ };
@@ -0,0 +1,141 @@
1
+ // src/compose/index.ts
2
+ import { z } from "zod";
3
+ import {
4
+ RESOURCE_MIME_TYPE,
5
+ registerAppResource,
6
+ registerAppTool
7
+ } from "@modelcontextprotocol/ext-apps/server";
8
+ var DEFAULT_RESOURCE_URI = "ui://mountly/compose.html";
9
+ var DEFAULT_TOOL_NAME = "show_page";
10
+ function composeViewHtml(options) {
11
+ const importMap = JSON.stringify({ imports: options.imports }).replace(/</g, "\\u003c");
12
+ const styles = (options.styles ?? "").replace(/<\/style/gi, "<\\/style");
13
+ const title = (options.title ?? "Page").replace(/[<&]/g, "");
14
+ return `<!doctype html>
15
+ <html lang="en">
16
+ <head>
17
+ <meta charset="utf-8" />
18
+ <title>${title}</title>
19
+ <script type="importmap">${importMap}</script>
20
+ <style>${styles}</style>
21
+ </head>
22
+ <body>
23
+ <main id="page"></main>
24
+ <script type="module">
25
+ import { App } from "@modelcontextprotocol/ext-apps";
26
+ import { connectView } from "mountly-compose";
27
+
28
+ const app = new App({ name: "mountly-compose", version: "1.0.0" });
29
+ connectView(app, document.querySelector("#page"), {
30
+ request: "Show the updated page with ${DEFAULT_TOOL_NAME}.",
31
+ });
32
+ await app.connect();
33
+ </script>
34
+ </body>
35
+ </html>
36
+ `;
37
+ }
38
+ function originsOf(options) {
39
+ const urls = [
40
+ ...Object.values(options.imports),
41
+ ...options.catalog.elements.flatMap((el) => el.embed ? [el.embed] : [])
42
+ ];
43
+ const origins = /* @__PURE__ */ new Set();
44
+ for (const url of urls) {
45
+ try {
46
+ origins.add(new URL(url).origin);
47
+ } catch {
48
+ }
49
+ }
50
+ return [...origins];
51
+ }
52
+ function tagsOf(node, into = /* @__PURE__ */ new Set()) {
53
+ into.add(node.tag);
54
+ for (const child of node.children ?? []) tagsOf(child, into);
55
+ return into;
56
+ }
57
+ function viewPayload(catalog, page) {
58
+ return { page, elements: catalog.elements, actions: catalog.actions };
59
+ }
60
+ function registerComposeApp(server, options) {
61
+ const { catalog } = options;
62
+ const name = options.name ?? DEFAULT_TOOL_NAME;
63
+ const resourceUri = options.resourceUri ?? DEFAULT_RESOURCE_URI;
64
+ if (!resourceUri.startsWith("ui://")) {
65
+ throw new Error(
66
+ `mountly-mcp/compose: resourceUri must use 'ui://' (received '${resourceUri}')`
67
+ );
68
+ }
69
+ const origins = originsOf(options);
70
+ const csp = { connectDomains: origins, resourceDomains: origins, frameDomains: [] };
71
+ const html = composeViewHtml(options);
72
+ registerAppResource(
73
+ server,
74
+ "compose_view",
75
+ resourceUri,
76
+ {
77
+ mimeType: RESOURCE_MIME_TYPE,
78
+ description: "A page composed from teams' custom elements.",
79
+ _meta: { ui: { prefersBorder: false, csp } }
80
+ },
81
+ async () => ({
82
+ contents: [
83
+ { uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html, _meta: { ui: { csp } } }
84
+ ]
85
+ })
86
+ );
87
+ const description = [
88
+ `Show the user a page built from the components below. Call it with { "page": <tree> }.`,
89
+ `To change the page, call it again with the whole updated page.`,
90
+ options.instructions ?? "",
91
+ "",
92
+ catalog.prompt()
93
+ ].filter((line, index) => line || index > 2).join("\n");
94
+ registerAppTool(
95
+ server,
96
+ name,
97
+ {
98
+ title: "Show page",
99
+ description,
100
+ inputSchema: { page: z.unknown().describe("The page as a tree: { tag, attrs, children }.") },
101
+ _meta: { ui: { resourceUri, visibility: ["model", "app"] } }
102
+ },
103
+ (async (args) => {
104
+ const errors = catalog.validate(args.page);
105
+ let page = args.page;
106
+ let note = "";
107
+ if (errors.length) {
108
+ const { tree, dropped } = catalog.repair(args.page);
109
+ if (!tree || catalog.validate(tree).length) {
110
+ return {
111
+ isError: true,
112
+ content: [
113
+ {
114
+ type: "text",
115
+ text: `The page cannot be shown:
116
+ ${errors.join("\n")}
117
+ Fix these and call ${name} again.`
118
+ }
119
+ ]
120
+ };
121
+ }
122
+ page = tree;
123
+ note = ` Removed what the catalog does not allow: ${dropped.join("; ")}.`;
124
+ }
125
+ const hostTags = new Set(
126
+ catalog.elements.filter((el) => el.team === "host").map((el) => el.tag)
127
+ );
128
+ const tags = [...tagsOf(page)].filter((tag) => !hostTags.has(tag));
129
+ return {
130
+ content: [{ type: "text", text: `Showing ${tags.join(", ") || "the page"}.${note}` }],
131
+ structuredContent: viewPayload(catalog, page)
132
+ };
133
+ })
134
+ );
135
+ return { tool: name, resourceUri };
136
+ }
137
+ export {
138
+ composeViewHtml,
139
+ registerComposeApp
140
+ };
141
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/compose/index.ts"],"sourcesContent":["/**\n * Show a page composed from many teams' custom elements inside an MCP host.\n *\n * The agent is the composer: `show_page` carries the catalog's prompt as its\n * description, the agent calls it with a tree, the server validates (or\n * repairs) it, and the `ui://` view renders it with `mountly-compose` — each\n * team's code loading on first use. When the user acts in a widget, the view\n * sends the agent its next turn, with the current page, so it edits rather than\n * starts over.\n */\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\nimport {\n RESOURCE_MIME_TYPE,\n registerAppResource,\n registerAppTool,\n} from \"@modelcontextprotocol/ext-apps/server\";\nimport type { Catalog, Tree } from \"mountly-compose\";\n\nconst DEFAULT_RESOURCE_URI = \"ui://mountly/compose.html\";\nconst DEFAULT_TOOL_NAME = \"show_page\";\n\nexport interface ComposeViewOptions {\n /**\n * The view's import map. Must map `mountly-compose` and\n * `@modelcontextprotocol/ext-apps` (its `app-with-deps` build), plus\n * whatever the teams' peer embeds import: `react`, `react/jsx-runtime`,\n * `react-dom/client`, `mountly-react`, `mountly/embed`.\n */\n imports: Record<string, string>;\n /** The page's own CSS: host layout (`ui-stack`, `ui-grid`, `ui-note`) and design tokens. */\n styles?: string;\n /** Document title. */\n title?: string;\n}\n\nexport interface RegisterComposeAppOptions extends ComposeViewOptions {\n catalog: Catalog;\n /** MCP tool name. Default `show_page`. */\n name?: string;\n /** `ui://` resource for the view. */\n resourceUri?: string;\n /** Appended to the tool description: what the agent may fill ids from, house rules. */\n instructions?: string;\n}\n\n/** The view HTML: an import map, the host's CSS, and a module that renders tool results. */\nexport function composeViewHtml(options: ComposeViewOptions): string {\n const importMap = JSON.stringify({ imports: options.imports }).replace(/</g, \"\\\\u003c\");\n const styles = (options.styles ?? \"\").replace(/<\\/style/gi, \"<\\\\/style\");\n const title = (options.title ?? \"Page\").replace(/[<&]/g, \"\");\n return `<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <title>${title}</title>\n <script type=\"importmap\">${importMap}</script>\n <style>${styles}</style>\n </head>\n <body>\n <main id=\"page\"></main>\n <script type=\"module\">\n import { App } from \"@modelcontextprotocol/ext-apps\";\n import { connectView } from \"mountly-compose\";\n\n const app = new App({ name: \"mountly-compose\", version: \"1.0.0\" });\n connectView(app, document.querySelector(\"#page\"), {\n request: \"Show the updated page with ${DEFAULT_TOOL_NAME}.\",\n });\n await app.connect();\n </script>\n </body>\n</html>\n`;\n}\n\n/** Every origin the view loads code or data from, for its content security policy. */\nfunction originsOf(options: RegisterComposeAppOptions): string[] {\n const urls = [\n ...Object.values(options.imports),\n ...options.catalog.elements.flatMap((el) => (el.embed ? [el.embed] : [])),\n ];\n const origins = new Set<string>();\n for (const url of urls) {\n try {\n origins.add(new URL(url).origin);\n } catch {\n // A relative URL is same-origin with the view and needs no entry.\n }\n }\n return [...origins];\n}\n\nfunction tagsOf(node: Tree, into = new Set<string>()): Set<string> {\n into.add(node.tag);\n for (const child of node.children ?? []) tagsOf(child, into);\n return into;\n}\n\n/**\n * What the view renders with: the page, and the catalog's elements and actions.\n * All the elements, not just the page's: an action names the widgets the next\n * page must show, and the view hands that to the agent on the next turn.\n */\nfunction viewPayload(catalog: Catalog, page: Tree) {\n return { page, elements: catalog.elements, actions: catalog.actions };\n}\n\n/**\n * Register the `show_page` tool and its `ui://` view on an MCP server.\n *\n * The input is `{ page }`, typed loosely on purpose: a strict schema would make\n * the SDK reject a near-miss before the handler could repair it. The handler\n * validates against the catalog, strips small mistakes, and otherwise returns\n * the reasons as a tool error the agent can act on.\n */\nexport function registerComposeApp(\n server: McpServer,\n options: RegisterComposeAppOptions,\n): { tool: string; resourceUri: string } {\n const { catalog } = options;\n const name = options.name ?? DEFAULT_TOOL_NAME;\n const resourceUri = options.resourceUri ?? DEFAULT_RESOURCE_URI;\n if (!resourceUri.startsWith(\"ui://\")) {\n throw new Error(\n `mountly-mcp/compose: resourceUri must use 'ui://' (received '${resourceUri}')`,\n );\n }\n const origins = originsOf(options);\n const csp = { connectDomains: origins, resourceDomains: origins, frameDomains: [] };\n const html = composeViewHtml(options);\n\n registerAppResource(\n server,\n \"compose_view\",\n resourceUri,\n {\n mimeType: RESOURCE_MIME_TYPE,\n description: \"A page composed from teams' custom elements.\",\n _meta: { ui: { prefersBorder: false, csp } },\n },\n async () => ({\n contents: [\n { uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html, _meta: { ui: { csp } } },\n ],\n }),\n );\n\n const description = [\n `Show the user a page built from the components below. Call it with { \"page\": <tree> }.`,\n `To change the page, call it again with the whole updated page.`,\n options.instructions ?? \"\",\n \"\",\n catalog.prompt(),\n ]\n .filter((line, index) => line || index > 2)\n .join(\"\\n\");\n\n registerAppTool(\n server,\n name,\n {\n title: \"Show page\",\n description,\n inputSchema: { page: z.unknown().describe(\"The page as a tree: { tag, attrs, children }.\") },\n _meta: { ui: { resourceUri, visibility: [\"model\", \"app\"] } },\n } as never,\n (async (args: { page?: unknown }) => {\n const errors = catalog.validate(args.page);\n let page = args.page as Tree;\n let note = \"\";\n if (errors.length) {\n const { tree, dropped } = catalog.repair(args.page);\n if (!tree || catalog.validate(tree).length) {\n return {\n isError: true,\n content: [\n {\n type: \"text\",\n text: `The page cannot be shown:\\n${errors.join(\"\\n\")}\\nFix these and call ${name} again.`,\n },\n ],\n };\n }\n page = tree;\n note = ` Removed what the catalog does not allow: ${dropped.join(\"; \")}.`;\n }\n const hostTags = new Set(\n catalog.elements.filter((el) => el.team === \"host\").map((el) => el.tag),\n );\n const tags = [...tagsOf(page)].filter((tag) => !hostTags.has(tag));\n return {\n content: [{ type: \"text\", text: `Showing ${tags.join(\", \") || \"the page\"}.${note}` }],\n structuredContent: viewPayload(catalog, page),\n };\n }) as never,\n );\n\n return { tool: name, resourceUri };\n}\n"],"mappings":"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","names":[]}
@@ -18213,6 +18213,8 @@ container holding the app. Specify either width or maxWidth, and either height o
18213
18213
  var capabilities = {
18214
18214
  openLinks: {},
18215
18215
  logging: {},
18216
+ // A view's `sendMessage` asks the host to post the user's next turn to the agent.
18217
+ message: { text: {} },
18216
18218
  ...config2.hasServer ? { serverTools: {} } : {}
18217
18219
  };
18218
18220
  var bridge = new DX(null, { name: "mountly-mcp-dev", version: "1.0.0" }, capabilities, {
@@ -18225,6 +18227,11 @@ container holding the app. Specify either width or maxWidth, and either height o
18225
18227
  }
18226
18228
  });
18227
18229
  bridge.onloggingmessage = (params) => log("in", `notifications/message ${params.level}`);
18230
+ bridge.onmessage = async ({ content }) => {
18231
+ const text = content.map((block) => "text" in block ? block.text : `[${block.type}]`).join(" ");
18232
+ log("in", `ui/message ${text}`);
18233
+ return {};
18234
+ };
18228
18235
  bridge.onopenlink = async ({ url: url2 }) => {
18229
18236
  window.open(url2, "_blank", "noopener,noreferrer");
18230
18237
  return {};
package/dist/dev/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  sandboxProxyHtml,
3
3
  startDevHost
4
- } from "../chunk-KWVGW5Q7.js";
4
+ } from "../chunk-GN2IQYV3.js";
5
5
  import {
6
6
  connectMcpServer
7
7
  } from "../chunk-OWAXBNZA.js";
@@ -1,3 +1,7 @@
1
+ import {
2
+ buildAppHtml,
3
+ useJsonRenderApp
4
+ } from "../chunk-33DMFKKD.js";
1
5
  import {
2
6
  createJsonRenderMcpApp,
3
7
  registerJsonRenderResource,
@@ -17,10 +21,6 @@ import {
17
21
  useToolResult
18
22
  } from "../chunk-SXF23BZD.js";
19
23
  import "../chunk-5L3QHYWK.js";
20
- import {
21
- buildAppHtml,
22
- useJsonRenderApp
23
- } from "../chunk-33DMFKKD.js";
24
24
  import "../chunk-YJA4H6IB.js";
25
25
 
26
26
  // src/json-render/generative-view.tsx
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mountly-mcp",
3
- "version": "4.1.0",
3
+ "version": "4.2.0",
4
4
  "description": "Build MCP Apps (SEP-1865) views from React, Vue or Svelte components — new or existing. One build emits the ui:// resource, its sidecar, and the MCP server wiring.",
5
5
  "keywords": [
6
6
  "chatgpt",
@@ -69,6 +69,10 @@
69
69
  "types": "./dist/json-render/server.d.ts",
70
70
  "import": "./dist/json-render/server.js"
71
71
  },
72
+ "./compose": {
73
+ "types": "./dist/compose/index.d.ts",
74
+ "import": "./dist/compose/index.js"
75
+ },
72
76
  "./json-render/app": {
73
77
  "types": "./dist/json-render/app.d.ts",
74
78
  "import": "./dist/json-render/app.js"
@@ -110,7 +114,7 @@
110
114
  "dependencies": {
111
115
  "@modelcontextprotocol/ext-apps": "^1.7.5",
112
116
  "@modelcontextprotocol/sdk": "^1.30.0",
113
- "mountly": "^1.0.1",
117
+ "mountly": "^1.3.0",
114
118
  "mountly-react": "^2.1.0",
115
119
  "zod": "^4.4.3"
116
120
  },
@@ -128,14 +132,16 @@
128
132
  "tsup": "^8.5.1",
129
133
  "typescript": "^7.0.2",
130
134
  "vite": "^8.1.5",
131
- "mountly-vue": "^2.1.0",
132
- "mountly-svelte": "^2.1.0"
135
+ "mountly-svelte": "^2.1.1",
136
+ "mountly-compose": "0.1.0",
137
+ "mountly-vue": "^2.1.0"
133
138
  },
134
139
  "peerDependencies": {
135
140
  "@json-render/core": "^0.20.0",
136
141
  "@json-render/react": "^0.20.0",
137
142
  "ai": "^7.0.65",
138
143
  "axe-core": ">=4.10.0",
144
+ "mountly-compose": "^0.1.0",
139
145
  "mountly-svelte": "^2.1.0",
140
146
  "mountly-vue": "^2.1.0",
141
147
  "playwright": ">=1.62.1",
@@ -173,6 +179,9 @@
173
179
  },
174
180
  "axe-core": {
175
181
  "optional": true
182
+ },
183
+ "mountly-compose": {
184
+ "optional": true
176
185
  }
177
186
  },
178
187
  "scripts": {
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/dev/index.ts"],"sourcesContent":["/**\n * A local MCP Apps host, so a View can be developed without installing a\n * real host and restarting it on every change.\n *\n * It is a genuine host, not a preview shim: two origins as the spec requires\n * (§8.4), a sandbox proxy that injects the sidecar's CSP and permissions, the\n * full `ui/initialize` handshake, and tool-input/tool-result delivery. A View\n * that works here works in Claude, and one that reaches for `eval` fails here\n * for the same reason it would fail there.\n *\n * ```ts\n * const host = await startDevHost({ htmlPath: \"dist/view.html\", fixtures });\n * console.log(host.hostUrl);\n * ```\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { createServer, type Server } from \"node:http\";\nimport { fileURLToPath } from \"node:url\";\nimport type { McpResourceDeclaration } from \"../types.js\";\nimport { escapeInlineScript, serializeInlineScriptValue } from \"../html.js\";\n\nexport interface DevHostOptions {\n /** Built View HTML. Its `.meta.json` sidecar is read alongside. */\n htmlPath: string;\n /**\n * Named samples, each becoming a button in the host chrome.\n *\n * Without `callTool` the value is delivered straight through as\n * `structuredContent`. With `callTool` it is the tool's *arguments*, and\n * what the view receives is whatever the real tool returns.\n */\n fixtures?: Record<string, unknown>;\n /**\n * Run a real tool. Supplied when the developer points `dev` at their server,\n * and used both for the fixture buttons and for `tools/call` requests the\n * view makes itself — so app-only tools behave as they will in production.\n */\n callTool?: (name: string, args: unknown) => Promise<unknown>;\n /** Arguments delivered as `ui/notifications/tool-input`. */\n toolInput?: Record<string, unknown>;\n /** Shown in the host chrome. Defaults to the sidecar's resource name. */\n toolName?: string;\n /** Defaults to 5179, incrementing until a free port is found. */\n hostPort?: number;\n /** Defaults to `hostPort + 1`, incrementing until a free port is found. */\n sandboxPort?: number;\n}\n\nexport interface DevHost {\n hostUrl: string;\n sandboxUrl: string;\n /** Tell connected browsers to reload — call after a rebuild. */\n reload(): void;\n close(): Promise<void>;\n}\n\nfunction escapeHtml(value: string): string {\n return value.replace(\n /[&<>\"']/g,\n (character) =>\n ({ \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", '\"': \"&quot;\", \"'\": \"&#39;\" })[character]!,\n );\n}\n\nconst runtimeCache = new Map<string, Promise<string>>();\n\n/**\n * Load one of the browser-side runtimes (`host-entry`, `sandbox-entry`) as a\n * string to inline into a page.\n *\n * Installed, the built IIFE is a declared package export, so ask the module\n * resolver for it. Walking from `import.meta.url` breaks the moment a bundler\n * hoists this module into a shared chunk at a different depth — which is what\n * tsup does, leaving it looking in dist/ while the file sits in dist/dev/.\n */\nfunction loadRuntime(name: \"host-entry\" | \"sandbox-entry\"): Promise<string> {\n const cached = runtimeCache.get(name);\n if (cached) return cached;\n const loading = (async () => {\n try {\n return await readFile(\n fileURLToPath(import.meta.resolve(`mountly-mcp/dev/${name}.js`)),\n \"utf8\",\n );\n } catch {\n // Running from source in this repo, where nothing is built yet.\n }\n const entry = fileURLToPath(new URL(`./${name}.ts`, import.meta.url));\n if (!existsSync(entry)) {\n throw new Error(\n `mountly-mcp dev: ${name} runtime missing. Expected the packaged 'mountly-mcp/dev/${name}.js' export, or ${entry} when running from source.`,\n );\n }\n const { build } = await import(\"vite\");\n const result = await build({\n configFile: false,\n logLevel: \"silent\",\n build: {\n write: false,\n target: \"es2022\",\n minify: false,\n lib: { entry, formats: [\"iife\"], name: \"__mountlyMcpDevRuntime\" },\n },\n });\n const outputs = Array.isArray(result) ? result : [result];\n const chunk = outputs\n .flatMap((output) => (\"output\" in output ? output.output : []))\n .find((item) => item.type === \"chunk\");\n if (!chunk || chunk.type !== \"chunk\")\n throw new Error(`mountly-mcp dev: failed to bundle ${name}`);\n return chunk.code;\n })();\n runtimeCache.set(name, loading);\n return loading;\n}\n\nfunction listenOnFreePort(server: Server, port: number): Promise<number> {\n return new Promise((resolvePort, reject) => {\n const attempt = (candidate: number): void => {\n // Listeners are paired and torn down together: `listen()`'s callback form\n // leaves a stale one attached across retries, which then resolves with\n // the port that was already taken.\n const onError = (error: NodeJS.ErrnoException): void => {\n server.removeListener(\"listening\", onListening);\n // ponytail: linear probe, fine for a dev tool on a developer's laptop.\n if (error.code === \"EADDRINUSE\" && candidate < port + 50) attempt(candidate + 1);\n else reject(error);\n };\n const onListening = (): void => {\n server.removeListener(\"error\", onError);\n const address = server.address();\n resolvePort(typeof address === \"object\" && address !== null ? address.port : candidate);\n };\n server.once(\"error\", onError);\n server.once(\"listening\", onListening);\n server.listen(candidate);\n };\n attempt(port);\n });\n}\n\nexport async function startDevHost(options: DevHostOptions): Promise<DevHost> {\n const { htmlPath, fixtures = {}, toolInput = {}, callTool } = options;\n\n const metaPath = `${htmlPath}.meta.json`;\n const initialMeta = JSON.parse(await readFile(metaPath, \"utf8\")) as McpResourceDeclaration;\n const toolName = options.toolName ?? initialMeta.name;\n const [hostRuntime, sandboxRuntime] = await Promise.all([\n loadRuntime(\"host-entry\"),\n loadRuntime(\"sandbox-entry\"),\n ]);\n\n // Bumped on reload(); the browser polls it and reloads when it changes. The\n // View HTML is read per request rather than baked into the page, so a\n // rebuild is picked up with no server restart.\n let version = 1;\n\n // The two servers reference each other's origin, and each only needs the\n // other's once a request arrives — so the sandbox reads this after the host\n // has bound its port.\n let hostOrigin = \"\";\n\n const sandboxServer = createServer((_req, res) => {\n res.writeHead(200, { \"Content-Type\": \"text/html; charset=utf-8\", \"Cache-Control\": \"no-store\" });\n res.end(sandboxProxyShell(hostOrigin, sandboxRuntime));\n });\n const sandboxPort = await listenOnFreePort(\n sandboxServer,\n options.sandboxPort ?? (options.hostPort ?? 5179) + 1,\n );\n const sandboxOrigin = `http://localhost:${sandboxPort}`;\n\n const hostServer = createServer(async (req, res) => {\n const path = new URL(req.url ?? \"/\", \"http://localhost\").pathname;\n const send = (status: number, type: string, body: string | Buffer): void => {\n res.writeHead(status, { \"Content-Type\": type, \"Cache-Control\": \"no-store\" });\n res.end(body);\n };\n try {\n if (path === \"/call\" && req.method === \"POST\") {\n if (!callTool) return send(400, \"text/plain\", \"no server connected\");\n const body = await new Promise<string>((done, fail) => {\n let raw = \"\";\n req.on(\"data\", (chunk) => (raw += chunk));\n req.on(\"end\", () => done(raw));\n req.on(\"error\", fail);\n });\n const { name, arguments: args } = JSON.parse(body) as { name: string; arguments?: unknown };\n // Tool failures are the view's problem to render, not the dev server's\n // to crash on, so they come back as a normal result.\n const result = await callTool(name, args).catch((error: unknown) => ({\n isError: true,\n content: [{ type: \"text\", text: error instanceof Error ? error.message : String(error) }],\n }));\n return send(200, \"application/json\", JSON.stringify(result));\n }\n if (path === \"/\") return send(200, \"text/html; charset=utf-8\", hostPage());\n if (path === \"/host.js\") return send(200, \"text/javascript; charset=utf-8\", hostRuntime);\n if (path === \"/version\") return send(200, \"application/json\", String(version));\n if (path === \"/view.html\")\n return send(200, \"text/plain; charset=utf-8\", await readFile(htmlPath, \"utf8\"));\n if (path === \"/view.meta.json\")\n return send(200, \"application/json\", await readFile(metaPath, \"utf8\"));\n send(404, \"text/plain\", \"not found\");\n } catch (error) {\n // A rebuild in flight can briefly leave the file missing; say so rather\n // than crashing the dev server.\n send(500, \"text/plain\", error instanceof Error ? error.message : String(error));\n }\n });\n let hostPort: number;\n try {\n hostPort = await listenOnFreePort(hostServer, options.hostPort ?? 5179);\n } catch (caught) {\n await new Promise<void>((done) => sandboxServer.close(() => done()));\n throw caught;\n }\n hostOrigin = `http://localhost:${hostPort}`;\n\n function hostPage(): string {\n return devHostHtml({\n toolName,\n sandboxOrigin,\n fixtures,\n toolInput,\n uri: initialMeta.uri,\n hasServer: callTool !== undefined,\n });\n }\n\n return {\n hostUrl: hostOrigin,\n sandboxUrl: sandboxOrigin,\n reload() {\n version += 1;\n },\n async close() {\n await Promise.all([\n new Promise<void>((done) => hostServer.close(() => done())),\n new Promise<void>((done) => sandboxServer.close(() => done())),\n ]);\n },\n };\n}\n\ninterface HostPageOptions {\n toolName: string;\n sandboxOrigin: string;\n fixtures: Record<string, unknown>;\n toolInput: Record<string, unknown>;\n uri: string;\n hasServer: boolean;\n}\n\nfunction devHostHtml(options: HostPageOptions): string {\n const { toolName, sandboxOrigin, fixtures, toolInput, uri, hasServer } = options;\n const buttons = Object.keys(fixtures)\n .map(\n (name, index) =>\n `<button class=\"chip\" type=\"button\" data-fixture=\"${escapeHtml(name)}\"${index === 0 ? ' data-active=\"true\"' : \"\"}>${escapeHtml(name)}</button>`,\n )\n .join(\"\\n\");\n const firstFixture = Object.keys(fixtures)[0] ?? \"sample\";\n\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n<title>${escapeHtml(toolName)} · Mountly MCP</title>\n<style>\n :root {\n color-scheme: light;\n --bg: #f5f5f5;\n --bg-chat: #efefef;\n --fg: #171717;\n --muted: #737373;\n --accent: #0f766e;\n --accent-soft: #ccfbf1;\n --border: #e5e5e5;\n --surface: #ffffff;\n --user-bg: #171717;\n --user-fg: #fafafa;\n --assistant-fg: #171717;\n --composer: #ffffff;\n --shadow: 0 1px 2px rgb(0 0 0 / 4%), 0 8px 24px rgb(0 0 0 / 6%);\n }\n :root[data-theme=\"dark\"] {\n color-scheme: dark;\n --bg: #0a0a0a;\n --bg-chat: #111111;\n --fg: #f5f5f5;\n --muted: #a3a3a3;\n --accent: #2dd4bf;\n --accent-soft: #134e4a;\n --border: #262626;\n --surface: #171717;\n --user-bg: #e5e5e5;\n --user-fg: #0a0a0a;\n --assistant-fg: #f5f5f5;\n --composer: #171717;\n --shadow: 0 1px 2px rgb(0 0 0 / 35%), 0 12px 32px rgb(0 0 0 / 35%);\n }\n * { box-sizing: border-box; }\n html, body { height: 100%; }\n body {\n margin: 0;\n background: var(--bg);\n color: var(--fg);\n font: 15px/1.5 \"Segoe UI\", \"Helvetica Neue\", system-ui, sans-serif;\n }\n .app {\n display: grid;\n grid-template-rows: auto 1fr auto;\n min-height: 100%;\n max-width: 880px;\n margin: 0 auto;\n }\n .topbar {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px 18px;\n border-bottom: 1px solid var(--border);\n background: color-mix(in srgb, var(--surface) 92%, transparent);\n backdrop-filter: blur(10px);\n position: sticky;\n top: 0;\n z-index: 3;\n }\n .model {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n }\n .avatar {\n width: 28px;\n height: 28px;\n border-radius: 8px;\n background:\n linear-gradient(135deg, var(--accent-soft), transparent 60%),\n var(--surface);\n border: 1px solid var(--border);\n flex: 0 0 auto;\n }\n .model h1 {\n margin: 0;\n font-size: 14px;\n font-weight: 600;\n letter-spacing: -0.01em;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n .model .sub {\n color: var(--muted);\n font-size: 12px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n .spacer { flex: 1; }\n .icon-btn {\n appearance: none;\n border: 1px solid var(--border);\n background: var(--surface);\n color: var(--fg);\n width: 34px;\n height: 34px;\n border-radius: 10px;\n font: inherit;\n cursor: pointer;\n }\n .icon-btn:hover { border-color: var(--accent); color: var(--accent); }\n .chat {\n background:\n radial-gradient(900px 420px at 50% -8%, var(--accent-soft), transparent 55%),\n var(--bg-chat);\n padding: 28px 18px 18px;\n display: flex;\n flex-direction: column;\n gap: 22px;\n overflow: auto;\n }\n .turn {\n display: grid;\n grid-template-columns: 32px 1fr;\n gap: 12px;\n align-items: start;\n max-width: 720px;\n width: 100%;\n }\n .turn.user {\n margin-left: auto;\n grid-template-columns: 1fr 32px;\n }\n .turn.user .who { order: 2; }\n .turn.user .body { order: 1; text-align: right; }\n .who {\n width: 32px;\n height: 32px;\n border-radius: 999px;\n display: grid;\n place-items: center;\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.04em;\n border: 1px solid var(--border);\n background: var(--surface);\n color: var(--muted);\n }\n .turn.user .who {\n background: var(--user-bg);\n color: var(--user-fg);\n border-color: transparent;\n }\n .bubble {\n display: inline-block;\n text-align: left;\n background: var(--surface);\n border: 1px solid var(--border);\n border-radius: 18px;\n padding: 12px 14px;\n box-shadow: var(--shadow);\n color: var(--assistant-fg);\n }\n .turn.user .bubble {\n background: var(--user-bg);\n color: var(--user-fg);\n border-color: transparent;\n }\n .bubble p { margin: 0; }\n .artifact {\n margin-top: 10px;\n border: 1px solid var(--border);\n border-radius: 16px;\n overflow: hidden;\n background: var(--surface);\n box-shadow: var(--shadow);\n text-align: left;\n }\n .artifact-bar {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 12px;\n border-bottom: 1px solid var(--border);\n background: color-mix(in srgb, var(--bg-chat) 55%, var(--surface));\n color: var(--muted);\n font-size: 12px;\n }\n .artifact-bar .dot {\n width: 7px;\n height: 7px;\n border-radius: 50%;\n background: var(--accent);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);\n }\n iframe {\n display: block;\n width: 100%;\n height: 420px;\n border: 0;\n background: #fff;\n }\n .composer {\n border-top: 1px solid var(--border);\n background: color-mix(in srgb, var(--composer) 94%, transparent);\n backdrop-filter: blur(10px);\n padding: 12px 18px 16px;\n position: sticky;\n bottom: 0;\n }\n .composer-box {\n border: 1px solid var(--border);\n background: var(--surface);\n border-radius: 18px;\n padding: 10px 12px 12px;\n box-shadow: var(--shadow);\n }\n .composer-hint {\n color: var(--muted);\n font-size: 13px;\n margin: 0 0 10px;\n }\n .samples {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n align-items: center;\n }\n .samples .label {\n color: var(--muted);\n font-size: 12px;\n margin-right: 2px;\n }\n #fixtures { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }\n .chip {\n appearance: none;\n border: 1px solid var(--border);\n background: color-mix(in srgb, var(--bg-chat) 40%, var(--surface));\n color: var(--fg);\n padding: 7px 12px;\n border-radius: 999px;\n font: inherit;\n font-size: 13px;\n cursor: pointer;\n }\n .chip:hover { border-color: var(--accent); color: var(--accent); }\n .chip[data-active=\"true\"] {\n background: var(--accent);\n color: #fff;\n border-color: var(--accent);\n }\n :root[data-theme=\"dark\"] .chip[data-active=\"true\"] { color: #042f2e; }\n details.debug {\n margin-top: 10px;\n border: 1px dashed var(--border);\n border-radius: 14px;\n background: transparent;\n overflow: hidden;\n }\n details.debug > summary {\n cursor: pointer;\n list-style: none;\n padding: 10px 12px;\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n color: var(--muted);\n }\n details.debug > summary::-webkit-details-marker { display: none; }\n .debug-body {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 0;\n border-top: 1px solid var(--border);\n }\n .panel { padding: 12px; min-width: 0; }\n .panel + .panel { border-left: 1px solid var(--border); }\n .panel h2 {\n margin: 0 0 8px;\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n color: var(--muted);\n }\n pre {\n margin: 0;\n padding: 10px;\n border-radius: 8px;\n background: color-mix(in srgb, var(--bg-chat) 80%, transparent);\n font: 12px/1.45 ui-monospace, Menlo, Consolas, monospace;\n overflow: auto;\n max-height: 180px;\n }\n .log {\n font: 11px/1.5 ui-monospace, Menlo, Consolas, monospace;\n color: var(--muted);\n white-space: pre-wrap;\n max-height: 180px;\n overflow: auto;\n }\n @media (max-width: 720px) {\n .debug-body { grid-template-columns: 1fr; }\n .panel + .panel { border-left: 0; border-top: 1px solid var(--border); }\n .turn, .turn.user { grid-template-columns: 28px 1fr; }\n .turn.user .who { order: 0; }\n .turn.user .body { order: 0; text-align: left; }\n }\n</style>\n</head>\n<body>\n<div class=\"app\">\n <header class=\"topbar\">\n <div class=\"model\">\n <div class=\"avatar\" aria-hidden=\"true\"></div>\n <div>\n <h1>Mountly · ${escapeHtml(toolName)}</h1>\n <div class=\"sub\">${escapeHtml(uri)}</div>\n </div>\n </div>\n <span class=\"spacer\"></span>\n <button class=\"icon-btn\" id=\"theme\" type=\"button\" title=\"Toggle theme\" aria-label=\"Toggle theme\">◐</button>\n <button class=\"icon-btn\" id=\"teardown\" type=\"button\" title=\"Teardown\" aria-label=\"Teardown\">×</button>\n </header>\n\n <main class=\"chat\">\n <div class=\"turn user\">\n <div class=\"who\" aria-hidden=\"true\">You</div>\n <div class=\"body\">\n <div class=\"bubble\">\n <p>Call <strong>${escapeHtml(toolName)}</strong> with sample “${escapeHtml(firstFixture)}”.</p>\n </div>\n </div>\n </div>\n\n <div class=\"turn assistant\">\n <div class=\"who\" aria-hidden=\"true\">AI</div>\n <div class=\"body\">\n <div class=\"bubble\">\n <p>Here’s the interactive result from that tool.</p>\n <div class=\"artifact\">\n <div class=\"artifact-bar\"><span class=\"dot\" aria-hidden=\"true\"></span> MCP App · inline</div>\n <iframe id=\"sandbox\" sandbox=\"allow-scripts allow-same-origin\" title=\"mountly-mcp sandbox proxy\"></iframe>\n </div>\n </div>\n </div>\n </div>\n </main>\n\n <footer class=\"composer\">\n <div class=\"composer-box\">\n <p class=\"composer-hint\">Ask another sample — same as switching fixtures in a real host.</p>\n <div class=\"samples\">\n <span class=\"label\">Samples</span>\n <div id=\"fixtures\">${buttons || '<span class=\"label\">No fixtures — add mcp.fixtures.json</span>'}</div>\n </div>\n <details class=\"debug\">\n <summary>Protocol debug</summary>\n <div class=\"debug-body\">\n <section class=\"panel\">\n <h2>Last structuredContent</h2>\n <pre id=\"payload\">(none yet)</pre>\n </section>\n <section class=\"panel\">\n <h2>Channel log</h2>\n <div class=\"log\" id=\"log\"></div>\n </section>\n </div>\n </details>\n </div>\n </footer>\n</div>\n<script>\nglobalThis.__mountlyMcpDevHost__ = ${serializeInlineScriptValue({\n toolName,\n sandboxOrigin,\n fixtures,\n toolInput,\n uri,\n hasServer,\n sandboxPath: \"/sandbox-proxy.html\",\n })};\nconst HAS_SERVER = ${hasServer ? \"true\" : \"false\"};\n</script>\n<script src=\"/host.js\"></script>\n</body>\n</html>\n`;\n}\n/**\n * The proxy document: a shell around the compiled sandbox runtime.\n *\n * Kept separate from {@link sandboxProxyHtml} so the dev server can load the\n * runtime once at startup and still stamp in the host origin per request,\n * which it only learns after binding a port.\n */\nfunction sandboxProxyShell(hostOrigin: string, runtime: string): string {\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<title>mountly-mcp sandbox proxy</title>\n<style>\n html, body { margin: 0; padding: 0; width: 100%; height: 100%; background: transparent; }\n #inner { width: 100%; height: 100%; border: 0; background: white; }\n</style>\n</head>\n<body>\n<script>globalThis.__mountlyMcpSandbox__ = ${serializeInlineScriptValue({ hostOrigin })};</script>\n<script>${escapeInlineScript(runtime)}</script>\n</body>\n</html>\n`;\n}\n\n/**\n * The MCP Apps sandbox proxy document (spec §8.4), ready to serve from an\n * origin distinct from the host page.\n *\n * Exported because this is the security boundary — it builds the CSP from the\n * sidecar and decides what the view may execute and reach. Anything standing\n * up a host, including the example harnesses in this repo, should serve this\n * rather than keep a copy that drifts from it.\n */\nexport async function sandboxProxyHtml(hostOrigin: string): Promise<string> {\n return sandboxProxyShell(hostOrigin, await loadRuntime(\"sandbox-entry\"));\n}\n\nexport { connectMcpServer, type ConnectedMcpServer } from \"./connect-server.js\";\n"],"mappings":"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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,2BAuTL,WAAW,QAAQ,CAAC;AAAA,2BACjB,WAAW,GAAG,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,4BAad,WAAW,QAAQ,CAAC,+BAA0B,WAAW,YAAY,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,6BAwBrE,WAAW,qEAAgE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qCAmBnE,2BAA2B;AAAA,IAC5D;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,aAAa;AAAA,EACf,CAAC,CAAC;AAAA,qBACiB,YAAY,SAAS,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAMjD;AAQA,SAAS,kBAAkB,YAAoB,SAAyB;AACtE,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,6CAWoC,2BAA2B,EAAE,WAAW,CAAC,CAAC;AAAA,UAC7E,mBAAmB,OAAO,CAAC;AAAA;AAAA;AAAA;AAIrC;AAWA,eAAsB,iBAAiB,YAAqC;AAC1E,SAAO,kBAAkB,YAAY,MAAM,YAAY,eAAe,CAAC;AACzE;","names":[]}