mountly-mcp 4.4.0 → 4.5.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.
Files changed (44) hide show
  1. package/dist/artifact/declaration.d.ts +61 -0
  2. package/dist/artifact/index.d.ts +2 -42
  3. package/dist/artifact/index.js +9 -5
  4. package/dist/chunk-6T5EIQGE.js +104 -0
  5. package/dist/chunk-6T5EIQGE.js.map +1 -0
  6. package/dist/chunk-BLT2MK32.js +35 -0
  7. package/dist/chunk-BLT2MK32.js.map +1 -0
  8. package/dist/{chunk-HIAIQVK5.js → chunk-QIA76O6M.js} +9 -1
  9. package/dist/chunk-QIA76O6M.js.map +1 -0
  10. package/dist/{chunk-DKK26TRS.js → chunk-RLDSMU5M.js} +9 -70
  11. package/dist/chunk-RLDSMU5M.js.map +1 -0
  12. package/dist/chunk-UMQ5OULD.js +243 -0
  13. package/dist/chunk-UMQ5OULD.js.map +1 -0
  14. package/dist/cli.js +1 -1
  15. package/dist/dev/host-entry.js +23 -0
  16. package/dist/{doctor-NB3DE4CG.js → doctor-OTOROLPI.js} +110 -8
  17. package/dist/doctor-OTOROLPI.js.map +1 -0
  18. package/dist/doctor.d.ts +4 -0
  19. package/dist/download.d.ts +17 -0
  20. package/dist/index.d.ts +1 -0
  21. package/dist/index.js +4 -0
  22. package/dist/json-render/index.js +2 -1
  23. package/dist/json-render/index.js.map +1 -1
  24. package/dist/react/hooks.d.ts +7 -0
  25. package/dist/react/index.js +4 -1
  26. package/dist/server/core.d.ts +84 -0
  27. package/dist/server/index.d.ts +2 -72
  28. package/dist/server/index.js +19 -219
  29. package/dist/server/index.js.map +1 -1
  30. package/dist/server/workers.d.ts +7 -0
  31. package/dist/server/workers.js +14 -0
  32. package/dist/server/workers.js.map +1 -0
  33. package/dist/testing/index.js +5 -3
  34. package/dist/testing/index.js.map +1 -1
  35. package/dist/vite/index.d.ts +15 -1
  36. package/dist/vite/index.js +133 -11
  37. package/dist/vite/index.js.map +1 -1
  38. package/dist/vue/index.d.ts +6 -0
  39. package/dist/vue/index.js +8 -0
  40. package/dist/vue/index.js.map +1 -1
  41. package/package.json +14 -6
  42. package/dist/chunk-DKK26TRS.js.map +0 -1
  43. package/dist/chunk-HIAIQVK5.js.map +0 -1
  44. package/dist/doctor-NB3DE4CG.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/testing/index.ts","../../src/testing/render-artifact.ts"],"sourcesContent":["import { access, readFile } from \"node:fs/promises\";\nimport { isDeepStrictEqual } from \"node:util\";\nimport {\n MCP_APP_MANIFEST_FILE,\n McpAppConformanceError,\n readMcpAppArtifact,\n readMcpAppManifest,\n type ConformanceDiagnostic,\n type McpAppArtifact,\n} from \"../artifact/index.js\";\nimport { renderMcpAppArtifact } from \"./render-artifact.js\";\n\nexport interface VerifyMcpAppsOptions {\n /** Canonical manifest. Defaults to `dist/mountly-mcp.manifest.json`. */\n manifestPath?: string;\n /** Transitional single-View inputs when no manifest exists. */\n htmlPaths?: ReadonlyArray<string>;\n /**\n * Load each View in a real browser and confirm the View mounts with content.\n *\n * Off by default: it needs Playwright and a Chromium download, which the\n * static checks do not. Turn it on in CI. Everything else here inspects\n * files, and a View can satisfy every file-level check while failing to\n * render — a framework left external, a Node global in the bundle, a bridge\n * that never completes the handshake all produce conforming HTML.\n */\n render?: boolean;\n}\n\nexport interface McpAppConformanceReport {\n ok: boolean;\n /** Verification tier selected by the caller. */\n mode: \"static\" | \"browser\";\n artifacts: ReadonlyArray<McpAppArtifact>;\n diagnostics: ReadonlyArray<ConformanceDiagnostic>;\n}\n\nfunction error(code: string, message: string, source?: string): ConformanceDiagnostic {\n return { severity: \"error\", code, message, source };\n}\n\nfunction warning(code: string, message: string, source?: string): ConformanceDiagnostic {\n return { severity: \"warning\", code, message, source };\n}\n\n/** Deterministic verification shared by the CLI and downstream CI. */\nexport async function verifyMcpApps(\n options: VerifyMcpAppsOptions = {},\n): Promise<McpAppConformanceReport> {\n const diagnostics: ConformanceDiagnostic[] = [];\n const artifacts: McpAppArtifact[] = [];\n if (options.htmlPaths && options.htmlPaths.length > 0) {\n for (const path of options.htmlPaths) {\n try {\n artifacts.push(await readMcpAppArtifact(path));\n } catch (caught) {\n if (caught instanceof McpAppConformanceError) diagnostics.push(...caught.diagnostics);\n else\n diagnostics.push(\n error(\"verify/load\", caught instanceof Error ? caught.message : String(caught)),\n );\n }\n }\n } else {\n try {\n const loaded = await readMcpAppManifest(\n options.manifestPath ?? `dist/${MCP_APP_MANIFEST_FILE}`,\n );\n for (const declared of loaded.artifacts) {\n try {\n const actual = await readMcpAppArtifact(declared.htmlPath, declared.uri);\n if (actual.name !== declared.name) {\n diagnostics.push(\n error(\n \"manifest/name-mismatch\",\n `manifest app '${declared.name}' points to artifact '${actual.name}'`,\n loaded.path,\n ),\n );\n }\n if (!isDeepStrictEqual(actual.declaration, declared.declaration)) {\n diagnostics.push(\n error(\n \"manifest/declaration-mismatch\",\n `manifest declaration for '${declared.name}' does not match its metadata file`,\n loaded.path,\n ),\n );\n }\n artifacts.push(actual);\n } catch (caught) {\n if (caught instanceof McpAppConformanceError) diagnostics.push(...caught.diagnostics);\n else\n diagnostics.push(\n error(\"verify/load\", caught instanceof Error ? caught.message : String(caught)),\n );\n }\n }\n } catch (caught) {\n if (caught instanceof McpAppConformanceError) diagnostics.push(...caught.diagnostics);\n else\n diagnostics.push(\n error(\"verify/load\", caught instanceof Error ? caught.message : String(caught)),\n );\n }\n }\n\n const names = new Set<string>();\n const uris = new Set<string>();\n for (const artifact of artifacts) {\n if (names.has(artifact.name)) {\n diagnostics.push(error(\"manifest/duplicate-name\", `duplicate app name '${artifact.name}'`));\n }\n if (uris.has(artifact.uri)) {\n diagnostics.push(error(\"manifest/duplicate-uri\", `duplicate app uri '${artifact.uri}'`));\n }\n names.add(artifact.name);\n uris.add(artifact.uri);\n try {\n await Promise.all([access(artifact.htmlPath), access(artifact.metaPath)]);\n const html = await readFile(artifact.htmlPath, \"utf8\");\n if (!html.includes(\"ui/initialize\") || !html.includes(\"ui/notifications/initialized\")) {\n diagnostics.push(\n error(\n \"artifact/bridge\",\n \"View HTML does not contain the MCP Apps initialization handshake\",\n artifact.htmlPath,\n ),\n );\n }\n if (options.render) diagnostics.push(...(await renderMcpAppArtifact(artifact)));\n if (!artifact.declaration.description) {\n diagnostics.push(\n warning(\n \"artifact/description\",\n `View '${artifact.name}' has no description`,\n artifact.metaPath,\n ),\n );\n }\n } catch (caught) {\n diagnostics.push(\n error(\n \"artifact/files\",\n caught instanceof Error ? caught.message : String(caught),\n artifact.htmlPath,\n ),\n );\n }\n }\n\n return {\n ok: !diagnostics.some((diagnostic) => diagnostic.severity === \"error\"),\n mode: options.render === true ? \"browser\" : \"static\",\n artifacts,\n diagnostics,\n };\n}\n\nexport function formatConformanceReport(report: McpAppConformanceReport): string {\n const lines = report.diagnostics.map((diagnostic) => {\n const marker = diagnostic.severity === \"error\" ? \"ERROR\" : \"WARN\";\n const source = diagnostic.source ? ` (${diagnostic.source})` : \"\";\n return `${marker} ${diagnostic.code}: ${diagnostic.message}${source}`;\n });\n lines.push(\n `${report.ok ? \"PASS\" : \"FAIL\"} ${report.artifacts.length} app artifact${report.artifacts.length === 1 ? \"\" : \"s\"} (${report.mode === \"browser\" ? \"browser checks\" : \"static checks only; pass --render for runtime coverage\"})`,\n );\n return `${lines.join(\"\\n\")}\\n`;\n}\n","import type { ConformanceDiagnostic, McpAppArtifact } from \"../artifact/index.js\";\n\ninterface FrameLike {\n url(): string;\n /**\n * A string expression is evaluated through the debugger protocol rather than\n * as page script, which is how axe reaches a View whose CSP withholds\n * `unsafe-eval` — the same CSP a host enforces in production.\n */\n evaluate<T>(fn: (() => T) | string): Promise<T>;\n}\n\ninterface PageLike {\n on(event: \"pageerror\", handler: (error: Error) => void): void;\n goto(url: string): Promise<unknown>;\n frames(): FrameLike[];\n}\n\nexport interface ConformanceBrowser {\n newPage(): Promise<PageLike>;\n close(): Promise<void>;\n}\n\nexport interface ConformanceHost {\n hostUrl: string;\n close(): Promise<void>;\n}\n\n/** @internal Test seam for browser and host lifecycle failures. */\nexport interface RenderMcpAppArtifactDependencies {\n launchBrowser(): Promise<ConformanceBrowser | undefined>;\n startHost(artifact: McpAppArtifact): Promise<ConformanceHost>;\n}\n\nfunction error(code: string, message: string, source: string): ConformanceDiagnostic {\n return { severity: \"error\", code, message, source };\n}\n\nfunction warning(code: string, message: string, source: string): ConformanceDiagnostic {\n return { severity: \"warning\", code, message, source };\n}\n\ninterface AxeViolation {\n id: string;\n impact: string | null;\n help: string;\n nodes: number;\n target: string;\n}\n\n/** axe-core is an optional peer, like Playwright: absent means the pass is skipped. */\nasync function loadAxeSource(): Promise<string | undefined> {\n const specifier: string = \"axe-core\";\n try {\n const mod = (await import(specifier)) as { source?: string; default?: { source?: string } };\n return mod.source ?? mod.default?.source;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Accessibility of the View as assembled, which component-level checks cannot\n * see: every part can be accessible and the composition still fail, because a\n * missing label or a contrast pairing only exists once the parts are together.\n *\n * Reported as warnings, so a run stays green unless `--strict` is on. They are\n * findings about the View you built, not proof the artifact is malformed.\n */\nasync function auditAccessibility(\n view: FrameLike,\n artifact: McpAppArtifact,\n): Promise<ConformanceDiagnostic[]> {\n const axeSource = await loadAxeSource();\n if (!axeSource) return [];\n\n let violations: AxeViolation[];\n try {\n await view.evaluate(axeSource);\n violations = await view.evaluate<AxeViolation[]>(`\n axe.run(document, { resultTypes: [\"violations\"] }).then((result) =>\n result.violations.map((violation) => ({\n id: violation.id,\n impact: violation.impact ?? null,\n help: violation.help,\n nodes: violation.nodes.length,\n target: String(violation.nodes[0]?.target?.[0] ?? \"\")\n }))\n )\n `);\n } catch (caught) {\n return [\n warning(\n \"render/a11y-unavailable\",\n `accessibility pass did not run: ${caught instanceof Error ? caught.message : String(caught)}`,\n artifact.htmlPath,\n ),\n ];\n }\n\n return violations.map((violation) =>\n warning(\n \"render/a11y\",\n `${violation.id} (${violation.impact ?? \"unknown\"} impact): ${violation.help} — ${violation.nodes} element${violation.nodes === 1 ? \"\" : \"s\"}, first at \\`${violation.target}\\``,\n artifact.htmlPath,\n ),\n );\n}\n\n/** Playwright is an optional peer — absent is a diagnostic, not a crash. */\nasync function launchBrowser(): Promise<ConformanceBrowser | undefined> {\n // Both specifiers are tried: under an isolated `node_modules` a project that\n // installed `@playwright/test` cannot resolve bare `playwright`, even though\n // one depends on the other. Specifiers stay `string`-typed so the optional\n // peer is optional at type-check time as well as at runtime.\n const specifiers: string[] = [\"playwright\", \"@playwright/test\"];\n for (const specifier of specifiers) {\n let mod: { chromium?: { launch(): Promise<ConformanceBrowser> } };\n try {\n mod = (await import(specifier)) as typeof mod;\n } catch {\n continue;\n }\n if (mod.chromium) return mod.chromium.launch();\n }\n return undefined;\n}\n\nasync function startHost(artifact: McpAppArtifact): Promise<ConformanceHost> {\n const { startDevHost } = await import(\"../dev/index.js\");\n return startDevHost({\n htmlPath: artifact.htmlPath,\n fixtures: { conformance: {} },\n hostPort: 5480,\n });\n}\n\n/**\n * Exercise an App artifact through the real two-origin host and browser.\n * Browser and host ownership stays local so every startup path is cleaned up.\n */\nexport async function renderMcpAppArtifactWith(\n artifact: McpAppArtifact,\n dependencies: RenderMcpAppArtifactDependencies,\n): Promise<ConformanceDiagnostic[]> {\n let browser: ConformanceBrowser | undefined;\n try {\n browser = await dependencies.launchBrowser();\n } catch (caught) {\n return [\n error(\n \"render/unavailable\",\n `could not launch Chromium: ${caught instanceof Error ? caught.message : String(caught)}`,\n artifact.htmlPath,\n ),\n ];\n }\n if (!browser) {\n return [\n error(\n \"render/unavailable\",\n \"render requested but Playwright is not installed (npm i -D playwright)\",\n artifact.htmlPath,\n ),\n ];\n }\n\n let host: ConformanceHost | undefined;\n const failures: ConformanceDiagnostic[] = [];\n try {\n host = await dependencies.startHost(artifact);\n const page = await browser.newPage();\n const pageErrors: string[] = [];\n page.on(\"pageerror\", (caught) => pageErrors.push(caught.message));\n await page.goto(host.hostUrl);\n\n const deadline = Date.now() + 20_000;\n let mounted = false;\n let renderedContent = false;\n let mountedAt: number | undefined;\n let boundary: string | undefined;\n while (!boundary && Date.now() < deadline && pageErrors.length === 0) {\n const view = page.frames().find((frame) => frame.url().includes(\"srcdoc\"));\n const state = view\n ? await view\n .evaluate(() => {\n const failed = document.querySelector(\"[data-mountly-mcp-error]\");\n const root = document.querySelector(\"#mountly-mcp-root\");\n const bounds = root?.getBoundingClientRect();\n return {\n state: root?.getAttribute(\"data-mountly-mcp-state\") ?? undefined,\n html: root?.innerHTML ?? \"\",\n shadowHtml: root?.shadowRoot?.innerHTML ?? \"\",\n visibleHeight: bounds?.height ?? 0,\n error: failed\n ? `${failed.getAttribute(\"data-mountly-mcp-error\") ?? \"\"}: ${failed.textContent ?? \"\"}`\n : undefined,\n };\n })\n .catch(() => undefined)\n : undefined;\n boundary = state?.error;\n // Sticky: the bridge's state only moves forward, and a View that mounted\n // stays mounted even if a later poll reads the frame mid-navigation.\n mounted ||= state?.state === \"mounted\";\n if (mounted && mountedAt === undefined) mountedAt = Date.now();\n renderedContent =\n (state?.html ?? \"\").trim().length > 0 ||\n (state?.shadowHtml ?? \"\").trim().length > 0 ||\n (state?.visibleHeight ?? 0) > 0;\n if (mounted && renderedContent) break;\n // Framework renderers may commit after mount() returns. Give them a\n // bounded window while still failing a genuinely empty View quickly.\n if (mountedAt !== undefined && Date.now() - mountedAt >= 2_000) break;\n await new Promise((done) => setTimeout(done, 100));\n }\n\n if (boundary) {\n failures.push(\n error(\"render/error-boundary\", `View rendered an error: ${boundary}`, artifact.htmlPath),\n );\n } else if (pageErrors.length > 0) {\n failures.push(\n error(\"render/threw\", `View threw while loading: ${pageErrors[0]}`, artifact.htmlPath),\n );\n } else if (!mounted) {\n failures.push(\n error(\n \"render/timeout\",\n \"View never mounted: the handshake or View mount did not complete\",\n artifact.htmlPath,\n ),\n );\n } else if (!renderedContent) {\n failures.push(\n error(\"render/blank\", \"View mounted but rendered no content\", artifact.htmlPath),\n );\n } else {\n // Only a View that actually rendered can be audited — anything else has\n // already failed, and axe would just report on an empty document.\n const view = page.frames().find((frame) => frame.url().includes(\"srcdoc\"));\n if (view) failures.push(...(await auditAccessibility(view, artifact)));\n }\n } catch (caught) {\n failures.push(\n error(\n \"render/failed\",\n caught instanceof Error ? caught.message : String(caught),\n artifact.htmlPath,\n ),\n );\n } finally {\n await Promise.allSettled([browser.close(), host?.close() ?? Promise.resolve()]);\n }\n return failures;\n}\n\nexport function renderMcpAppArtifact(artifact: McpAppArtifact): Promise<ConformanceDiagnostic[]> {\n return renderMcpAppArtifactWith(artifact, { launchBrowser, startHost });\n}\n"],"mappings":"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","names":["error","warning"]}
1
+ {"version":3,"sources":["../../src/testing/index.ts","../../src/testing/render-artifact.ts"],"sourcesContent":["import { access, readFile } from \"node:fs/promises\";\nimport { isDeepStrictEqual } from \"node:util\";\nimport {\n MCP_APP_MANIFEST_FILE,\n McpAppConformanceError,\n readMcpAppArtifact,\n readMcpAppManifest,\n type ConformanceDiagnostic,\n type McpAppArtifact,\n} from \"../artifact/index.js\";\nimport { renderMcpAppArtifact } from \"./render-artifact.js\";\n\nexport interface VerifyMcpAppsOptions {\n /** Canonical manifest. Defaults to `dist/mountly-mcp.manifest.json`. */\n manifestPath?: string;\n /** Transitional single-View inputs when no manifest exists. */\n htmlPaths?: ReadonlyArray<string>;\n /**\n * Load each View in a real browser and confirm the View mounts with content.\n *\n * Off by default: it needs Playwright and a Chromium download, which the\n * static checks do not. Turn it on in CI. Everything else here inspects\n * files, and a View can satisfy every file-level check while failing to\n * render — a framework left external, a Node global in the bundle, a bridge\n * that never completes the handshake all produce conforming HTML.\n */\n render?: boolean;\n}\n\nexport interface McpAppConformanceReport {\n ok: boolean;\n /** Verification tier selected by the caller. */\n mode: \"static\" | \"browser\";\n artifacts: ReadonlyArray<McpAppArtifact>;\n diagnostics: ReadonlyArray<ConformanceDiagnostic>;\n}\n\nfunction error(code: string, message: string, source?: string): ConformanceDiagnostic {\n return { severity: \"error\", code, message, source };\n}\n\nfunction warning(code: string, message: string, source?: string): ConformanceDiagnostic {\n return { severity: \"warning\", code, message, source };\n}\n\n/** Deterministic verification shared by the CLI and downstream CI. */\nexport async function verifyMcpApps(\n options: VerifyMcpAppsOptions = {},\n): Promise<McpAppConformanceReport> {\n const diagnostics: ConformanceDiagnostic[] = [];\n const artifacts: McpAppArtifact[] = [];\n if (options.htmlPaths && options.htmlPaths.length > 0) {\n for (const path of options.htmlPaths) {\n try {\n artifacts.push(await readMcpAppArtifact(path));\n } catch (caught) {\n if (caught instanceof McpAppConformanceError) diagnostics.push(...caught.diagnostics);\n else\n diagnostics.push(\n error(\"verify/load\", caught instanceof Error ? caught.message : String(caught)),\n );\n }\n }\n } else {\n try {\n const loaded = await readMcpAppManifest(\n options.manifestPath ?? `dist/${MCP_APP_MANIFEST_FILE}`,\n );\n for (const declared of loaded.artifacts) {\n try {\n const actual = await readMcpAppArtifact(declared.htmlPath, declared.uri);\n if (actual.name !== declared.name) {\n diagnostics.push(\n error(\n \"manifest/name-mismatch\",\n `manifest app '${declared.name}' points to artifact '${actual.name}'`,\n loaded.path,\n ),\n );\n }\n if (!isDeepStrictEqual(actual.declaration, declared.declaration)) {\n diagnostics.push(\n error(\n \"manifest/declaration-mismatch\",\n `manifest declaration for '${declared.name}' does not match its metadata file`,\n loaded.path,\n ),\n );\n }\n artifacts.push(actual);\n } catch (caught) {\n if (caught instanceof McpAppConformanceError) diagnostics.push(...caught.diagnostics);\n else\n diagnostics.push(\n error(\"verify/load\", caught instanceof Error ? caught.message : String(caught)),\n );\n }\n }\n } catch (caught) {\n if (caught instanceof McpAppConformanceError) diagnostics.push(...caught.diagnostics);\n else\n diagnostics.push(\n error(\"verify/load\", caught instanceof Error ? caught.message : String(caught)),\n );\n }\n }\n\n const names = new Set<string>();\n const uris = new Set<string>();\n for (const artifact of artifacts) {\n if (names.has(artifact.name)) {\n diagnostics.push(error(\"manifest/duplicate-name\", `duplicate app name '${artifact.name}'`));\n }\n if (uris.has(artifact.uri)) {\n diagnostics.push(error(\"manifest/duplicate-uri\", `duplicate app uri '${artifact.uri}'`));\n }\n names.add(artifact.name);\n uris.add(artifact.uri);\n try {\n await Promise.all([access(artifact.htmlPath), access(artifact.metaPath)]);\n const html = await readFile(artifact.htmlPath, \"utf8\");\n if (!html.includes(\"ui/initialize\") || !html.includes(\"ui/notifications/initialized\")) {\n diagnostics.push(\n error(\n \"artifact/bridge\",\n \"View HTML does not contain the MCP Apps initialization handshake\",\n artifact.htmlPath,\n ),\n );\n }\n if (options.render) diagnostics.push(...(await renderMcpAppArtifact(artifact)));\n if (!artifact.declaration.description) {\n diagnostics.push(\n warning(\n \"artifact/description\",\n `View '${artifact.name}' has no description`,\n artifact.metaPath,\n ),\n );\n }\n } catch (caught) {\n diagnostics.push(\n error(\n \"artifact/files\",\n caught instanceof Error ? caught.message : String(caught),\n artifact.htmlPath,\n ),\n );\n }\n }\n\n return {\n ok: !diagnostics.some((diagnostic) => diagnostic.severity === \"error\"),\n mode: options.render === true ? \"browser\" : \"static\",\n artifacts,\n diagnostics,\n };\n}\n\nexport function formatConformanceReport(report: McpAppConformanceReport): string {\n const lines = report.diagnostics.map((diagnostic) => {\n const marker = diagnostic.severity === \"error\" ? \"ERROR\" : \"WARN\";\n const source = diagnostic.source ? ` (${diagnostic.source})` : \"\";\n return `${marker} ${diagnostic.code}: ${diagnostic.message}${source}`;\n });\n lines.push(\n `${report.ok ? \"PASS\" : \"FAIL\"} ${report.artifacts.length} app artifact${report.artifacts.length === 1 ? \"\" : \"s\"} (${report.mode === \"browser\" ? \"browser checks\" : \"static checks only; pass --render for runtime coverage\"})`,\n );\n return `${lines.join(\"\\n\")}\\n`;\n}\n","import type { ConformanceDiagnostic, McpAppArtifact } from \"../artifact/index.js\";\n\ninterface FrameLike {\n url(): string;\n /**\n * A string expression is evaluated through the debugger protocol rather than\n * as page script, which is how axe reaches a View whose CSP withholds\n * `unsafe-eval` — the same CSP a host enforces in production.\n */\n evaluate<T>(fn: (() => T) | string): Promise<T>;\n}\n\ninterface PageLike {\n on(event: \"pageerror\", handler: (error: Error) => void): void;\n goto(url: string): Promise<unknown>;\n frames(): FrameLike[];\n}\n\nexport interface ConformanceBrowser {\n newPage(): Promise<PageLike>;\n close(): Promise<void>;\n}\n\nexport interface ConformanceHost {\n hostUrl: string;\n close(): Promise<void>;\n}\n\n/** @internal Test seam for browser and host lifecycle failures. */\nexport interface RenderMcpAppArtifactDependencies {\n launchBrowser(): Promise<ConformanceBrowser | undefined>;\n startHost(artifact: McpAppArtifact): Promise<ConformanceHost>;\n}\n\nfunction error(code: string, message: string, source: string): ConformanceDiagnostic {\n return { severity: \"error\", code, message, source };\n}\n\nfunction warning(code: string, message: string, source: string): ConformanceDiagnostic {\n return { severity: \"warning\", code, message, source };\n}\n\ninterface AxeViolation {\n id: string;\n impact: string | null;\n help: string;\n nodes: number;\n target: string;\n}\n\n/** axe-core is an optional peer, like Playwright: absent means the pass is skipped. */\nasync function loadAxeSource(): Promise<string | undefined> {\n const specifier: string = \"axe-core\";\n try {\n const mod = (await import(specifier)) as { source?: string; default?: { source?: string } };\n return mod.source ?? mod.default?.source;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Accessibility of the View as assembled, which component-level checks cannot\n * see: every part can be accessible and the composition still fail, because a\n * missing label or a contrast pairing only exists once the parts are together.\n *\n * Reported as warnings, so a run stays green unless `--strict` is on. They are\n * findings about the View you built, not proof the artifact is malformed.\n */\nasync function auditAccessibility(\n view: FrameLike,\n artifact: McpAppArtifact,\n): Promise<ConformanceDiagnostic[]> {\n const axeSource = await loadAxeSource();\n if (!axeSource) return [];\n\n let violations: AxeViolation[];\n try {\n await view.evaluate(axeSource);\n violations = await view.evaluate<AxeViolation[]>(`\n axe.run(document, { resultTypes: [\"violations\"] }).then((result) =>\n result.violations.map((violation) => ({\n id: violation.id,\n impact: violation.impact ?? null,\n help: violation.help,\n nodes: violation.nodes.length,\n target: String(violation.nodes[0]?.target?.[0] ?? \"\")\n }))\n )\n `);\n } catch (caught) {\n return [\n warning(\n \"render/a11y-unavailable\",\n `accessibility pass did not run: ${caught instanceof Error ? caught.message : String(caught)}`,\n artifact.htmlPath,\n ),\n ];\n }\n\n return violations.map((violation) =>\n warning(\n \"render/a11y\",\n `${violation.id} (${violation.impact ?? \"unknown\"} impact): ${violation.help} — ${violation.nodes} element${violation.nodes === 1 ? \"\" : \"s\"}, first at \\`${violation.target}\\``,\n artifact.htmlPath,\n ),\n );\n}\n\n/** Playwright is an optional peer — absent is a diagnostic, not a crash. */\nasync function launchBrowser(): Promise<ConformanceBrowser | undefined> {\n // Both specifiers are tried: under an isolated `node_modules` a project that\n // installed `@playwright/test` cannot resolve bare `playwright`, even though\n // one depends on the other. Specifiers stay `string`-typed so the optional\n // peer is optional at type-check time as well as at runtime.\n const specifiers: string[] = [\"playwright\", \"@playwright/test\"];\n for (const specifier of specifiers) {\n let mod: { chromium?: { launch(): Promise<ConformanceBrowser> } };\n try {\n mod = (await import(specifier)) as typeof mod;\n } catch {\n continue;\n }\n if (mod.chromium) return mod.chromium.launch();\n }\n return undefined;\n}\n\nasync function startHost(artifact: McpAppArtifact): Promise<ConformanceHost> {\n const { startDevHost } = await import(\"../dev/index.js\");\n return startDevHost({\n htmlPath: artifact.htmlPath,\n fixtures: { conformance: {} },\n hostPort: 5480,\n });\n}\n\n/**\n * Exercise an App artifact through the real two-origin host and browser.\n * Browser and host ownership stays local so every startup path is cleaned up.\n */\nexport async function renderMcpAppArtifactWith(\n artifact: McpAppArtifact,\n dependencies: RenderMcpAppArtifactDependencies,\n): Promise<ConformanceDiagnostic[]> {\n let browser: ConformanceBrowser | undefined;\n try {\n browser = await dependencies.launchBrowser();\n } catch (caught) {\n return [\n error(\n \"render/unavailable\",\n `could not launch Chromium: ${caught instanceof Error ? caught.message : String(caught)}`,\n artifact.htmlPath,\n ),\n ];\n }\n if (!browser) {\n return [\n error(\n \"render/unavailable\",\n \"render requested but Playwright is not installed (npm i -D playwright)\",\n artifact.htmlPath,\n ),\n ];\n }\n\n let host: ConformanceHost | undefined;\n const failures: ConformanceDiagnostic[] = [];\n try {\n host = await dependencies.startHost(artifact);\n const page = await browser.newPage();\n const pageErrors: string[] = [];\n page.on(\"pageerror\", (caught) => pageErrors.push(caught.message));\n await page.goto(host.hostUrl);\n\n const deadline = Date.now() + 20_000;\n let mounted = false;\n let renderedContent = false;\n let mountedAt: number | undefined;\n let boundary: string | undefined;\n while (!boundary && Date.now() < deadline && pageErrors.length === 0) {\n const view = page.frames().find((frame) => frame.url().includes(\"srcdoc\"));\n const state = view\n ? await view\n .evaluate(() => {\n const failed = document.querySelector(\"[data-mountly-mcp-error]\");\n const root = document.querySelector(\"#mountly-mcp-root\");\n const bounds = root?.getBoundingClientRect();\n return {\n state: root?.getAttribute(\"data-mountly-mcp-state\") ?? undefined,\n html: root?.innerHTML ?? \"\",\n shadowHtml: root?.shadowRoot?.innerHTML ?? \"\",\n visibleHeight: bounds?.height ?? 0,\n error: failed\n ? `${failed.getAttribute(\"data-mountly-mcp-error\") ?? \"\"}: ${failed.textContent ?? \"\"}`\n : undefined,\n };\n })\n .catch(() => undefined)\n : undefined;\n boundary = state?.error;\n // Sticky: the bridge's state only moves forward, and a View that mounted\n // stays mounted even if a later poll reads the frame mid-navigation.\n mounted ||= state?.state === \"mounted\";\n if (mounted && mountedAt === undefined) mountedAt = Date.now();\n renderedContent =\n (state?.html ?? \"\").trim().length > 0 ||\n (state?.shadowHtml ?? \"\").trim().length > 0 ||\n (state?.visibleHeight ?? 0) > 0;\n if (mounted && renderedContent) break;\n // Framework renderers may commit after mount() returns. Give them a\n // bounded window while still failing a genuinely empty View quickly.\n if (mountedAt !== undefined && Date.now() - mountedAt >= 2_000) break;\n await new Promise((done) => setTimeout(done, 100));\n }\n\n if (boundary) {\n failures.push(\n error(\"render/error-boundary\", `View rendered an error: ${boundary}`, artifact.htmlPath),\n );\n } else if (pageErrors.length > 0) {\n failures.push(\n error(\"render/threw\", `View threw while loading: ${pageErrors[0]}`, artifact.htmlPath),\n );\n } else if (!mounted) {\n failures.push(\n error(\n \"render/timeout\",\n \"View never mounted: the handshake or View mount did not complete\",\n artifact.htmlPath,\n ),\n );\n } else if (!renderedContent) {\n failures.push(\n error(\"render/blank\", \"View mounted but rendered no content\", artifact.htmlPath),\n );\n } else {\n // Only a View that actually rendered can be audited — anything else has\n // already failed, and axe would just report on an empty document.\n const view = page.frames().find((frame) => frame.url().includes(\"srcdoc\"));\n if (view) failures.push(...(await auditAccessibility(view, artifact)));\n }\n } catch (caught) {\n failures.push(\n error(\n \"render/failed\",\n caught instanceof Error ? caught.message : String(caught),\n artifact.htmlPath,\n ),\n );\n } finally {\n await Promise.allSettled([browser.close(), host?.close() ?? Promise.resolve()]);\n }\n return failures;\n}\n\nexport function renderMcpAppArtifact(artifact: McpAppArtifact): Promise<ConformanceDiagnostic[]> {\n return renderMcpAppArtifactWith(artifact, { launchBrowser, startHost });\n}\n"],"mappings":"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","names":["error","warning"]}
@@ -1,4 +1,4 @@
1
- import type { Plugin } from "vite";
1
+ import { type Plugin } from "vite";
2
2
  import type { BuildSelfContainedOptions } from "../build/index.js";
3
3
  export interface MountlyMcpViewOptions extends Omit<BuildSelfContainedOptions, "entry" | "output" | "cssEntry"> {
4
4
  entry: string;
@@ -9,12 +9,26 @@ export interface MountlyMcpViewsOptions {
9
9
  apps: ReadonlyArray<MountlyMcpViewOptions>;
10
10
  /** Defaults to `<outDir>/mountly-mcp.manifest.json`; false disables it. */
11
11
  manifest?: string | false;
12
+ /**
13
+ * Also write every View, HTML included, as an ES module, for a server that
14
+ * cannot read files — Cloudflare Workers, say: `import views from
15
+ * "./dist/mountly-mcp.views.js"`, then `views.map((artifact) => ({ artifact }))`.
16
+ * `true` writes `<outDir>/mountly-mcp.views.js`; a string names the path.
17
+ */
18
+ module?: string | boolean;
12
19
  }
20
+ export declare const MCP_APP_VIEWS_MODULE_FILE = "mountly-mcp.views.js";
13
21
  export type MountlyMcpViteOptions = MountlyMcpViewOptions | MountlyMcpViewsOptions;
14
22
  export interface MountlyMcpViteApi {
15
23
  apps: ReadonlyArray<MountlyMcpViewOptions>;
16
24
  manifest?: string | false;
17
25
  }
26
+ /**
27
+ * Rewrite a module's worker calls. In a `.vue` or `.svelte` file each
28
+ * `<script>` is parsed on its own, and its imports go inside that block —
29
+ * anything outside it is not script, and the compiler drops it.
30
+ */
31
+ export declare function inlineWorkers(code: string, file?: string): string | null;
18
32
  /**
19
33
  * Build one or many independent MCP Apps Views. Each View is a Vite build
20
34
  * environment, so frameworks and CSS remain isolated while callers configure
@@ -1,7 +1,9 @@
1
1
  import {
2
- MCP_APP_MANIFEST_FILE,
3
2
  writeMcpAppManifest
4
- } from "../chunk-DKK26TRS.js";
3
+ } from "../chunk-RLDSMU5M.js";
4
+ import {
5
+ MCP_APP_MANIFEST_FILE
6
+ } from "../chunk-6T5EIQGE.js";
5
7
  import {
6
8
  buildMcpResourceFromSource
7
9
  } from "../chunk-IDGTP2NI.js";
@@ -9,8 +11,37 @@ import "../chunk-M4WFJR3C.js";
9
11
  import "../chunk-YJA4H6IB.js";
10
12
 
11
13
  // src/vite/index.ts
12
- import { rm } from "fs/promises";
14
+ import { readFile, rm, writeFile } from "fs/promises";
13
15
  import { basename, isAbsolute, join, resolve } from "path";
16
+ import { parseAst } from "vite";
17
+ var MCP_APP_VIEWS_MODULE_FILE = "mountly-mcp.views.js";
18
+ async function writeViewsModule(path, artifacts) {
19
+ const views = await Promise.all(
20
+ artifacts.map(async ({ name, uri, declaration, htmlPath }) => ({
21
+ name,
22
+ uri,
23
+ declaration,
24
+ html: await readFile(htmlPath, "utf8")
25
+ }))
26
+ );
27
+ await writeFile(
28
+ path,
29
+ `// Generated by mountly-mcp. Do not edit.
30
+ export default ${JSON.stringify(views, null, 2)};
31
+ `,
32
+ "utf8"
33
+ );
34
+ await writeFile(
35
+ // `views.mjs` pairs with `views.d.mts`, which is where TypeScript looks.
36
+ path.replace(/\.(m?)js$/, ".d.$1ts"),
37
+ `// Generated by mountly-mcp. Do not edit.
38
+ import type { McpAppSource } from "mountly-mcp/artifact";
39
+ declare const views: ReadonlyArray<McpAppSource>;
40
+ export default views;
41
+ `,
42
+ "utf8"
43
+ );
44
+ }
14
45
  function isCollection(options) {
15
46
  return "apps" in options;
16
47
  }
@@ -22,11 +53,83 @@ function outputPath(root, outDir, app) {
22
53
  if (!app.output) return join(outDir, `${basename(app.name)}.html`);
23
54
  return isAbsolute(app.output) ? app.output : resolve(root, app.output);
24
55
  }
56
+ function isImportMetaUrl(node) {
57
+ return node?.type === "MemberExpression" && node.object?.type === "MetaProperty" && node.object.meta?.name === "import" && node.object.property?.name === "meta" && node.property?.name === "url";
58
+ }
59
+ function stringValue(node) {
60
+ if (node?.type === "Literal" && typeof node.value === "string") return node.value;
61
+ if (node?.type === "TemplateLiteral" && node.expressions.length === 0) {
62
+ return node.quasis[0]?.value?.cooked;
63
+ }
64
+ return void 0;
65
+ }
66
+ function workerEdits(script, lang, offset, imports, count) {
67
+ const edits = [];
68
+ const walk = (node) => {
69
+ if (Array.isArray(node)) return node.forEach(walk);
70
+ if (!node || typeof node !== "object") return;
71
+ const n = node;
72
+ if (n.type === "NewExpression" && (n.callee?.name === "Worker" || n.callee?.name === "SharedWorker")) {
73
+ const [url, options] = n.arguments ?? [];
74
+ const path = url?.type === "NewExpression" && url.callee?.name === "URL" ? stringValue(url.arguments?.[0]) : void 0;
75
+ if (path !== void 0 && isImportMetaUrl(url.arguments?.[1])) {
76
+ const id = `__mountlyMcpWorker${count.n++}`;
77
+ const query = n.callee.name === "SharedWorker" ? "sharedworker" : "worker";
78
+ imports.push(`import ${id} from ${JSON.stringify(`${path}?${query}&inline`)};`);
79
+ const end = options ? options.start : n.end;
80
+ const text = options ? `new ${id}(` : `new ${id}()`;
81
+ const breaks = script.slice(n.start, end).split("\n").length - 1;
82
+ edits.push([offset + n.start, offset + end, text + "\n".repeat(breaks)]);
83
+ }
84
+ }
85
+ for (const key in n) walk(n[key]);
86
+ };
87
+ walk(parseAst(script, { lang }));
88
+ return edits;
89
+ }
90
+ function langOf(file, attributes = "") {
91
+ const lang = /\blang\s*=\s*["']?(tsx|ts|jsx|js)\b/.exec(attributes)?.[1] ?? /\.([cm]?[jt]sx?)$/.exec(file)?.[1];
92
+ if (lang === "tsx" || lang === "jsx") return lang;
93
+ return lang && /ts$/.test(lang) ? "ts" : "js";
94
+ }
95
+ function inlineWorkers(code, file = "view.ts") {
96
+ if (!code.includes("import.meta")) return null;
97
+ const edits = [];
98
+ const count = { n: 0 };
99
+ try {
100
+ if (/\.(vue|svelte)$/.test(file)) {
101
+ for (const block of code.matchAll(/(<script\b([^>]*)>)([\s\S]*?)<\/script>/g)) {
102
+ const start = (block.index ?? 0) + (block[1]?.length ?? 0);
103
+ const imports = [];
104
+ edits.push(...workerEdits(block[3] ?? "", langOf(file, block[2]), start, imports, count));
105
+ if (imports.length) edits.push([start, start, imports.join("")]);
106
+ }
107
+ } else {
108
+ const imports = [];
109
+ edits.push(...workerEdits(code, langOf(file), 0, imports, count));
110
+ if (imports.length) edits.push([0, 0, imports.join("")]);
111
+ }
112
+ } catch {
113
+ return null;
114
+ }
115
+ if (!edits.length) return null;
116
+ let out = code;
117
+ for (const [start, end, text] of edits.sort((a, b) => b[0] - a[0] || b[1] - a[1])) {
118
+ out = out.slice(0, start) + text + out.slice(end);
119
+ }
120
+ return out;
121
+ }
25
122
  function mountlyMcpViews(options) {
26
123
  const configuredApps = isCollection(options) ? [...options.apps] : [options];
27
124
  const selectedForDev = process.env.MOUNTLY_MCP_SELECTED_APP;
28
125
  const apps = selectedForDev ? configuredApps.filter((app) => app.name === selectedForDev) : configuredApps;
29
126
  const manifestOption = isCollection(options) ? options.manifest : void 0;
127
+ const moduleOption = isCollection(options) ? options.module : void 0;
128
+ if (typeof moduleOption === "string" && !/\.m?js$/.test(moduleOption)) {
129
+ throw new Error(
130
+ `mountly-mcp: module must name a .js or .mjs file (received '${moduleOption}')`
131
+ );
132
+ }
30
133
  if (apps.length === 0) throw new Error("mountly-mcp: apps must contain at least one View");
31
134
  const names = /* @__PURE__ */ new Set();
32
135
  const uris = /* @__PURE__ */ new Set();
@@ -69,10 +172,22 @@ function mountlyMcpViews(options) {
69
172
  enforce: "post",
70
173
  // Only meaningful during `vite build`; a dev server has no bundle to emit.
71
174
  apply: "build",
72
- config(_config, env) {
175
+ // Before TypeScript is stripped and before Vite's own worker plugin claims
176
+ // the `new URL(…)` and emits a separate file.
177
+ transform: {
178
+ order: "pre",
179
+ handler(code, id) {
180
+ if (id.includes("/node_modules/") || id.includes("?")) return null;
181
+ const out = inlineWorkers(code, id);
182
+ return out === null ? null : { code: out, map: null };
183
+ }
184
+ },
185
+ config(config, env) {
73
186
  const first = environments[0];
74
187
  if (!first) throw new Error("mountly-mcp: configure at least one app");
75
188
  return {
189
+ // An inline worker is one blob, so its own bundle must be one file too.
190
+ ...config.worker?.format ? {} : { worker: { format: "iife" } },
76
191
  // Vite's lib mode leaves `process.env.NODE_ENV` for a consuming bundler
77
192
  // to replace. There isn't one — the bundle is inlined into HTML and run
78
193
  // in a sandboxed iframe, where `process` is undefined and React and Vue
@@ -192,20 +307,27 @@ function mountlyMcpViews(options) {
192
307
  if (source.cssFile) await rm(join(outDir, source.cssFile), { force: true });
193
308
  }
194
309
  this.info?.(`mountly-mcp: ${environment.app.uri} \u2192 ${result.htmlPath}`);
195
- if (!manifestWritten && artifacts.size === environments.length && manifestOption !== false) {
196
- manifestWritten = true;
197
- const path = manifestOption ? isAbsolute(manifestOption) ? manifestOption : resolve(root, manifestOption) : join(outDir, MCP_APP_MANIFEST_FILE);
198
- await writeMcpAppManifest(
199
- path,
200
- environments.map((item) => artifacts.get(item.name))
201
- );
310
+ if (manifestWritten || artifacts.size !== environments.length) return;
311
+ manifestWritten = true;
312
+ const all = environments.map((item) => artifacts.get(item.name));
313
+ const at = (path) => isAbsolute(path) ? path : resolve(root, path);
314
+ if (manifestOption !== false) {
315
+ const path = manifestOption ? at(manifestOption) : join(outDir, MCP_APP_MANIFEST_FILE);
316
+ await writeMcpAppManifest(path, all);
202
317
  this.info?.(`mountly-mcp: manifest \u2192 ${path}`);
203
318
  }
319
+ if (moduleOption) {
320
+ const path = typeof moduleOption === "string" ? at(moduleOption) : join(outDir, MCP_APP_VIEWS_MODULE_FILE);
321
+ await writeViewsModule(path, all);
322
+ this.info?.(`mountly-mcp: views module \u2192 ${path}`);
323
+ }
204
324
  }
205
325
  };
206
326
  return plugin;
207
327
  }
208
328
  export {
329
+ MCP_APP_VIEWS_MODULE_FILE,
330
+ inlineWorkers,
209
331
  mountlyMcpViews
210
332
  };
211
333
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/vite/index.ts"],"sourcesContent":["import { rm } from \"node:fs/promises\";\nimport { basename, isAbsolute, join, resolve } from \"node:path\";\nimport type { BuildEnvironment, Plugin, UserConfig } from \"vite\";\nimport { MCP_APP_MANIFEST_FILE, writeMcpAppManifest } from \"../artifact/index.js\";\nimport type { McpAppArtifact } from \"../artifact/index.js\";\nimport { buildMcpResourceFromSource } from \"../build/index.js\";\nimport type { BuildSelfContainedOptions } from \"../build/index.js\";\n\nexport interface MountlyMcpViewOptions extends Omit<\n BuildSelfContainedOptions,\n \"entry\" | \"output\" | \"cssEntry\"\n> {\n entry: string;\n output?: string;\n cleanIntermediate?: boolean;\n}\n\nexport interface MountlyMcpViewsOptions {\n apps: ReadonlyArray<MountlyMcpViewOptions>;\n /** Defaults to `<outDir>/mountly-mcp.manifest.json`; false disables it. */\n manifest?: string | false;\n}\n\nexport type MountlyMcpViteOptions = MountlyMcpViewOptions | MountlyMcpViewsOptions;\n\nexport interface MountlyMcpViteApi {\n apps: ReadonlyArray<MountlyMcpViewOptions>;\n manifest?: string | false;\n}\n\nfunction isCollection(options: MountlyMcpViteOptions): options is MountlyMcpViewsOptions {\n return \"apps\" in options;\n}\n\nfunction safeBuildName(name: string, index: number): string {\n const safe = name.replace(/[^a-zA-Z0-9_-]/g, \"_\");\n return `mountly_mcp_${index}_${safe || \"app\"}`;\n}\n\nfunction outputPath(root: string, outDir: string, app: MountlyMcpViewOptions): string {\n if (!app.output) return join(outDir, `${basename(app.name)}.html`);\n return isAbsolute(app.output) ? app.output : resolve(root, app.output);\n}\n\n/**\n * Build one or many independent MCP Apps Views. Each View is a Vite build\n * environment, so frameworks and CSS remain isolated while callers configure\n * the collection once.\n */\nexport function mountlyMcpViews(options: MountlyMcpViteOptions): Plugin {\n const configuredApps = isCollection(options) ? [...options.apps] : [options];\n const selectedForDev = process.env.MOUNTLY_MCP_SELECTED_APP;\n const apps = selectedForDev\n ? configuredApps.filter((app) => app.name === selectedForDev)\n : configuredApps;\n const manifestOption = isCollection(options) ? options.manifest : undefined;\n if (apps.length === 0) throw new Error(\"mountly-mcp: apps must contain at least one View\");\n\n const names = new Set<string>();\n const uris = new Set<string>();\n for (const app of apps) {\n if (!app.uri.startsWith(\"ui://\")) {\n throw new Error(\n `mountly-mcp: UI resource uri must use the 'ui://' scheme (received '${app.uri}')`,\n );\n }\n if (names.has(app.name)) throw new Error(`mountly-mcp: duplicate app name '${app.name}'`);\n if (uris.has(app.uri)) throw new Error(`mountly-mcp: duplicate app uri '${app.uri}'`);\n names.add(app.name);\n uris.add(app.uri);\n }\n\n const environments = apps.map((app, index) => ({\n app,\n name: safeBuildName(app.name, index),\n jsFile: `view-${index}-${basename(app.name)}.js`,\n cssFile: `view-${index}-${basename(app.name)}.css`,\n }));\n const byEnvironment = new Map(environments.map((environment) => [environment.name, environment]));\n const emitted = new Map<\n string,\n { js?: string; css?: string; jsFile?: string; cssFile?: string; names: string[] }\n >();\n const artifacts = new Map<string, McpAppArtifact>();\n let root = process.cwd();\n let outDir = resolve(root, \"dist\");\n let manifestWritten = false;\n\n function currentEnvironment(context: unknown) {\n const name = (context as { environment?: { name?: string } }).environment?.name;\n return (\n (name ? byEnvironment.get(name) : undefined) ??\n (environments.length === 1 ? environments[0] : undefined)\n );\n }\n\n const plugin: Plugin = {\n name: \"mountly-mcp-views\",\n api: {\n // The CLI consumes this documented plugin interface instead of scraping\n // Vite's resolved build settings.\n mountlyMcp: { apps: configuredApps, manifest: manifestOption } satisfies MountlyMcpViteApi,\n },\n // After Vite core plugins: css-post writes the stylesheet in its own\n // closeBundle, and this plugin has to see the file to inline it.\n enforce: \"post\",\n // Only meaningful during `vite build`; a dev server has no bundle to emit.\n apply: \"build\",\n\n config(_config, env): UserConfig {\n const first = environments[0];\n if (!first) throw new Error(\"mountly-mcp: configure at least one app\");\n return {\n // Vite's lib mode leaves `process.env.NODE_ENV` for a consuming bundler\n // to replace. There isn't one — the bundle is inlined into HTML and run\n // in a sandboxed iframe, where `process` is undefined and React and Vue\n // throw on their first environment check.\n define: { \"process.env.NODE_ENV\": JSON.stringify(env.mode) },\n // Legacy `vite build` builds one environment. Preserve that zero-config\n // path for single-View projects; multi-View projects use\n // `mountly-mcp build`, which drives the environments below.\n build: {\n lib: {\n entry: first.app.entry,\n // IIFE so the bundle inlines into a single <script> with no import\n // graph left to resolve inside the sandboxed iframe.\n formats: [\"iife\"] as const,\n name: \"__mountlyMcpEntry0\",\n fileName: () => first.jsFile,\n // Named explicitly: otherwise a lib build derives the stylesheet\n // name from package.json and fails outright when there isn't one.\n cssFileName: first.cssFile.replace(/\\.css$/, \"\"),\n },\n // One stylesheet to inline; per-chunk CSS would strand imports.\n cssCodeSplit: false,\n },\n builder: {\n sharedPlugins: true,\n async buildApp(builder) {\n for (const environment of environments) {\n const target = builder.environments[environment.name] as BuildEnvironment | undefined;\n if (!target)\n throw new Error(`mountly-mcp: missing Vite environment '${environment.name}'`);\n await builder.build(target);\n }\n },\n },\n environments: Object.fromEntries(\n environments.map((environment, index) => [\n environment.name,\n {\n // A custom environment defaults to a server consumer, which\n // externalizes node_modules — React and Vue would become bare\n // globals and the view would die on `vue is not defined`. A\n // `ui://` resource has no import graph at runtime, so everything\n // must be bundled in.\n consumer: \"client\" as const,\n resolve: { noExternal: true as const },\n build: {\n emptyOutDir: index === 0,\n lib: {\n entry: environment.app.entry,\n formats: [\"iife\"] as const,\n name: `__mountlyMcpEntry${index}`,\n fileName: () => environment.jsFile,\n cssFileName: environment.cssFile.replace(/\\.css$/, \"\"),\n },\n cssCodeSplit: false,\n },\n },\n ]),\n ),\n };\n },\n\n configResolved(config) {\n root = config.root;\n outDir = resolve(config.root, config.build.outDir);\n },\n\n buildStart() {\n const name = (this as { environment?: { name?: string } }).environment?.name;\n if (apps.length > 1 && !byEnvironment.has(String(name))) {\n this.error(\n \"mountly-mcp: multi-View builds require 'mountly-mcp build' (or 'vite build --app') so Vite builds every environment\",\n );\n }\n },\n\n generateBundle: {\n order: \"post\",\n handler(_outputOptions, bundle) {\n const environment = currentEnvironment(this);\n if (!environment) return;\n const chunk =\n bundle[environment.jsFile] ??\n Object.values(bundle).find((item) => item.type === \"chunk\" && item.isEntry);\n const cssAsset =\n bundle[environment.cssFile] ??\n Object.values(bundle).find(\n (item) => item.type === \"asset\" && item.fileName.endsWith(\".css\"),\n );\n emitted.set(environment.name, {\n js: chunk && \"code\" in chunk ? chunk.code : undefined,\n css: cssAsset && \"source\" in cssAsset ? cssAsset.source.toString() : undefined,\n jsFile: chunk?.fileName,\n cssFile: cssAsset?.fileName,\n names: Object.keys(bundle),\n });\n },\n },\n\n async closeBundle() {\n const environment = currentEnvironment(this);\n if (!environment || artifacts.has(environment.name)) return;\n const source = emitted.get(environment.name);\n if (source?.js === undefined) {\n this.error(\n `mountly-mcp: the build emitted no ${environment.jsFile} for '${environment.app.entry}'. Emitted: ${source?.names.join(\", \") || \"nothing\"}`,\n );\n return;\n }\n const {\n entry: _entry,\n output: _output,\n cleanIntermediate = true,\n ...resourceOptions\n } = environment.app;\n const result = await buildMcpResourceFromSource({\n ...resourceOptions,\n js: source.js,\n css: source.css,\n output: outputPath(root, outDir, environment.app),\n });\n artifacts.set(environment.name, result.artifact);\n if (cleanIntermediate) {\n if (source.jsFile) await rm(join(outDir, source.jsFile), { force: true });\n if (source.cssFile) await rm(join(outDir, source.cssFile), { force: true });\n }\n this.info?.(`mountly-mcp: ${environment.app.uri} → ${result.htmlPath}`);\n\n if (!manifestWritten && artifacts.size === environments.length && manifestOption !== false) {\n manifestWritten = true;\n const path = manifestOption\n ? isAbsolute(manifestOption)\n ? manifestOption\n : resolve(root, manifestOption)\n : join(outDir, MCP_APP_MANIFEST_FILE);\n await writeMcpAppManifest(\n path,\n environments.map((item) => artifacts.get(item.name) as McpAppArtifact),\n );\n this.info?.(`mountly-mcp: manifest → ${path}`);\n }\n },\n };\n\n return plugin;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/vite/index.ts"],"sourcesContent":["import { readFile, rm, writeFile } from \"node:fs/promises\";\nimport { basename, isAbsolute, join, resolve } from \"node:path\";\nimport { parseAst, type BuildEnvironment, type Plugin, type UserConfig } from \"vite\";\nimport { MCP_APP_MANIFEST_FILE, writeMcpAppManifest } from \"../artifact/index.js\";\nimport type { McpAppArtifact } from \"../artifact/index.js\";\nimport { buildMcpResourceFromSource } from \"../build/index.js\";\nimport type { BuildSelfContainedOptions } from \"../build/index.js\";\n\nexport interface MountlyMcpViewOptions extends Omit<\n BuildSelfContainedOptions,\n \"entry\" | \"output\" | \"cssEntry\"\n> {\n entry: string;\n output?: string;\n cleanIntermediate?: boolean;\n}\n\nexport interface MountlyMcpViewsOptions {\n apps: ReadonlyArray<MountlyMcpViewOptions>;\n /** Defaults to `<outDir>/mountly-mcp.manifest.json`; false disables it. */\n manifest?: string | false;\n /**\n * Also write every View, HTML included, as an ES module, for a server that\n * cannot read files — Cloudflare Workers, say: `import views from\n * \"./dist/mountly-mcp.views.js\"`, then `views.map((artifact) => ({ artifact }))`.\n * `true` writes `<outDir>/mountly-mcp.views.js`; a string names the path.\n */\n module?: string | boolean;\n}\n\nexport const MCP_APP_VIEWS_MODULE_FILE = \"mountly-mcp.views.js\";\n\n/** `mountly-mcp.views.js` and the declaration beside it. */\nasync function writeViewsModule(path: string, artifacts: ReadonlyArray<McpAppArtifact>) {\n const views = await Promise.all(\n artifacts.map(async ({ name, uri, declaration, htmlPath }) => ({\n name,\n uri,\n declaration,\n html: await readFile(htmlPath, \"utf8\"),\n })),\n );\n await writeFile(\n path,\n `// Generated by mountly-mcp. Do not edit.\\nexport default ${JSON.stringify(views, null, 2)};\\n`,\n \"utf8\",\n );\n await writeFile(\n // `views.mjs` pairs with `views.d.mts`, which is where TypeScript looks.\n path.replace(/\\.(m?)js$/, \".d.$1ts\"),\n `// Generated by mountly-mcp. Do not edit.\\nimport type { McpAppSource } from \"mountly-mcp/artifact\";\\ndeclare const views: ReadonlyArray<McpAppSource>;\\nexport default views;\\n`,\n \"utf8\",\n );\n}\n\nexport type MountlyMcpViteOptions = MountlyMcpViewOptions | MountlyMcpViewsOptions;\n\nexport interface MountlyMcpViteApi {\n apps: ReadonlyArray<MountlyMcpViewOptions>;\n manifest?: string | false;\n}\n\nfunction isCollection(options: MountlyMcpViteOptions): options is MountlyMcpViewsOptions {\n return \"apps\" in options;\n}\n\nfunction safeBuildName(name: string, index: number): string {\n const safe = name.replace(/[^a-zA-Z0-9_-]/g, \"_\");\n return `mountly_mcp_${index}_${safe || \"app\"}`;\n}\n\nfunction outputPath(root: string, outDir: string, app: MountlyMcpViewOptions): string {\n if (!app.output) return join(outDir, `${basename(app.name)}.html`);\n return isAbsolute(app.output) ? app.output : resolve(root, app.output);\n}\n\n/**\n * `new Worker(new URL(\"./w.ts\", import.meta.url), …)` emits the worker as its own\n * file, which a `ui://` resource cannot serve: the View is one HTML document.\n * Rewrite each to Vite's `?worker&inline` import, which bundles the worker into\n * the View and starts it from a blob URL — so the host's CSP must allow\n * `worker-src blob:`. Workers built any other way are left alone; `mountly-mcp\n * doctor` points them out.\n */\ntype Node = Record<string, any>;\ntype Edit = [start: number, end: number, text: string];\n\nfunction isImportMetaUrl(node: Node | undefined): boolean {\n return (\n node?.type === \"MemberExpression\" &&\n node.object?.type === \"MetaProperty\" &&\n node.object.meta?.name === \"import\" &&\n node.object.property?.name === \"meta\" &&\n node.property?.name === \"url\"\n );\n}\n\nfunction stringValue(node: Node | undefined): string | undefined {\n if (node?.type === \"Literal\" && typeof node.value === \"string\") return node.value;\n if (node?.type === \"TemplateLiteral\" && node.expressions.length === 0) {\n return node.quasis[0]?.value?.cooked;\n }\n return undefined;\n}\n\n/**\n * The edits for every worker call in one script. Parsed, not pattern-matched:\n * the same characters in a comment or a string are not a call.\n */\nfunction workerEdits(\n script: string,\n lang: \"js\" | \"jsx\" | \"ts\" | \"tsx\",\n offset: number,\n imports: string[],\n count: { n: number },\n): Edit[] {\n const edits: Edit[] = [];\n const walk = (node: unknown): void => {\n if (Array.isArray(node)) return node.forEach(walk);\n if (!node || typeof node !== \"object\") return;\n const n = node as Node;\n if (\n n.type === \"NewExpression\" &&\n (n.callee?.name === \"Worker\" || n.callee?.name === \"SharedWorker\")\n ) {\n const [url, options] = n.arguments ?? [];\n const path =\n url?.type === \"NewExpression\" && url.callee?.name === \"URL\"\n ? stringValue(url.arguments?.[0])\n : undefined;\n if (path !== undefined && isImportMetaUrl(url.arguments?.[1])) {\n const id = `__mountlyMcpWorker${count.n++}`;\n const query = n.callee.name === \"SharedWorker\" ? \"sharedworker\" : \"worker\";\n imports.push(`import ${id} from ${JSON.stringify(`${path}?${query}&inline`)};`);\n // The wrapper takes the options object (it reads `name`) as its only\n // argument. Without one, the whole call goes, trailing comma included.\n const end = options ? options.start : n.end;\n const text = options ? `new ${id}(` : `new ${id}()`;\n // Keep the span's line breaks, so every later line keeps its number.\n const breaks = script.slice(n.start, end).split(\"\\n\").length - 1;\n edits.push([offset + n.start, offset + end, text + \"\\n\".repeat(breaks)]);\n }\n }\n for (const key in n) walk(n[key]);\n };\n walk(parseAst(script, { lang }));\n return edits;\n}\n\nfunction langOf(file: string, attributes = \"\"): \"js\" | \"jsx\" | \"ts\" | \"tsx\" {\n const lang =\n /\\blang\\s*=\\s*[\"']?(tsx|ts|jsx|js)\\b/.exec(attributes)?.[1] ??\n /\\.([cm]?[jt]sx?)$/.exec(file)?.[1];\n if (lang === \"tsx\" || lang === \"jsx\") return lang;\n return lang && /ts$/.test(lang) ? \"ts\" : \"js\";\n}\n\n/**\n * Rewrite a module's worker calls. In a `.vue` or `.svelte` file each\n * `<script>` is parsed on its own, and its imports go inside that block —\n * anything outside it is not script, and the compiler drops it.\n */\nexport function inlineWorkers(code: string, file = \"view.ts\"): string | null {\n if (!code.includes(\"import.meta\")) return null;\n const edits: Edit[] = [];\n const count = { n: 0 };\n try {\n if (/\\.(vue|svelte)$/.test(file)) {\n for (const block of code.matchAll(/(<script\\b([^>]*)>)([\\s\\S]*?)<\\/script>/g)) {\n const start = (block.index ?? 0) + (block[1]?.length ?? 0);\n const imports: string[] = [];\n edits.push(...workerEdits(block[3] ?? \"\", langOf(file, block[2]), start, imports, count));\n if (imports.length) edits.push([start, start, imports.join(\"\")]);\n }\n } else {\n const imports: string[] = [];\n edits.push(...workerEdits(code, langOf(file), 0, imports, count));\n if (imports.length) edits.push([0, 0, imports.join(\"\")]);\n }\n } catch {\n // Unparseable here means some later plugin owns this syntax; leave it to Vite.\n return null;\n }\n if (!edits.length) return null;\n let out = code;\n // Right to left, so earlier offsets stay valid.\n for (const [start, end, text] of edits.sort((a, b) => b[0] - a[0] || b[1] - a[1])) {\n out = out.slice(0, start) + text + out.slice(end);\n }\n return out;\n}\n\n/**\n * Build one or many independent MCP Apps Views. Each View is a Vite build\n * environment, so frameworks and CSS remain isolated while callers configure\n * the collection once.\n */\nexport function mountlyMcpViews(options: MountlyMcpViteOptions): Plugin {\n const configuredApps = isCollection(options) ? [...options.apps] : [options];\n const selectedForDev = process.env.MOUNTLY_MCP_SELECTED_APP;\n const apps = selectedForDev\n ? configuredApps.filter((app) => app.name === selectedForDev)\n : configuredApps;\n const manifestOption = isCollection(options) ? options.manifest : undefined;\n const moduleOption = isCollection(options) ? options.module : undefined;\n if (typeof moduleOption === \"string\" && !/\\.m?js$/.test(moduleOption)) {\n throw new Error(\n `mountly-mcp: module must name a .js or .mjs file (received '${moduleOption}')`,\n );\n }\n if (apps.length === 0) throw new Error(\"mountly-mcp: apps must contain at least one View\");\n\n const names = new Set<string>();\n const uris = new Set<string>();\n for (const app of apps) {\n if (!app.uri.startsWith(\"ui://\")) {\n throw new Error(\n `mountly-mcp: UI resource uri must use the 'ui://' scheme (received '${app.uri}')`,\n );\n }\n if (names.has(app.name)) throw new Error(`mountly-mcp: duplicate app name '${app.name}'`);\n if (uris.has(app.uri)) throw new Error(`mountly-mcp: duplicate app uri '${app.uri}'`);\n names.add(app.name);\n uris.add(app.uri);\n }\n\n const environments = apps.map((app, index) => ({\n app,\n name: safeBuildName(app.name, index),\n jsFile: `view-${index}-${basename(app.name)}.js`,\n cssFile: `view-${index}-${basename(app.name)}.css`,\n }));\n const byEnvironment = new Map(environments.map((environment) => [environment.name, environment]));\n const emitted = new Map<\n string,\n { js?: string; css?: string; jsFile?: string; cssFile?: string; names: string[] }\n >();\n const artifacts = new Map<string, McpAppArtifact>();\n let root = process.cwd();\n let outDir = resolve(root, \"dist\");\n let manifestWritten = false;\n\n function currentEnvironment(context: unknown) {\n const name = (context as { environment?: { name?: string } }).environment?.name;\n return (\n (name ? byEnvironment.get(name) : undefined) ??\n (environments.length === 1 ? environments[0] : undefined)\n );\n }\n\n const plugin: Plugin = {\n name: \"mountly-mcp-views\",\n api: {\n // The CLI consumes this documented plugin interface instead of scraping\n // Vite's resolved build settings.\n mountlyMcp: { apps: configuredApps, manifest: manifestOption } satisfies MountlyMcpViteApi,\n },\n // After Vite core plugins: css-post writes the stylesheet in its own\n // closeBundle, and this plugin has to see the file to inline it.\n enforce: \"post\",\n // Only meaningful during `vite build`; a dev server has no bundle to emit.\n apply: \"build\",\n\n // Before TypeScript is stripped and before Vite's own worker plugin claims\n // the `new URL(…)` and emits a separate file.\n transform: {\n order: \"pre\",\n handler(code, id) {\n if (id.includes(\"/node_modules/\") || id.includes(\"?\")) return null;\n const out = inlineWorkers(code, id);\n return out === null ? null : { code: out, map: null };\n },\n },\n\n config(config, env): UserConfig {\n const first = environments[0];\n if (!first) throw new Error(\"mountly-mcp: configure at least one app\");\n return {\n // An inline worker is one blob, so its own bundle must be one file too.\n ...(config.worker?.format ? {} : { worker: { format: \"iife\" as const } }),\n // Vite's lib mode leaves `process.env.NODE_ENV` for a consuming bundler\n // to replace. There isn't one — the bundle is inlined into HTML and run\n // in a sandboxed iframe, where `process` is undefined and React and Vue\n // throw on their first environment check.\n define: { \"process.env.NODE_ENV\": JSON.stringify(env.mode) },\n // Legacy `vite build` builds one environment. Preserve that zero-config\n // path for single-View projects; multi-View projects use\n // `mountly-mcp build`, which drives the environments below.\n build: {\n lib: {\n entry: first.app.entry,\n // IIFE so the bundle inlines into a single <script> with no import\n // graph left to resolve inside the sandboxed iframe.\n formats: [\"iife\"] as const,\n name: \"__mountlyMcpEntry0\",\n fileName: () => first.jsFile,\n // Named explicitly: otherwise a lib build derives the stylesheet\n // name from package.json and fails outright when there isn't one.\n cssFileName: first.cssFile.replace(/\\.css$/, \"\"),\n },\n // One stylesheet to inline; per-chunk CSS would strand imports.\n cssCodeSplit: false,\n },\n builder: {\n sharedPlugins: true,\n async buildApp(builder) {\n for (const environment of environments) {\n const target = builder.environments[environment.name] as BuildEnvironment | undefined;\n if (!target)\n throw new Error(`mountly-mcp: missing Vite environment '${environment.name}'`);\n await builder.build(target);\n }\n },\n },\n environments: Object.fromEntries(\n environments.map((environment, index) => [\n environment.name,\n {\n // A custom environment defaults to a server consumer, which\n // externalizes node_modules — React and Vue would become bare\n // globals and the view would die on `vue is not defined`. A\n // `ui://` resource has no import graph at runtime, so everything\n // must be bundled in.\n consumer: \"client\" as const,\n resolve: { noExternal: true as const },\n build: {\n emptyOutDir: index === 0,\n lib: {\n entry: environment.app.entry,\n formats: [\"iife\"] as const,\n name: `__mountlyMcpEntry${index}`,\n fileName: () => environment.jsFile,\n cssFileName: environment.cssFile.replace(/\\.css$/, \"\"),\n },\n cssCodeSplit: false,\n },\n },\n ]),\n ),\n };\n },\n\n configResolved(config) {\n root = config.root;\n outDir = resolve(config.root, config.build.outDir);\n },\n\n buildStart() {\n const name = (this as { environment?: { name?: string } }).environment?.name;\n if (apps.length > 1 && !byEnvironment.has(String(name))) {\n this.error(\n \"mountly-mcp: multi-View builds require 'mountly-mcp build' (or 'vite build --app') so Vite builds every environment\",\n );\n }\n },\n\n generateBundle: {\n order: \"post\",\n handler(_outputOptions, bundle) {\n const environment = currentEnvironment(this);\n if (!environment) return;\n const chunk =\n bundle[environment.jsFile] ??\n Object.values(bundle).find((item) => item.type === \"chunk\" && item.isEntry);\n const cssAsset =\n bundle[environment.cssFile] ??\n Object.values(bundle).find(\n (item) => item.type === \"asset\" && item.fileName.endsWith(\".css\"),\n );\n emitted.set(environment.name, {\n js: chunk && \"code\" in chunk ? chunk.code : undefined,\n css: cssAsset && \"source\" in cssAsset ? cssAsset.source.toString() : undefined,\n jsFile: chunk?.fileName,\n cssFile: cssAsset?.fileName,\n names: Object.keys(bundle),\n });\n },\n },\n\n async closeBundle() {\n const environment = currentEnvironment(this);\n if (!environment || artifacts.has(environment.name)) return;\n const source = emitted.get(environment.name);\n if (source?.js === undefined) {\n this.error(\n `mountly-mcp: the build emitted no ${environment.jsFile} for '${environment.app.entry}'. Emitted: ${source?.names.join(\", \") || \"nothing\"}`,\n );\n return;\n }\n const {\n entry: _entry,\n output: _output,\n cleanIntermediate = true,\n ...resourceOptions\n } = environment.app;\n const result = await buildMcpResourceFromSource({\n ...resourceOptions,\n js: source.js,\n css: source.css,\n output: outputPath(root, outDir, environment.app),\n });\n artifacts.set(environment.name, result.artifact);\n if (cleanIntermediate) {\n if (source.jsFile) await rm(join(outDir, source.jsFile), { force: true });\n if (source.cssFile) await rm(join(outDir, source.cssFile), { force: true });\n }\n this.info?.(`mountly-mcp: ${environment.app.uri} → ${result.htmlPath}`);\n\n if (manifestWritten || artifacts.size !== environments.length) return;\n manifestWritten = true;\n const all = environments.map((item) => artifacts.get(item.name) as McpAppArtifact);\n const at = (path: string) => (isAbsolute(path) ? path : resolve(root, path));\n if (manifestOption !== false) {\n const path = manifestOption ? at(manifestOption) : join(outDir, MCP_APP_MANIFEST_FILE);\n await writeMcpAppManifest(path, all);\n this.info?.(`mountly-mcp: manifest → ${path}`);\n }\n if (moduleOption) {\n const path =\n typeof moduleOption === \"string\"\n ? at(moduleOption)\n : join(outDir, MCP_APP_VIEWS_MODULE_FILE);\n await writeViewsModule(path, all);\n this.info?.(`mountly-mcp: views module → ${path}`);\n }\n },\n };\n\n return plugin;\n}\n"],"mappings":"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","names":[]}
@@ -16,6 +16,7 @@ import { type Component, type ComputedRef, type InjectionKey } from "vue";
16
16
  import type { AdapterOptions } from "mountly/adapter";
17
17
  import type { App, McpUiDisplayMode, McpUiHostContext } from "@modelcontextprotocol/ext-apps";
18
18
  import type { McpView, McpViewProps } from "../types.js";
19
+ import { type DownloadableFile } from "../download.js";
19
20
  /** What {@link createMcpView} provides to descendants. */
20
21
  export interface McpVueContext {
21
22
  app: App;
@@ -29,6 +30,11 @@ export declare const McpInjectionKey: InjectionKey<ComputedRef<McpVueContext>>;
29
30
  export declare function createMcpView<P extends object>(Component: Component<P>, options?: AdapterOptions): McpView;
30
31
  /** The view-side ext-apps App: call server tools, send messages, open links. */
31
32
  export declare function useMcpApp(): App;
33
+ /**
34
+ * Return a function that hands the user a file: through the host's
35
+ * `ui/download-file` where it has one, else an ordinary `<a download>`.
36
+ */
37
+ export declare function useDownload(): (file: DownloadableFile) => Promise<boolean>;
32
38
  /** Complete notification params; read the tool arguments from `.arguments`. */
33
39
  export declare function useToolInput<T = unknown>(): ComputedRef<T | undefined>;
34
40
  /**
package/dist/vue/index.js CHANGED
@@ -1,3 +1,6 @@
1
+ import {
2
+ downloadFile
3
+ } from "../chunk-BLT2MK32.js";
1
4
  import {
2
5
  publishMcpView
3
6
  } from "../chunk-5L3QHYWK.js";
@@ -49,6 +52,10 @@ function useEnsuredContext() {
49
52
  function useMcpApp() {
50
53
  return useEnsuredContext().value.app;
51
54
  }
55
+ function useDownload() {
56
+ const context = useEnsuredContext();
57
+ return (file) => downloadFile(context.value.app, file);
58
+ }
52
59
  function useToolInput() {
53
60
  const context = useEnsuredContext();
54
61
  return computed(() => context.value.toolInput);
@@ -83,6 +90,7 @@ export {
83
90
  McpInjectionKey,
84
91
  createMcpView,
85
92
  useDisplayMode,
93
+ useDownload,
86
94
  useHostContext,
87
95
  useMcpApp,
88
96
  useRequestDisplayMode,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/vue/index.ts"],"sourcesContent":["/**\n * Vue entry point: turn a Vue component into an MCP App View.\n *\n * ```ts\n * import { createMcpView } from \"mountly-mcp/vue\";\n * import Dashboard from \"./Dashboard.vue\";\n *\n * createMcpView(Dashboard);\n * ```\n *\n * The component receives the bridge's props (`mcp`, `toolInput`, `toolResult`,\n * `hostContext`) and can also reach them from any depth with the composables\n * below, which mirror `mountly-mcp/react`'s hooks.\n */\nimport {\n computed,\n defineComponent,\n h,\n inject,\n provide,\n type Component,\n type ComputedRef,\n type InjectionKey,\n} from \"vue\";\nimport { createWidget } from \"mountly-vue\";\nimport type { AdapterOptions } from \"mountly/adapter\";\nimport type { App, McpUiDisplayMode, McpUiHostContext } from \"@modelcontextprotocol/ext-apps\";\nimport type { McpView, McpViewProps } from \"../types.js\";\nimport { publishMcpView } from \"../publish.js\";\n\n/** What {@link createMcpView} provides to descendants. */\nexport interface McpVueContext {\n app: App;\n toolInput?: McpViewProps[\"toolInput\"];\n toolInputPartial?: McpViewProps[\"toolInputPartial\"];\n toolResult?: McpViewProps[\"toolResult\"];\n hostContext?: McpUiHostContext;\n}\n\nexport const McpInjectionKey: InjectionKey<ComputedRef<McpVueContext>> = Symbol(\"mountly-mcp\");\n\n/** Wraps a Vue component as an MCP Apps View and publishes it for the bridge. */\nexport function createMcpView<P extends object>(\n Component: Component<P>,\n options?: AdapterOptions,\n): McpView {\n const Wrapped = defineComponent({\n name: \"MountlyMcpProvider\",\n inheritAttrs: false,\n props: {\n mcp: { type: Object, required: true },\n toolInput: { type: Object, default: undefined },\n toolInputPartial: { type: Object, default: undefined },\n toolResult: { type: Object, default: undefined },\n hostContext: { type: Object, default: undefined },\n },\n setup(props, { attrs }) {\n const context = computed<McpVueContext>(() => ({\n app: props.mcp as App,\n toolInput: props.toolInput as McpVueContext[\"toolInput\"],\n toolInputPartial: props.toolInputPartial as McpVueContext[\"toolInputPartial\"],\n toolResult: props.toolResult as McpVueContext[\"toolResult\"],\n hostContext: props.hostContext as McpUiHostContext | undefined,\n }));\n provide(McpInjectionKey, context);\n return () => h(Component as Component, attrs);\n },\n });\n return publishMcpView(createWidget(Wrapped, options));\n}\n\nfunction useEnsuredContext(): ComputedRef<McpVueContext> {\n const context = inject(McpInjectionKey, null);\n if (!context) {\n throw new Error(\n \"mountly-mcp/vue: useMcpApp/useToolResult/etc must be used inside a component wrapped with createMcpView().\",\n );\n }\n return context;\n}\n\n/** The view-side ext-apps App: call server tools, send messages, open links. */\nexport function useMcpApp(): App {\n return useEnsuredContext().value.app;\n}\n\n/** Complete notification params; read the tool arguments from `.arguments`. */\nexport function useToolInput<T = unknown>(): ComputedRef<T | undefined> {\n const context = useEnsuredContext();\n return computed(() => context.value.toolInput as T | undefined);\n}\n\n/**\n * Streaming tool arguments (`ui/notifications/tool-input-partial`), present only\n * when the bridge runs with `streamToolInput`. Best-effort recovered JSON —\n * render loading states from it, never critical operations.\n */\nexport function useToolInputPartial<T = unknown>(): ComputedRef<Partial<T> | undefined> {\n const context = useEnsuredContext();\n return computed(() => context.value.toolInputPartial as Partial<T> | undefined);\n}\n\n/** Complete MCP tool result; structured data is available at `.structuredContent`. */\nexport function useToolResult<T = unknown>(): ComputedRef<T | undefined> {\n const context = useEnsuredContext();\n return computed(() => context.value.toolResult as T | undefined);\n}\n\n/** Latest host context (theme, styles, displayMode, container dimensions). */\nexport function useHostContext(): ComputedRef<McpUiHostContext | undefined> {\n const context = useEnsuredContext();\n return computed(() => context.value.hostContext);\n}\n\n/** Current display mode reported by the host. */\nexport function useDisplayMode(): ComputedRef<McpUiDisplayMode | undefined> {\n const context = useEnsuredContext();\n return computed(() => context.value.hostContext?.displayMode);\n}\n\n/**\n * Request a display mode change, resolving to the mode the host actually\n * applied. Modes the host didn't advertise are never requested — the spec\n * requires views to check `availableDisplayModes` first.\n */\nexport function useRequestDisplayMode(): (\n mode: McpUiDisplayMode,\n) => Promise<McpUiDisplayMode | undefined> {\n const context = useEnsuredContext();\n return async (mode) => {\n const { app, hostContext } = context.value;\n const available = hostContext?.availableDisplayModes;\n if (available && !available.includes(mode)) return hostContext?.displayMode;\n const result = await app.requestDisplayMode({ mode });\n return result.mode;\n };\n}\n"],"mappings":";;;;;AAcA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AACP,SAAS,oBAAoB;AAetB,IAAM,kBAA4D,uBAAO,aAAa;AAGtF,SAAS,cACd,WACA,SACS;AACT,QAAM,UAAU,gBAAgB;AAAA,IAC9B,MAAM;AAAA,IACN,cAAc;AAAA,IACd,OAAO;AAAA,MACL,KAAK,EAAE,MAAM,QAAQ,UAAU,KAAK;AAAA,MACpC,WAAW,EAAE,MAAM,QAAQ,SAAS,OAAU;AAAA,MAC9C,kBAAkB,EAAE,MAAM,QAAQ,SAAS,OAAU;AAAA,MACrD,YAAY,EAAE,MAAM,QAAQ,SAAS,OAAU;AAAA,MAC/C,aAAa,EAAE,MAAM,QAAQ,SAAS,OAAU;AAAA,IAClD;AAAA,IACA,MAAM,OAAO,EAAE,MAAM,GAAG;AACtB,YAAM,UAAU,SAAwB,OAAO;AAAA,QAC7C,KAAK,MAAM;AAAA,QACX,WAAW,MAAM;AAAA,QACjB,kBAAkB,MAAM;AAAA,QACxB,YAAY,MAAM;AAAA,QAClB,aAAa,MAAM;AAAA,MACrB,EAAE;AACF,cAAQ,iBAAiB,OAAO;AAChC,aAAO,MAAM,EAAE,WAAwB,KAAK;AAAA,IAC9C;AAAA,EACF,CAAC;AACD,SAAO,eAAe,aAAa,SAAS,OAAO,CAAC;AACtD;AAEA,SAAS,oBAAgD;AACvD,QAAM,UAAU,OAAO,iBAAiB,IAAI;AAC5C,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAGO,SAAS,YAAiB;AAC/B,SAAO,kBAAkB,EAAE,MAAM;AACnC;AAGO,SAAS,eAAwD;AACtE,QAAM,UAAU,kBAAkB;AAClC,SAAO,SAAS,MAAM,QAAQ,MAAM,SAA0B;AAChE;AAOO,SAAS,sBAAwE;AACtF,QAAM,UAAU,kBAAkB;AAClC,SAAO,SAAS,MAAM,QAAQ,MAAM,gBAA0C;AAChF;AAGO,SAAS,gBAAyD;AACvE,QAAM,UAAU,kBAAkB;AAClC,SAAO,SAAS,MAAM,QAAQ,MAAM,UAA2B;AACjE;AAGO,SAAS,iBAA4D;AAC1E,QAAM,UAAU,kBAAkB;AAClC,SAAO,SAAS,MAAM,QAAQ,MAAM,WAAW;AACjD;AAGO,SAAS,iBAA4D;AAC1E,QAAM,UAAU,kBAAkB;AAClC,SAAO,SAAS,MAAM,QAAQ,MAAM,aAAa,WAAW;AAC9D;AAOO,SAAS,wBAE2B;AACzC,QAAM,UAAU,kBAAkB;AAClC,SAAO,OAAO,SAAS;AACrB,UAAM,EAAE,KAAK,YAAY,IAAI,QAAQ;AACrC,UAAM,YAAY,aAAa;AAC/B,QAAI,aAAa,CAAC,UAAU,SAAS,IAAI,EAAG,QAAO,aAAa;AAChE,UAAM,SAAS,MAAM,IAAI,mBAAmB,EAAE,KAAK,CAAC;AACpD,WAAO,OAAO;AAAA,EAChB;AACF;","names":[]}
1
+ {"version":3,"sources":["../../src/vue/index.ts"],"sourcesContent":["/**\n * Vue entry point: turn a Vue component into an MCP App View.\n *\n * ```ts\n * import { createMcpView } from \"mountly-mcp/vue\";\n * import Dashboard from \"./Dashboard.vue\";\n *\n * createMcpView(Dashboard);\n * ```\n *\n * The component receives the bridge's props (`mcp`, `toolInput`, `toolResult`,\n * `hostContext`) and can also reach them from any depth with the composables\n * below, which mirror `mountly-mcp/react`'s hooks.\n */\nimport {\n computed,\n defineComponent,\n h,\n inject,\n provide,\n type Component,\n type ComputedRef,\n type InjectionKey,\n} from \"vue\";\nimport { createWidget } from \"mountly-vue\";\nimport type { AdapterOptions } from \"mountly/adapter\";\nimport type { App, McpUiDisplayMode, McpUiHostContext } from \"@modelcontextprotocol/ext-apps\";\nimport type { McpView, McpViewProps } from \"../types.js\";\nimport { publishMcpView } from \"../publish.js\";\nimport { downloadFile, type DownloadableFile } from \"../download.js\";\n\n/** What {@link createMcpView} provides to descendants. */\nexport interface McpVueContext {\n app: App;\n toolInput?: McpViewProps[\"toolInput\"];\n toolInputPartial?: McpViewProps[\"toolInputPartial\"];\n toolResult?: McpViewProps[\"toolResult\"];\n hostContext?: McpUiHostContext;\n}\n\nexport const McpInjectionKey: InjectionKey<ComputedRef<McpVueContext>> = Symbol(\"mountly-mcp\");\n\n/** Wraps a Vue component as an MCP Apps View and publishes it for the bridge. */\nexport function createMcpView<P extends object>(\n Component: Component<P>,\n options?: AdapterOptions,\n): McpView {\n const Wrapped = defineComponent({\n name: \"MountlyMcpProvider\",\n inheritAttrs: false,\n props: {\n mcp: { type: Object, required: true },\n toolInput: { type: Object, default: undefined },\n toolInputPartial: { type: Object, default: undefined },\n toolResult: { type: Object, default: undefined },\n hostContext: { type: Object, default: undefined },\n },\n setup(props, { attrs }) {\n const context = computed<McpVueContext>(() => ({\n app: props.mcp as App,\n toolInput: props.toolInput as McpVueContext[\"toolInput\"],\n toolInputPartial: props.toolInputPartial as McpVueContext[\"toolInputPartial\"],\n toolResult: props.toolResult as McpVueContext[\"toolResult\"],\n hostContext: props.hostContext as McpUiHostContext | undefined,\n }));\n provide(McpInjectionKey, context);\n return () => h(Component as Component, attrs);\n },\n });\n return publishMcpView(createWidget(Wrapped, options));\n}\n\nfunction useEnsuredContext(): ComputedRef<McpVueContext> {\n const context = inject(McpInjectionKey, null);\n if (!context) {\n throw new Error(\n \"mountly-mcp/vue: useMcpApp/useToolResult/etc must be used inside a component wrapped with createMcpView().\",\n );\n }\n return context;\n}\n\n/** The view-side ext-apps App: call server tools, send messages, open links. */\nexport function useMcpApp(): App {\n return useEnsuredContext().value.app;\n}\n\n/**\n * Return a function that hands the user a file: through the host's\n * `ui/download-file` where it has one, else an ordinary `<a download>`.\n */\nexport function useDownload(): (file: DownloadableFile) => Promise<boolean> {\n const context = useEnsuredContext();\n return (file) => downloadFile(context.value.app, file);\n}\n\n/** Complete notification params; read the tool arguments from `.arguments`. */\nexport function useToolInput<T = unknown>(): ComputedRef<T | undefined> {\n const context = useEnsuredContext();\n return computed(() => context.value.toolInput as T | undefined);\n}\n\n/**\n * Streaming tool arguments (`ui/notifications/tool-input-partial`), present only\n * when the bridge runs with `streamToolInput`. Best-effort recovered JSON —\n * render loading states from it, never critical operations.\n */\nexport function useToolInputPartial<T = unknown>(): ComputedRef<Partial<T> | undefined> {\n const context = useEnsuredContext();\n return computed(() => context.value.toolInputPartial as Partial<T> | undefined);\n}\n\n/** Complete MCP tool result; structured data is available at `.structuredContent`. */\nexport function useToolResult<T = unknown>(): ComputedRef<T | undefined> {\n const context = useEnsuredContext();\n return computed(() => context.value.toolResult as T | undefined);\n}\n\n/** Latest host context (theme, styles, displayMode, container dimensions). */\nexport function useHostContext(): ComputedRef<McpUiHostContext | undefined> {\n const context = useEnsuredContext();\n return computed(() => context.value.hostContext);\n}\n\n/** Current display mode reported by the host. */\nexport function useDisplayMode(): ComputedRef<McpUiDisplayMode | undefined> {\n const context = useEnsuredContext();\n return computed(() => context.value.hostContext?.displayMode);\n}\n\n/**\n * Request a display mode change, resolving to the mode the host actually\n * applied. Modes the host didn't advertise are never requested — the spec\n * requires views to check `availableDisplayModes` first.\n */\nexport function useRequestDisplayMode(): (\n mode: McpUiDisplayMode,\n) => Promise<McpUiDisplayMode | undefined> {\n const context = useEnsuredContext();\n return async (mode) => {\n const { app, hostContext } = context.value;\n const available = hostContext?.availableDisplayModes;\n if (available && !available.includes(mode)) return hostContext?.displayMode;\n const result = await app.requestDisplayMode({ mode });\n return result.mode;\n };\n}\n"],"mappings":";;;;;;;;AAcA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AACP,SAAS,oBAAoB;AAgBtB,IAAM,kBAA4D,uBAAO,aAAa;AAGtF,SAAS,cACd,WACA,SACS;AACT,QAAM,UAAU,gBAAgB;AAAA,IAC9B,MAAM;AAAA,IACN,cAAc;AAAA,IACd,OAAO;AAAA,MACL,KAAK,EAAE,MAAM,QAAQ,UAAU,KAAK;AAAA,MACpC,WAAW,EAAE,MAAM,QAAQ,SAAS,OAAU;AAAA,MAC9C,kBAAkB,EAAE,MAAM,QAAQ,SAAS,OAAU;AAAA,MACrD,YAAY,EAAE,MAAM,QAAQ,SAAS,OAAU;AAAA,MAC/C,aAAa,EAAE,MAAM,QAAQ,SAAS,OAAU;AAAA,IAClD;AAAA,IACA,MAAM,OAAO,EAAE,MAAM,GAAG;AACtB,YAAM,UAAU,SAAwB,OAAO;AAAA,QAC7C,KAAK,MAAM;AAAA,QACX,WAAW,MAAM;AAAA,QACjB,kBAAkB,MAAM;AAAA,QACxB,YAAY,MAAM;AAAA,QAClB,aAAa,MAAM;AAAA,MACrB,EAAE;AACF,cAAQ,iBAAiB,OAAO;AAChC,aAAO,MAAM,EAAE,WAAwB,KAAK;AAAA,IAC9C;AAAA,EACF,CAAC;AACD,SAAO,eAAe,aAAa,SAAS,OAAO,CAAC;AACtD;AAEA,SAAS,oBAAgD;AACvD,QAAM,UAAU,OAAO,iBAAiB,IAAI;AAC5C,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAGO,SAAS,YAAiB;AAC/B,SAAO,kBAAkB,EAAE,MAAM;AACnC;AAMO,SAAS,cAA4D;AAC1E,QAAM,UAAU,kBAAkB;AAClC,SAAO,CAAC,SAAS,aAAa,QAAQ,MAAM,KAAK,IAAI;AACvD;AAGO,SAAS,eAAwD;AACtE,QAAM,UAAU,kBAAkB;AAClC,SAAO,SAAS,MAAM,QAAQ,MAAM,SAA0B;AAChE;AAOO,SAAS,sBAAwE;AACtF,QAAM,UAAU,kBAAkB;AAClC,SAAO,SAAS,MAAM,QAAQ,MAAM,gBAA0C;AAChF;AAGO,SAAS,gBAAyD;AACvE,QAAM,UAAU,kBAAkB;AAClC,SAAO,SAAS,MAAM,QAAQ,MAAM,UAA2B;AACjE;AAGO,SAAS,iBAA4D;AAC1E,QAAM,UAAU,kBAAkB;AAClC,SAAO,SAAS,MAAM,QAAQ,MAAM,WAAW;AACjD;AAGO,SAAS,iBAA4D;AAC1E,QAAM,UAAU,kBAAkB;AAClC,SAAO,SAAS,MAAM,QAAQ,MAAM,aAAa,WAAW;AAC9D;AAOO,SAAS,wBAE2B;AACzC,QAAM,UAAU,kBAAkB;AAClC,SAAO,OAAO,SAAS;AACrB,UAAM,EAAE,KAAK,YAAY,IAAI,QAAQ;AACrC,UAAM,YAAY,aAAa;AAC/B,QAAI,aAAa,CAAC,UAAU,SAAS,IAAI,EAAG,QAAO,aAAa;AAChE,UAAM,SAAS,MAAM,IAAI,mBAAmB,EAAE,KAAK,CAAC;AACpD,WAAO,OAAO;AAAA,EAChB;AACF;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mountly-mcp",
3
- "version": "4.4.0",
3
+ "version": "4.5.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",
@@ -62,6 +62,14 @@
62
62
  "import": "./dist/react/openai-extensions.js"
63
63
  },
64
64
  "./server": {
65
+ "workerd": {
66
+ "types": "./dist/server/workers.d.ts",
67
+ "import": "./dist/server/workers.js"
68
+ },
69
+ "worker": {
70
+ "types": "./dist/server/workers.d.ts",
71
+ "import": "./dist/server/workers.js"
72
+ },
65
73
  "types": "./dist/server/index.d.ts",
66
74
  "import": "./dist/server/index.js"
67
75
  },
@@ -127,7 +135,7 @@
127
135
  "@modelcontextprotocol/ext-apps": "^1.7.5",
128
136
  "@modelcontextprotocol/sdk": "^1.30.0",
129
137
  "mountly": "^1.3.0",
130
- "mountly-react": "^2.1.0",
138
+ "mountly-react": "^2.3.0",
131
139
  "zod": "^4.4.3"
132
140
  },
133
141
  "devDependencies": {
@@ -145,9 +153,9 @@
145
153
  "tsup": "^8.5.1",
146
154
  "typescript": "^7.0.2",
147
155
  "vite": "^8.1.5",
148
- "mountly-svelte": "^2.1.1",
156
+ "mountly-compose": "0.1.0",
149
157
  "mountly-vue": "^2.1.0",
150
- "mountly-compose": "0.1.0"
158
+ "mountly-svelte": "^2.1.1"
151
159
  },
152
160
  "peerDependencies": {
153
161
  "@json-render/core": "^0.20.0",
@@ -159,8 +167,8 @@
159
167
  "mountly-svelte": "^2.1.0",
160
168
  "mountly-vue": "^2.1.0",
161
169
  "playwright": ">=1.62.1",
162
- "react": "^19",
163
- "react-dom": "^19",
170
+ "react": "^18 || ^19",
171
+ "react-dom": "^18 || ^19",
164
172
  "vite": "^8"
165
173
  },
166
174
  "peerDependenciesMeta": {
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/artifact/index.ts"],"sourcesContent":["import { readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, isAbsolute, relative, resolve } from \"node:path\";\nimport {\n McpUiDisplayModeSchema,\n McpUiResourceMetaSchema,\n RESOURCE_MIME_TYPE,\n LATEST_PROTOCOL_VERSION,\n} from \"@modelcontextprotocol/ext-apps\";\nimport type { McpResourceDeclaration } from \"../types.js\";\n\nexport const MCP_APP_MANIFEST_VERSION = 1 as const;\nexport const MCP_APP_MANIFEST_FILE = \"mountly-mcp.manifest.json\";\n\nexport interface McpAppArtifact {\n /** Developer-facing unique key. */\n name: string;\n /** Protocol identity. */\n uri: string;\n /** Absolute path to the self-contained View HTML. */\n htmlPath: string;\n /** Absolute path to the transitional per-View sidecar. */\n metaPath: string;\n declaration: McpResourceDeclaration;\n}\n\nexport interface McpAppManifestEntry {\n name: string;\n uri: string;\n /** Path relative to the manifest, unless explicitly absolute. */\n html: string;\n /** Path relative to the manifest, unless explicitly absolute. */\n meta: string;\n declaration: McpResourceDeclaration;\n}\n\nexport interface McpAppManifest {\n formatVersion: typeof MCP_APP_MANIFEST_VERSION;\n protocolVersion: typeof LATEST_PROTOCOL_VERSION;\n apps: ReadonlyArray<McpAppManifestEntry>;\n}\n\nexport type ConformanceSeverity = \"error\" | \"warning\";\n\nexport interface ConformanceDiagnostic {\n severity: ConformanceSeverity;\n code: string;\n message: string;\n source?: string;\n}\n\nexport class McpAppConformanceError extends Error {\n readonly diagnostics: ReadonlyArray<ConformanceDiagnostic>;\n\n constructor(diagnostics: ReadonlyArray<ConformanceDiagnostic>) {\n super(diagnostics.map((d) => `${d.code}: ${d.message}`).join(\"\\n\"));\n this.name = \"McpAppConformanceError\";\n this.diagnostics = diagnostics;\n }\n}\n\nfunction object(value: unknown): Record<string, unknown> | undefined {\n return typeof value === \"object\" && value !== null && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : undefined;\n}\n\nexport function inspectMcpResourceDeclaration(\n value: unknown,\n source?: string,\n): ReadonlyArray<ConformanceDiagnostic> {\n const diagnostics: ConformanceDiagnostic[] = [];\n const add = (code: string, message: string): void => {\n diagnostics.push({ severity: \"error\", code, message, source });\n };\n const candidate = object(value);\n if (!candidate) {\n add(\"artifact/not-object\", \"resource declaration must be an object\");\n return diagnostics;\n }\n if (candidate.protocolVersion !== LATEST_PROTOCOL_VERSION) {\n add(\n \"artifact/protocol-version\",\n `sidecar protocolVersion must be '${LATEST_PROTOCOL_VERSION}' (received '${String(candidate.protocolVersion)}')`,\n );\n }\n if (typeof candidate.uri !== \"string\" || !candidate.uri.startsWith(\"ui://\")) {\n add(\"artifact/uri\", `uri must use the 'ui://' scheme (received '${String(candidate.uri)}')`);\n }\n if (typeof candidate.name !== \"string\" || candidate.name.trim() === \"\") {\n add(\"artifact/name\", \"name must be a non-empty string\");\n }\n if (candidate.mimeType !== RESOURCE_MIME_TYPE) {\n add(\n \"artifact/mime-type\",\n `sidecar mimeType must be '${RESOURCE_MIME_TYPE}' (received '${String(candidate.mimeType)}')`,\n );\n }\n if (typeof candidate.awaitToolResult !== \"boolean\") {\n add(\"artifact/await-tool-result\", \"awaitToolResult must be a boolean\");\n }\n if (\n !Array.isArray(candidate.displayModes) ||\n candidate.displayModes.length === 0 ||\n candidate.displayModes.some((mode) => !McpUiDisplayModeSchema.safeParse(mode).success)\n ) {\n add(\"artifact/display-modes\", \"displayModes must contain supported MCP Apps display modes\");\n }\n const meta = object(candidate._meta);\n const ui = meta && object(meta.ui);\n if (!ui || !McpUiResourceMetaSchema.safeParse(ui).success) {\n add(\"artifact/resource-meta\", \"_meta.ui must be valid MCP Apps resource metadata\");\n }\n return diagnostics;\n}\n\nexport function parseMcpResourceDeclaration(\n value: unknown,\n source?: string,\n): McpResourceDeclaration {\n const diagnostics = inspectMcpResourceDeclaration(value, source);\n if (diagnostics.length > 0) throw new McpAppConformanceError(diagnostics);\n return value as McpResourceDeclaration;\n}\n\nexport async function readMcpAppArtifact(\n htmlPath: string,\n expectedUri?: string,\n): Promise<McpAppArtifact> {\n const absoluteHtmlPath = resolve(htmlPath);\n const metaPath = `${absoluteHtmlPath}.meta.json`;\n const [html, raw] = await Promise.all([\n readFile(absoluteHtmlPath, \"utf8\"),\n readFile(metaPath, \"utf8\"),\n ]);\n if (!/^\\s*<!doctype html>/i.test(html)) {\n throw new McpAppConformanceError([\n {\n severity: \"error\",\n code: \"artifact/html5\",\n message: \"View HTML must start with an HTML5 doctype\",\n source: absoluteHtmlPath,\n },\n ]);\n }\n let decoded: unknown;\n try {\n decoded = JSON.parse(raw);\n } catch (error) {\n throw new McpAppConformanceError([\n {\n severity: \"error\",\n code: \"artifact/meta-json\",\n message: `metadata is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,\n source: metaPath,\n },\n ]);\n }\n const declaration = parseMcpResourceDeclaration(decoded, metaPath);\n if (expectedUri !== undefined && declaration.uri !== expectedUri) {\n throw new McpAppConformanceError([\n {\n severity: \"error\",\n code: \"artifact/uri-mismatch\",\n message: `registration uri '${expectedUri}' does not match sidecar uri '${declaration.uri}'`,\n source: metaPath,\n },\n ]);\n }\n return {\n name: declaration.name,\n uri: declaration.uri,\n htmlPath: absoluteHtmlPath,\n metaPath,\n declaration,\n };\n}\n\nfunction manifestPath(path: string, base: string): string {\n return isAbsolute(path) ? path : resolve(base, path);\n}\n\nexport async function writeMcpAppManifest(\n path: string,\n artifacts: ReadonlyArray<McpAppArtifact>,\n): Promise<McpAppManifest> {\n const absolutePath = resolve(path);\n const base = dirname(absolutePath);\n const names = new Set<string>();\n const uris = new Set<string>();\n for (const artifact of artifacts) {\n if (names.has(artifact.name))\n throw new Error(`mountly-mcp: duplicate app name '${artifact.name}'`);\n if (uris.has(artifact.uri)) throw new Error(`mountly-mcp: duplicate app uri '${artifact.uri}'`);\n names.add(artifact.name);\n uris.add(artifact.uri);\n }\n const manifest: McpAppManifest = {\n formatVersion: MCP_APP_MANIFEST_VERSION,\n protocolVersion: LATEST_PROTOCOL_VERSION,\n apps: artifacts.map((artifact) => ({\n name: artifact.name,\n uri: artifact.uri,\n html: relative(base, artifact.htmlPath),\n meta: relative(base, artifact.metaPath),\n declaration: artifact.declaration,\n })),\n };\n await writeFile(absolutePath, JSON.stringify(manifest, null, 2), \"utf8\");\n return manifest;\n}\n\nexport async function readMcpAppManifest(path: string): Promise<{\n path: string;\n manifest: McpAppManifest;\n artifacts: ReadonlyArray<McpAppArtifact>;\n}> {\n const absolutePath = resolve(path);\n const base = dirname(absolutePath);\n let decoded: unknown;\n try {\n decoded = JSON.parse(await readFile(absolutePath, \"utf8\"));\n } catch (error) {\n throw new Error(\n `mountly-mcp: could not read manifest '${absolutePath}': ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n const candidate = object(decoded);\n if (\n candidate?.formatVersion !== MCP_APP_MANIFEST_VERSION ||\n candidate.protocolVersion !== LATEST_PROTOCOL_VERSION ||\n !Array.isArray(candidate.apps)\n ) {\n throw new Error(\n `mountly-mcp: '${absolutePath}' is not a supported mountly-mcp manifest (expected formatVersion ${MCP_APP_MANIFEST_VERSION})`,\n );\n }\n const manifest = decoded as McpAppManifest;\n const names = new Set<string>();\n const uris = new Set<string>();\n const artifacts: McpAppArtifact[] = [];\n for (const entry of manifest.apps) {\n if (names.has(entry.name)) throw new Error(`mountly-mcp: duplicate app name '${entry.name}'`);\n if (uris.has(entry.uri)) throw new Error(`mountly-mcp: duplicate app uri '${entry.uri}'`);\n names.add(entry.name);\n uris.add(entry.uri);\n const declaration = parseMcpResourceDeclaration(entry.declaration, absolutePath);\n if (declaration.name !== entry.name || declaration.uri !== entry.uri) {\n throw new Error(\n `mountly-mcp: manifest identity does not match declaration for '${entry.name}'`,\n );\n }\n artifacts.push({\n name: entry.name,\n uri: entry.uri,\n htmlPath: manifestPath(entry.html, base),\n metaPath: manifestPath(entry.meta, base),\n declaration,\n });\n }\n return { path: absolutePath, manifest, artifacts };\n}\n"],"mappings":"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","names":[]}