mountly-mcp 0.0.0 → 4.0.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 (132) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +283 -20
  3. package/dist/artifact/index.d.ts +49 -0
  4. package/dist/artifact/index.js +21 -0
  5. package/dist/artifact/index.js.map +1 -0
  6. package/dist/bridge/iframe-entry.d.ts +1 -0
  7. package/dist/bridge/iframe-entry.js +270 -220
  8. package/dist/bridge/index.d.ts +20 -15
  9. package/dist/bridge/index.js +2 -2
  10. package/dist/build/emit-html.d.ts +30 -0
  11. package/dist/build/emit-meta.d.ts +17 -0
  12. package/dist/build/index.d.ts +33 -48
  13. package/dist/build/index.js +9 -135
  14. package/dist/build/index.js.map +1 -1
  15. package/dist/chunk-33DMFKKD.js +111 -0
  16. package/dist/chunk-33DMFKKD.js.map +1 -0
  17. package/dist/chunk-4HC3OWAQ.js +185 -0
  18. package/dist/chunk-4HC3OWAQ.js.map +1 -0
  19. package/dist/chunk-5EHNJCMF.js +58 -0
  20. package/dist/chunk-5EHNJCMF.js.map +1 -0
  21. package/dist/chunk-6QHKKFO3.js +30 -0
  22. package/dist/chunk-6QHKKFO3.js.map +1 -0
  23. package/dist/chunk-AEVQKFXX.js +119 -0
  24. package/dist/chunk-AEVQKFXX.js.map +1 -0
  25. package/dist/chunk-DKK26TRS.js +199 -0
  26. package/dist/chunk-DKK26TRS.js.map +1 -0
  27. package/dist/chunk-JVQZGKBL.js +171 -0
  28. package/dist/chunk-JVQZGKBL.js.map +1 -0
  29. package/dist/chunk-KWVGW5Q7.js +582 -0
  30. package/dist/chunk-KWVGW5Q7.js.map +1 -0
  31. package/dist/{chunk-A64ZILAS.js → chunk-M4WFJR3C.js} +3 -2
  32. package/dist/{chunk-A64ZILAS.js.map → chunk-M4WFJR3C.js.map} +1 -1
  33. package/dist/chunk-MTJDRWGD.js +196 -0
  34. package/dist/chunk-MTJDRWGD.js.map +1 -0
  35. package/dist/chunk-SADNMGP6.js +16 -0
  36. package/dist/chunk-SADNMGP6.js.map +1 -0
  37. package/dist/chunk-UECZA34Q.js +52 -0
  38. package/dist/chunk-UECZA34Q.js.map +1 -0
  39. package/dist/chunk-YJA4H6IB.js +13 -0
  40. package/dist/chunk-YJA4H6IB.js.map +1 -0
  41. package/dist/cli.d.ts +2 -0
  42. package/dist/cli.js +297 -0
  43. package/dist/cli.js.map +1 -0
  44. package/dist/connect-server-7L4HGKKL.js +7 -0
  45. package/dist/connect-server-7L4HGKKL.js.map +1 -0
  46. package/dist/create.d.ts +10 -0
  47. package/dist/create.js +107 -0
  48. package/dist/create.js.map +1 -0
  49. package/dist/dev/connect-server.d.ts +13 -0
  50. package/dist/dev/host-entry.d.ts +13 -0
  51. package/dist/dev/host-entry.js +18330 -0
  52. package/dist/dev/index.d.ts +45 -0
  53. package/dist/dev/index.js +14 -0
  54. package/dist/dev/index.js.map +1 -0
  55. package/dist/dev/sandbox-entry.d.ts +7 -0
  56. package/dist/dev/sandbox-entry.js +80 -0
  57. package/dist/html.d.ts +4 -0
  58. package/dist/index.d.ts +7 -26
  59. package/dist/index.js +16 -3
  60. package/dist/json-render/app.d.ts +22 -0
  61. package/dist/json-render/app.js +10 -0
  62. package/dist/json-render/app.js.map +1 -0
  63. package/dist/json-render/generative-view.d.ts +57 -0
  64. package/dist/json-render/index.d.ts +10 -0
  65. package/dist/json-render/index.js +120 -0
  66. package/dist/json-render/index.js.map +1 -0
  67. package/dist/json-render/mcp.d.ts +49 -0
  68. package/dist/json-render/mcp.js +11 -0
  69. package/dist/json-render/mcp.js.map +1 -0
  70. package/dist/json-render/server.d.ts +72 -0
  71. package/dist/json-render/server.js +98 -0
  72. package/dist/json-render/server.js.map +1 -0
  73. package/dist/json-render/spec-stream.d.ts +26 -0
  74. package/dist/json-render/use-spec-stream.d.ts +26 -0
  75. package/dist/publish.d.ts +10 -0
  76. package/dist/react/context.d.ts +14 -0
  77. package/dist/react/create-view.d.ts +8 -0
  78. package/dist/react/hooks.d.ts +46 -0
  79. package/dist/react/index.d.ts +3 -0
  80. package/dist/react/index.js +36 -0
  81. package/dist/react/index.js.map +1 -0
  82. package/dist/sandbox/index.d.ts +16 -0
  83. package/dist/sandbox/index.js +8 -0
  84. package/dist/sandbox/index.js.map +1 -0
  85. package/dist/schema.d.ts +20 -0
  86. package/dist/server/index.d.ts +73 -0
  87. package/dist/server/index.js +235 -0
  88. package/dist/server/index.js.map +1 -0
  89. package/dist/svelte/index.d.ts +29 -0
  90. package/dist/svelte/index.js +13 -0
  91. package/dist/svelte/index.js.map +1 -0
  92. package/dist/testing/index.d.ts +27 -0
  93. package/dist/testing/index.js +312 -0
  94. package/dist/testing/index.js.map +1 -0
  95. package/dist/testing/render-artifact.d.ts +35 -0
  96. package/dist/types.d.ts +65 -0
  97. package/dist/vite/index.d.ts +23 -0
  98. package/dist/vite/index.js +211 -0
  99. package/dist/vite/index.js.map +1 -0
  100. package/dist/vue/index.d.ts +51 -0
  101. package/dist/vue/index.js +93 -0
  102. package/dist/vue/index.js.map +1 -0
  103. package/package.json +162 -20
  104. package/templates/react/.gitignore.tmpl +4 -0
  105. package/templates/react/README.md.tmpl +15 -0
  106. package/templates/react/mcp.fixtures.json.tmpl +4 -0
  107. package/templates/react/package.json.tmpl +27 -0
  108. package/templates/react/server.mjs.tmpl +70 -0
  109. package/templates/react/src/view.tsx.tmpl +160 -0
  110. package/templates/react/tsconfig.json.tmpl +15 -0
  111. package/templates/react/vite.config.ts.tmpl +22 -0
  112. package/templates/svelte/.gitignore.tmpl +4 -0
  113. package/templates/svelte/README.md.tmpl +14 -0
  114. package/templates/svelte/mcp.fixtures.json.tmpl +4 -0
  115. package/templates/svelte/package.json.tmpl +26 -0
  116. package/templates/svelte/server.mjs.tmpl +70 -0
  117. package/templates/svelte/src/Dashboard.svelte.tmpl +158 -0
  118. package/templates/svelte/src/view.ts.tmpl +14 -0
  119. package/templates/svelte/tsconfig.json.tmpl +14 -0
  120. package/templates/svelte/vite.config.ts.tmpl +22 -0
  121. package/templates/vue/.gitignore.tmpl +4 -0
  122. package/templates/vue/README.md.tmpl +14 -0
  123. package/templates/vue/mcp.fixtures.json.tmpl +4 -0
  124. package/templates/vue/package.json.tmpl +26 -0
  125. package/templates/vue/server.mjs.tmpl +70 -0
  126. package/templates/vue/src/Dashboard.vue.tmpl +159 -0
  127. package/templates/vue/src/view.ts.tmpl +14 -0
  128. package/templates/vue/tsconfig.json.tmpl +15 -0
  129. package/templates/vue/vite.config.ts.tmpl +22 -0
  130. package/dist/chunk-BUGGLMNA.js +0 -132
  131. package/dist/chunk-BUGGLMNA.js.map +0 -1
  132. package/dist/types-B9JAWm4W.d.ts +0 -62
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/server/index.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { InMemoryTransport } from \"@modelcontextprotocol/sdk/inMemory.js\";\nimport type { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\nimport {\n EXTENSION_ID,\n RESOURCE_MIME_TYPE,\n getUiCapability,\n registerAppResource,\n registerAppTool,\n type McpUiAppToolConfig,\n} from \"@modelcontextprotocol/ext-apps/server\";\nimport type { McpUiToolMeta, McpUiToolVisibility } from \"@modelcontextprotocol/ext-apps\";\nimport { z } from \"zod\";\nimport { readMcpAppArtifact, type McpAppArtifact } from \"../artifact/index.js\";\n\nexport interface McpAppToolResult {\n structuredContent?: unknown;\n content?: ReadonlyArray<{ type: string; [key: string]: unknown }>;\n isError?: boolean;\n}\n\n/** Complete official tool configuration, plus JSON Schema support at Mountly's seam. */\nexport interface McpAppToolConfig extends Omit<\n McpUiAppToolConfig,\n \"_meta\" | \"inputSchema\" | \"outputSchema\"\n> {\n inputSchema?: object;\n outputSchema?: object;\n /** Additional metadata is preserved; Mountly only merges `_meta.ui`. */\n _meta?: Record<string, unknown>;\n}\n\n/** A View is independent of the tools that reference it. */\nexport interface McpAppViewRegistration {\n /** Built artifact or the path to its self-contained HTML. */\n artifact: McpAppArtifact | string;\n /** Optional assertion used by migration adapters; artifacts already own identity. */\n uri?: string;\n}\n\n/** A tool links to a separately declared View through the protocol resource URI. */\nexport interface McpAppToolRegistration<Args = unknown> {\n name: string;\n resourceUri: string;\n config?: McpAppToolConfig;\n /** Convenience override for `_meta.ui.visibility`. */\n visibility?: McpUiToolVisibility | ReadonlyArray<McpUiToolVisibility>;\n handler: (args: Args, extra?: unknown) => Promise<McpAppToolResult>;\n}\n\nexport interface RegisterMcpAppsOptions {\n views: ReadonlyArray<McpAppViewRegistration>;\n tools?: ReadonlyArray<McpAppToolRegistration>;\n}\n\n/**\n * `createMcpAppServer` declares Views and tools exactly as `registerMcpApps`\n * does; it only owns the `McpServer` for you. One vocabulary, two entry points.\n */\nexport interface CreateMcpAppServerOptions extends RegisterMcpAppsOptions {\n name: string;\n version: string;\n}\n\ninterface LoadedView {\n artifact: McpAppArtifact;\n html: string;\n}\n\ninterface InstalledTool {\n registration: McpAppToolRegistration;\n registered: { remove(): void; update(u: { _meta?: object }): void };\n metadataWithoutUi: Record<string, unknown>;\n visibility: ReadonlyArray<McpUiToolVisibility>;\n}\n\nexport interface RegisteredMcpApps {\n artifacts: ReadonlyArray<McpAppArtifact>;\n /** Remove every resource and tool installed by this registration. */\n remove(): void;\n}\n\nfunction normalizeVisibility(\n visibility: McpAppToolRegistration[\"visibility\"],\n): McpUiToolVisibility[] | undefined {\n if (visibility === undefined) return undefined;\n return typeof visibility === \"string\" ? [visibility] : [...visibility];\n}\n\nfunction isSchemaLibraryValue(value: unknown): boolean {\n return typeof value === \"object\" && value !== null && (\"~standard\" in value || \"_def\" in value);\n}\n\n/** Convert documented JSON Schema while preserving Zod and Standard Schema values. */\nfunction normalizeToolSchema(schema: object | undefined): object | undefined {\n if (!schema || Object.keys(schema).length === 0) return schema;\n const candidate = schema as Record<string, unknown>;\n if (isSchemaLibraryValue(candidate) || Object.values(candidate).some(isSchemaLibraryValue)) {\n return schema;\n }\n const jsonSchemaKeys = [\n \"$schema\",\n \"$ref\",\n \"$defs\",\n \"type\",\n \"properties\",\n \"required\",\n \"additionalProperties\",\n \"allOf\",\n \"anyOf\",\n \"oneOf\",\n ];\n return jsonSchemaKeys.some((key) => key in candidate)\n ? z.fromJSONSchema(schema as never)\n : schema;\n}\n\nfunction withTextFallback(result: McpAppToolResult): McpAppToolResult {\n if (result.content && result.content.length > 0) return result;\n if (result.structuredContent === undefined) return result;\n return {\n ...result,\n content: [{ type: \"text\", text: JSON.stringify(result.structuredContent) }],\n };\n}\n\nfunction withoutUiMetadata(meta: Record<string, unknown>): Record<string, unknown> {\n const copy = { ...meta };\n delete copy.ui;\n delete copy[\"ui/resourceUri\"];\n return copy;\n}\n\nfunction mergedToolMetadata(tool: McpAppToolRegistration): {\n meta: Record<string, unknown> & { ui: McpUiToolMeta };\n withoutUi: Record<string, unknown>;\n visibility: McpUiToolVisibility[];\n} {\n const base = { ...(tool.config?._meta ?? {}) };\n const existingUi =\n typeof base.ui === \"object\" && base.ui !== null ? (base.ui as Partial<McpUiToolMeta>) : {};\n const visibility = normalizeVisibility(tool.visibility) ?? [\n ...(existingUi.visibility ?? [\"model\", \"app\"]),\n ];\n const meta = {\n ...base,\n ui: { ...existingUi, resourceUri: tool.resourceUri, visibility },\n };\n return { meta, withoutUi: withoutUiMetadata(base), visibility };\n}\n\nasync function loadViews(\n registrations: ReadonlyArray<McpAppViewRegistration>,\n): Promise<ReadonlyArray<LoadedView>> {\n const loaded: LoadedView[] = [];\n const names = new Set<string>();\n const uris = new Set<string>();\n for (const registration of registrations) {\n if (registration.uri !== undefined && !registration.uri.startsWith(\"ui://\")) {\n throw new Error(\n `mountly-mcp/server: registration uri must use the 'ui://' scheme (received '${registration.uri}')`,\n );\n }\n const supplied = typeof registration.artifact === \"string\" ? undefined : registration.artifact;\n const artifact = await readMcpAppArtifact(\n typeof registration.artifact === \"string\"\n ? registration.artifact\n : registration.artifact.htmlPath,\n registration.uri ?? supplied?.uri,\n );\n if (supplied && supplied.name !== artifact.name) {\n throw new Error(\n `mountly-mcp: supplied artifact name '${supplied.name}' does not match file artifact name '${artifact.name}'`,\n );\n }\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 loaded.push({ artifact, html: await readFile(artifact.htmlPath, \"utf8\") });\n }\n return loaded;\n}\n\n/**\n * Install Mountly Views and their independently declared tools into an existing,\n * unconnected MCP server. The caller retains ownership of identity, auth,\n * transports, prompts, ordinary tools, and deployment.\n */\nexport async function registerMcpApps(\n server: McpServer,\n options: RegisterMcpAppsOptions,\n): Promise<RegisteredMcpApps> {\n if (server.isConnected()) {\n throw new Error(\"mountly-mcp: registerMcpApps() must run before the server connects\");\n }\n if (options.views.length === 0) {\n throw new Error(\"mountly-mcp: registerMcpApps() requires at least one View\");\n }\n const views = await loadViews(options.views);\n const byUri = new Map(views.map((view) => [view.artifact.uri, view]));\n for (const tool of options.tools ?? []) {\n if (!byUri.has(tool.resourceUri)) {\n throw new Error(\n `mountly-mcp: tool '${tool.name}' references undeclared View '${tool.resourceUri}'`,\n );\n }\n }\n\n server.server.registerCapabilities({\n extensions: { [EXTENSION_ID]: { mimeTypes: [RESOURCE_MIME_TYPE] } },\n } as never);\n\n const resources = views.map(({ artifact, html }) =>\n registerAppResource(\n server,\n artifact.name,\n artifact.uri,\n {\n mimeType: artifact.declaration.mimeType,\n description: artifact.declaration.description,\n _meta: { ui: artifact.declaration._meta.ui },\n },\n async () => ({\n contents: [\n {\n uri: artifact.uri,\n mimeType: artifact.declaration.mimeType,\n text: html,\n _meta: { ui: artifact.declaration._meta.ui },\n },\n ],\n }),\n ),\n );\n\n const tools: InstalledTool[] = (options.tools ?? []).map((registration) => {\n const { meta, withoutUi, visibility } = mergedToolMetadata(registration);\n const config = registration.config ?? {};\n const registered = registerAppTool(\n server,\n registration.name,\n {\n ...config,\n inputSchema: normalizeToolSchema(config.inputSchema) as never,\n outputSchema: normalizeToolSchema(config.outputSchema) as never,\n _meta: meta,\n },\n (async (args: unknown, extra: unknown) =>\n withTextFallback(await registration.handler(args, extra))) as never,\n );\n return { registration, registered, metadataWithoutUi: withoutUi, visibility };\n });\n\n const previousInitialized = server.server.oninitialized;\n const handleInitialized = () => {\n previousInitialized?.();\n const ui = getUiCapability(server.server.getClientCapabilities());\n if (ui?.mimeTypes?.includes(RESOURCE_MIME_TYPE)) return;\n for (const resource of resources) resource.remove();\n for (const tool of tools) {\n if (tool.visibility.includes(\"model\")) {\n tool.registered.update({ _meta: tool.metadataWithoutUi });\n } else {\n tool.registered.remove();\n }\n }\n };\n server.server.oninitialized = handleInitialized;\n\n return {\n artifacts: views.map((view) => view.artifact),\n remove() {\n for (const resource of resources) resource.remove();\n for (const tool of tools) tool.registered.remove();\n if (server.server.oninitialized === handleInitialized) {\n server.server.oninitialized = previousInitialized;\n }\n },\n };\n}\n\nexport interface RunningMcpAppServer {\n connectInProcess(options?: { ui?: boolean }): Promise<{ client: Client }>;\n listen(transport: Transport): Promise<void>;\n close(): Promise<void>;\n}\n\nfunction createConfiguredServer(options: CreateMcpAppServerOptions): McpServer {\n return new McpServer(\n { name: options.name, version: options.version },\n { capabilities: { resources: {}, tools: {} } },\n );\n}\n\n/** Convenience adapter that retains the original one-call server experience. */\nexport function createMcpAppServer(options: CreateMcpAppServerOptions): RunningMcpAppServer {\n let server: McpServer | undefined;\n async function build(): Promise<McpServer> {\n const next = createConfiguredServer(options);\n await registerMcpApps(next, { views: options.views, tools: options.tools });\n server = next;\n return next;\n }\n return {\n async connectInProcess(connectOptions) {\n const next = await build();\n const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();\n await next.connect(serverTransport);\n const client = new Client(\n { name: `${options.name}-inprocess-client`, version: options.version },\n (connectOptions?.ui ?? true)\n ? ({\n capabilities: {\n extensions: { [EXTENSION_ID]: { mimeTypes: [RESOURCE_MIME_TYPE] } },\n },\n } as never)\n : undefined,\n );\n await client.connect(clientTransport);\n return { client };\n },\n async listen(transport) {\n const next = await build();\n await next.connect(transport);\n },\n async close() {\n await server?.close();\n },\n };\n}\n\nexport async function serveStdio(options: CreateMcpAppServerOptions): Promise<RunningMcpAppServer> {\n const { StdioServerTransport } = await import(\"@modelcontextprotocol/sdk/server/stdio.js\");\n const server = createMcpAppServer(options);\n await server.listen(new StdioServerTransport());\n return server;\n}\n\nexport { EXTENSION_ID, RESOURCE_MIME_TYPE };\n"],"mappings":"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","names":[]}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Svelte entry point: turn a Svelte component into an MCP App View.
3
+ *
4
+ * ```ts
5
+ * import { createMcpView } from "mountly-mcp/svelte";
6
+ * import Dashboard from "./Dashboard.svelte";
7
+ *
8
+ * createMcpView(Dashboard);
9
+ * ```
10
+ *
11
+ * ```svelte
12
+ * <script lang="ts">
13
+ * import type { McpViewProps } from "mountly-mcp";
14
+ * let { mcp, toolResult, hostContext }: McpViewProps = $props();
15
+ * </script>
16
+ * ```
17
+ *
18
+ * Unlike the React and Vue entry points there are no context helpers: Svelte's
19
+ * `setContext` has to run inside a compiled component, which would mean
20
+ * shipping a `.svelte` file and a compiler from this package. Props are already
21
+ * reactive via `$props()`, so pass them down where a nested component needs
22
+ * them.
23
+ */
24
+ import { createWidget } from "mountly-svelte";
25
+ import type { AdapterOptions } from "mountly/adapter";
26
+ import type { McpView, McpViewProps } from "../types.js";
27
+ /** Wraps a Svelte component as an MCP Apps View and publishes it for the bridge. */
28
+ export declare function createMcpView<P extends object>(Component: Parameters<typeof createWidget<P & McpViewProps>>[0], options?: AdapterOptions): McpView;
29
+ export type { McpViewProps };
@@ -0,0 +1,13 @@
1
+ import {
2
+ publishMcpView
3
+ } from "../chunk-SADNMGP6.js";
4
+
5
+ // src/svelte/index.ts
6
+ import { createWidget } from "mountly-svelte";
7
+ function createMcpView(Component, options) {
8
+ return publishMcpView(createWidget(Component, options));
9
+ }
10
+ export {
11
+ createMcpView
12
+ };
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/svelte/index.ts"],"sourcesContent":["/**\n * Svelte entry point: turn a Svelte component into an MCP App View.\n *\n * ```ts\n * import { createMcpView } from \"mountly-mcp/svelte\";\n * import Dashboard from \"./Dashboard.svelte\";\n *\n * createMcpView(Dashboard);\n * ```\n *\n * ```svelte\n * <script lang=\"ts\">\n * import type { McpViewProps } from \"mountly-mcp\";\n * let { mcp, toolResult, hostContext }: McpViewProps = $props();\n * </script>\n * ```\n *\n * Unlike the React and Vue entry points there are no context helpers: Svelte's\n * `setContext` has to run inside a compiled component, which would mean\n * shipping a `.svelte` file and a compiler from this package. Props are already\n * reactive via `$props()`, so pass them down where a nested component needs\n * them.\n */\nimport { createWidget } from \"mountly-svelte\";\nimport type { AdapterOptions } from \"mountly/adapter\";\nimport type { McpView, McpViewProps } from \"../types.js\";\nimport { publishMcpView } from \"../publish.js\";\n\n/** Wraps a Svelte component as an MCP Apps View and publishes it for the bridge. */\nexport function createMcpView<P extends object>(\n Component: Parameters<typeof createWidget<P & McpViewProps>>[0],\n options?: AdapterOptions,\n): McpView {\n return publishMcpView(createWidget<P & McpViewProps>(Component, options));\n}\n\nexport type { McpViewProps };\n"],"mappings":";;;;;AAuBA,SAAS,oBAAoB;AAMtB,SAAS,cACd,WACA,SACS;AACT,SAAO,eAAe,aAA+B,WAAW,OAAO,CAAC;AAC1E;","names":[]}
@@ -0,0 +1,27 @@
1
+ import { type ConformanceDiagnostic, type McpAppArtifact } from "../artifact/index.js";
2
+ export interface VerifyMcpAppsOptions {
3
+ /** Canonical manifest. Defaults to `dist/mountly-mcp.manifest.json`. */
4
+ manifestPath?: string;
5
+ /** Transitional single-View inputs when no manifest exists. */
6
+ htmlPaths?: ReadonlyArray<string>;
7
+ /**
8
+ * Load each View in a real browser and confirm the View mounts with content.
9
+ *
10
+ * Off by default: it needs Playwright and a Chromium download, which the
11
+ * static checks do not. Turn it on in CI. Everything else here inspects
12
+ * files, and a View can satisfy every file-level check while failing to
13
+ * render — a framework left external, a Node global in the bundle, a bridge
14
+ * that never completes the handshake all produce conforming HTML.
15
+ */
16
+ render?: boolean;
17
+ }
18
+ export interface McpAppConformanceReport {
19
+ ok: boolean;
20
+ /** Verification tier selected by the caller. */
21
+ mode: "static" | "browser";
22
+ artifacts: ReadonlyArray<McpAppArtifact>;
23
+ diagnostics: ReadonlyArray<ConformanceDiagnostic>;
24
+ }
25
+ /** Deterministic verification shared by the CLI and downstream CI. */
26
+ export declare function verifyMcpApps(options?: VerifyMcpAppsOptions): Promise<McpAppConformanceReport>;
27
+ export declare function formatConformanceReport(report: McpAppConformanceReport): string;
@@ -0,0 +1,312 @@
1
+ import {
2
+ MCP_APP_MANIFEST_FILE,
3
+ McpAppConformanceError,
4
+ readMcpAppArtifact,
5
+ readMcpAppManifest
6
+ } from "../chunk-DKK26TRS.js";
7
+
8
+ // src/testing/index.ts
9
+ import { access, readFile } from "fs/promises";
10
+ import { isDeepStrictEqual } from "util";
11
+
12
+ // src/testing/render-artifact.ts
13
+ function error(code, message, source) {
14
+ return { severity: "error", code, message, source };
15
+ }
16
+ function warning(code, message, source) {
17
+ return { severity: "warning", code, message, source };
18
+ }
19
+ async function loadAxeSource() {
20
+ const specifier = "axe-core";
21
+ try {
22
+ const mod = await import(specifier);
23
+ return mod.source ?? mod.default?.source;
24
+ } catch {
25
+ return void 0;
26
+ }
27
+ }
28
+ async function auditAccessibility(view, artifact) {
29
+ const axeSource = await loadAxeSource();
30
+ if (!axeSource) return [];
31
+ let violations;
32
+ try {
33
+ await view.evaluate(axeSource);
34
+ violations = await view.evaluate(`
35
+ axe.run(document, { resultTypes: ["violations"] }).then((result) =>
36
+ result.violations.map((violation) => ({
37
+ id: violation.id,
38
+ impact: violation.impact ?? null,
39
+ help: violation.help,
40
+ nodes: violation.nodes.length,
41
+ target: String(violation.nodes[0]?.target?.[0] ?? "")
42
+ }))
43
+ )
44
+ `);
45
+ } catch (caught) {
46
+ return [
47
+ warning(
48
+ "render/a11y-unavailable",
49
+ `accessibility pass did not run: ${caught instanceof Error ? caught.message : String(caught)}`,
50
+ artifact.htmlPath
51
+ )
52
+ ];
53
+ }
54
+ return violations.map(
55
+ (violation) => warning(
56
+ "render/a11y",
57
+ `${violation.id} (${violation.impact ?? "unknown"} impact): ${violation.help} \u2014 ${violation.nodes} element${violation.nodes === 1 ? "" : "s"}, first at \`${violation.target}\``,
58
+ artifact.htmlPath
59
+ )
60
+ );
61
+ }
62
+ async function launchBrowser() {
63
+ const specifiers = ["playwright", "@playwright/test"];
64
+ for (const specifier of specifiers) {
65
+ let mod;
66
+ try {
67
+ mod = await import(specifier);
68
+ } catch {
69
+ continue;
70
+ }
71
+ if (mod.chromium) return mod.chromium.launch();
72
+ }
73
+ return void 0;
74
+ }
75
+ async function startHost(artifact) {
76
+ const { startDevHost } = await import("../dev/index.js");
77
+ return startDevHost({
78
+ htmlPath: artifact.htmlPath,
79
+ fixtures: { conformance: {} },
80
+ hostPort: 5480
81
+ });
82
+ }
83
+ async function renderMcpAppArtifactWith(artifact, dependencies) {
84
+ let browser;
85
+ try {
86
+ browser = await dependencies.launchBrowser();
87
+ } catch (caught) {
88
+ return [
89
+ error(
90
+ "render/unavailable",
91
+ `could not launch Chromium: ${caught instanceof Error ? caught.message : String(caught)}`,
92
+ artifact.htmlPath
93
+ )
94
+ ];
95
+ }
96
+ if (!browser) {
97
+ return [
98
+ error(
99
+ "render/unavailable",
100
+ "render requested but Playwright is not installed (npm i -D playwright)",
101
+ artifact.htmlPath
102
+ )
103
+ ];
104
+ }
105
+ let host;
106
+ const failures = [];
107
+ try {
108
+ host = await dependencies.startHost(artifact);
109
+ const page = await browser.newPage();
110
+ const pageErrors = [];
111
+ page.on("pageerror", (caught) => pageErrors.push(caught.message));
112
+ await page.goto(host.hostUrl);
113
+ const deadline = Date.now() + 2e4;
114
+ let mounted = false;
115
+ let renderedContent = false;
116
+ let mountedAt;
117
+ let boundary;
118
+ while (!boundary && Date.now() < deadline && pageErrors.length === 0) {
119
+ const view = page.frames().find((frame) => frame.url().includes("srcdoc"));
120
+ const state = view ? await view.evaluate(() => {
121
+ const failed = document.querySelector("[data-mountly-mcp-error]");
122
+ const root = document.querySelector("#mountly-mcp-root");
123
+ const bounds = root?.getBoundingClientRect();
124
+ return {
125
+ state: root?.getAttribute("data-mountly-mcp-state") ?? void 0,
126
+ html: root?.innerHTML ?? "",
127
+ shadowHtml: root?.shadowRoot?.innerHTML ?? "",
128
+ visibleHeight: bounds?.height ?? 0,
129
+ error: failed ? `${failed.getAttribute("data-mountly-mcp-error") ?? ""}: ${failed.textContent ?? ""}` : void 0
130
+ };
131
+ }).catch(() => void 0) : void 0;
132
+ boundary = state?.error;
133
+ mounted || (mounted = state?.state === "mounted");
134
+ if (mounted && mountedAt === void 0) mountedAt = Date.now();
135
+ renderedContent = (state?.html ?? "").trim().length > 0 || (state?.shadowHtml ?? "").trim().length > 0 || (state?.visibleHeight ?? 0) > 0;
136
+ if (mounted && renderedContent) break;
137
+ if (mountedAt !== void 0 && Date.now() - mountedAt >= 2e3) break;
138
+ await new Promise((done) => setTimeout(done, 100));
139
+ }
140
+ if (boundary) {
141
+ failures.push(
142
+ error("render/error-boundary", `View rendered an error: ${boundary}`, artifact.htmlPath)
143
+ );
144
+ } else if (pageErrors.length > 0) {
145
+ failures.push(
146
+ error("render/threw", `View threw while loading: ${pageErrors[0]}`, artifact.htmlPath)
147
+ );
148
+ } else if (!mounted) {
149
+ failures.push(
150
+ error(
151
+ "render/timeout",
152
+ "View never mounted: the handshake or View mount did not complete",
153
+ artifact.htmlPath
154
+ )
155
+ );
156
+ } else if (!renderedContent) {
157
+ failures.push(
158
+ error("render/blank", "View mounted but rendered no content", artifact.htmlPath)
159
+ );
160
+ } else {
161
+ const view = page.frames().find((frame) => frame.url().includes("srcdoc"));
162
+ if (view) failures.push(...await auditAccessibility(view, artifact));
163
+ }
164
+ } catch (caught) {
165
+ failures.push(
166
+ error(
167
+ "render/failed",
168
+ caught instanceof Error ? caught.message : String(caught),
169
+ artifact.htmlPath
170
+ )
171
+ );
172
+ } finally {
173
+ await Promise.allSettled([browser.close(), host?.close() ?? Promise.resolve()]);
174
+ }
175
+ return failures;
176
+ }
177
+ function renderMcpAppArtifact(artifact) {
178
+ return renderMcpAppArtifactWith(artifact, { launchBrowser, startHost });
179
+ }
180
+
181
+ // src/testing/index.ts
182
+ function error2(code, message, source) {
183
+ return { severity: "error", code, message, source };
184
+ }
185
+ function warning2(code, message, source) {
186
+ return { severity: "warning", code, message, source };
187
+ }
188
+ async function verifyMcpApps(options = {}) {
189
+ const diagnostics = [];
190
+ const artifacts = [];
191
+ if (options.htmlPaths && options.htmlPaths.length > 0) {
192
+ for (const path of options.htmlPaths) {
193
+ try {
194
+ artifacts.push(await readMcpAppArtifact(path));
195
+ } catch (caught) {
196
+ if (caught instanceof McpAppConformanceError) diagnostics.push(...caught.diagnostics);
197
+ else
198
+ diagnostics.push(
199
+ error2("verify/load", caught instanceof Error ? caught.message : String(caught))
200
+ );
201
+ }
202
+ }
203
+ } else {
204
+ try {
205
+ const loaded = await readMcpAppManifest(
206
+ options.manifestPath ?? `dist/${MCP_APP_MANIFEST_FILE}`
207
+ );
208
+ for (const declared of loaded.artifacts) {
209
+ try {
210
+ const actual = await readMcpAppArtifact(declared.htmlPath, declared.uri);
211
+ if (actual.name !== declared.name) {
212
+ diagnostics.push(
213
+ error2(
214
+ "manifest/name-mismatch",
215
+ `manifest app '${declared.name}' points to artifact '${actual.name}'`,
216
+ loaded.path
217
+ )
218
+ );
219
+ }
220
+ if (!isDeepStrictEqual(actual.declaration, declared.declaration)) {
221
+ diagnostics.push(
222
+ error2(
223
+ "manifest/declaration-mismatch",
224
+ `manifest declaration for '${declared.name}' does not match its metadata file`,
225
+ loaded.path
226
+ )
227
+ );
228
+ }
229
+ artifacts.push(actual);
230
+ } catch (caught) {
231
+ if (caught instanceof McpAppConformanceError) diagnostics.push(...caught.diagnostics);
232
+ else
233
+ diagnostics.push(
234
+ error2("verify/load", caught instanceof Error ? caught.message : String(caught))
235
+ );
236
+ }
237
+ }
238
+ } catch (caught) {
239
+ if (caught instanceof McpAppConformanceError) diagnostics.push(...caught.diagnostics);
240
+ else
241
+ diagnostics.push(
242
+ error2("verify/load", caught instanceof Error ? caught.message : String(caught))
243
+ );
244
+ }
245
+ }
246
+ const names = /* @__PURE__ */ new Set();
247
+ const uris = /* @__PURE__ */ new Set();
248
+ for (const artifact of artifacts) {
249
+ if (names.has(artifact.name)) {
250
+ diagnostics.push(error2("manifest/duplicate-name", `duplicate app name '${artifact.name}'`));
251
+ }
252
+ if (uris.has(artifact.uri)) {
253
+ diagnostics.push(error2("manifest/duplicate-uri", `duplicate app uri '${artifact.uri}'`));
254
+ }
255
+ names.add(artifact.name);
256
+ uris.add(artifact.uri);
257
+ try {
258
+ await Promise.all([access(artifact.htmlPath), access(artifact.metaPath)]);
259
+ const html = await readFile(artifact.htmlPath, "utf8");
260
+ if (!html.includes("ui/initialize") || !html.includes("ui/notifications/initialized")) {
261
+ diagnostics.push(
262
+ error2(
263
+ "artifact/bridge",
264
+ "View HTML does not contain the MCP Apps initialization handshake",
265
+ artifact.htmlPath
266
+ )
267
+ );
268
+ }
269
+ if (options.render) diagnostics.push(...await renderMcpAppArtifact(artifact));
270
+ if (!artifact.declaration.description) {
271
+ diagnostics.push(
272
+ warning2(
273
+ "artifact/description",
274
+ `View '${artifact.name}' has no description`,
275
+ artifact.metaPath
276
+ )
277
+ );
278
+ }
279
+ } catch (caught) {
280
+ diagnostics.push(
281
+ error2(
282
+ "artifact/files",
283
+ caught instanceof Error ? caught.message : String(caught),
284
+ artifact.htmlPath
285
+ )
286
+ );
287
+ }
288
+ }
289
+ return {
290
+ ok: !diagnostics.some((diagnostic) => diagnostic.severity === "error"),
291
+ mode: options.render === true ? "browser" : "static",
292
+ artifacts,
293
+ diagnostics
294
+ };
295
+ }
296
+ function formatConformanceReport(report) {
297
+ const lines = report.diagnostics.map((diagnostic) => {
298
+ const marker = diagnostic.severity === "error" ? "ERROR" : "WARN";
299
+ const source = diagnostic.source ? ` (${diagnostic.source})` : "";
300
+ return `${marker} ${diagnostic.code}: ${diagnostic.message}${source}`;
301
+ });
302
+ lines.push(
303
+ `${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"})`
304
+ );
305
+ return `${lines.join("\n")}
306
+ `;
307
+ }
308
+ export {
309
+ formatConformanceReport,
310
+ verifyMcpApps
311
+ };
312
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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":";;;;;;;;AAAA,SAAS,QAAQ,gBAAgB;AACjC,SAAS,yBAAyB;;;ACiClC,SAAS,MAAM,MAAc,SAAiB,QAAuC;AACnF,SAAO,EAAE,UAAU,SAAS,MAAM,SAAS,OAAO;AACpD;AAEA,SAAS,QAAQ,MAAc,SAAiB,QAAuC;AACrF,SAAO,EAAE,UAAU,WAAW,MAAM,SAAS,OAAO;AACtD;AAWA,eAAe,gBAA6C;AAC1D,QAAM,YAAoB;AAC1B,MAAI;AACF,UAAM,MAAO,MAAM,OAAO;AAC1B,WAAO,IAAI,UAAU,IAAI,SAAS;AAAA,EACpC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAUA,eAAe,mBACb,MACA,UACkC;AAClC,QAAM,YAAY,MAAM,cAAc;AACtC,MAAI,CAAC,UAAW,QAAO,CAAC;AAExB,MAAI;AACJ,MAAI;AACF,UAAM,KAAK,SAAS,SAAS;AAC7B,iBAAa,MAAM,KAAK,SAAyB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAUhD;AAAA,EACH,SAAS,QAAQ;AACf,WAAO;AAAA,MACL;AAAA,QACE;AAAA,QACA,mCAAmC,kBAAkB,QAAQ,OAAO,UAAU,OAAO,MAAM,CAAC;AAAA,QAC5F,SAAS;AAAA,MACX;AAAA,IACF;AAAA,EACF;AAEA,SAAO,WAAW;AAAA,IAAI,CAAC,cACrB;AAAA,MACE;AAAA,MACA,GAAG,UAAU,EAAE,KAAK,UAAU,UAAU,SAAS,aAAa,UAAU,IAAI,WAAM,UAAU,KAAK,WAAW,UAAU,UAAU,IAAI,KAAK,GAAG,gBAAgB,UAAU,MAAM;AAAA,MAC5K,SAAS;AAAA,IACX;AAAA,EACF;AACF;AAGA,eAAe,gBAAyD;AAKtE,QAAM,aAAuB,CAAC,cAAc,kBAAkB;AAC9D,aAAW,aAAa,YAAY;AAClC,QAAI;AACJ,QAAI;AACF,YAAO,MAAM,OAAO;AAAA,IACtB,QAAQ;AACN;AAAA,IACF;AACA,QAAI,IAAI,SAAU,QAAO,IAAI,SAAS,OAAO;AAAA,EAC/C;AACA,SAAO;AACT;AAEA,eAAe,UAAU,UAAoD;AAC3E,QAAM,EAAE,aAAa,IAAI,MAAM,OAAO,iBAAiB;AACvD,SAAO,aAAa;AAAA,IAClB,UAAU,SAAS;AAAA,IACnB,UAAU,EAAE,aAAa,CAAC,EAAE;AAAA,IAC5B,UAAU;AAAA,EACZ,CAAC;AACH;AAMA,eAAsB,yBACpB,UACA,cACkC;AAClC,MAAI;AACJ,MAAI;AACF,cAAU,MAAM,aAAa,cAAc;AAAA,EAC7C,SAAS,QAAQ;AACf,WAAO;AAAA,MACL;AAAA,QACE;AAAA,QACA,8BAA8B,kBAAkB,QAAQ,OAAO,UAAU,OAAO,MAAM,CAAC;AAAA,QACvF,SAAS;AAAA,MACX;AAAA,IACF;AAAA,EACF;AACA,MAAI,CAAC,SAAS;AACZ,WAAO;AAAA,MACL;AAAA,QACE;AAAA,QACA;AAAA,QACA,SAAS;AAAA,MACX;AAAA,IACF;AAAA,EACF;AAEA,MAAI;AACJ,QAAM,WAAoC,CAAC;AAC3C,MAAI;AACF,WAAO,MAAM,aAAa,UAAU,QAAQ;AAC5C,UAAM,OAAO,MAAM,QAAQ,QAAQ;AACnC,UAAM,aAAuB,CAAC;AAC9B,SAAK,GAAG,aAAa,CAAC,WAAW,WAAW,KAAK,OAAO,OAAO,CAAC;AAChE,UAAM,KAAK,KAAK,KAAK,OAAO;AAE5B,UAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,QAAI,UAAU;AACd,QAAI,kBAAkB;AACtB,QAAI;AACJ,QAAI;AACJ,WAAO,CAAC,YAAY,KAAK,IAAI,IAAI,YAAY,WAAW,WAAW,GAAG;AACpE,YAAM,OAAO,KAAK,OAAO,EAAE,KAAK,CAAC,UAAU,MAAM,IAAI,EAAE,SAAS,QAAQ,CAAC;AACzE,YAAM,QAAQ,OACV,MAAM,KACH,SAAS,MAAM;AACd,cAAM,SAAS,SAAS,cAAc,0BAA0B;AAChE,cAAM,OAAO,SAAS,cAAc,mBAAmB;AACvD,cAAM,SAAS,MAAM,sBAAsB;AAC3C,eAAO;AAAA,UACL,OAAO,MAAM,aAAa,wBAAwB,KAAK;AAAA,UACvD,MAAM,MAAM,aAAa;AAAA,UACzB,YAAY,MAAM,YAAY,aAAa;AAAA,UAC3C,eAAe,QAAQ,UAAU;AAAA,UACjC,OAAO,SACH,GAAG,OAAO,aAAa,wBAAwB,KAAK,EAAE,KAAK,OAAO,eAAe,EAAE,KACnF;AAAA,QACN;AAAA,MACF,CAAC,EACA,MAAM,MAAM,MAAS,IACxB;AACJ,iBAAW,OAAO;AAGlB,4BAAY,OAAO,UAAU;AAC7B,UAAI,WAAW,cAAc,OAAW,aAAY,KAAK,IAAI;AAC7D,yBACG,OAAO,QAAQ,IAAI,KAAK,EAAE,SAAS,MACnC,OAAO,cAAc,IAAI,KAAK,EAAE,SAAS,MACzC,OAAO,iBAAiB,KAAK;AAChC,UAAI,WAAW,gBAAiB;AAGhC,UAAI,cAAc,UAAa,KAAK,IAAI,IAAI,aAAa,IAAO;AAChE,YAAM,IAAI,QAAQ,CAAC,SAAS,WAAW,MAAM,GAAG,CAAC;AAAA,IACnD;AAEA,QAAI,UAAU;AACZ,eAAS;AAAA,QACP,MAAM,yBAAyB,2BAA2B,QAAQ,IAAI,SAAS,QAAQ;AAAA,MACzF;AAAA,IACF,WAAW,WAAW,SAAS,GAAG;AAChC,eAAS;AAAA,QACP,MAAM,gBAAgB,6BAA6B,WAAW,CAAC,CAAC,IAAI,SAAS,QAAQ;AAAA,MACvF;AAAA,IACF,WAAW,CAAC,SAAS;AACnB,eAAS;AAAA,QACP;AAAA,UACE;AAAA,UACA;AAAA,UACA,SAAS;AAAA,QACX;AAAA,MACF;AAAA,IACF,WAAW,CAAC,iBAAiB;AAC3B,eAAS;AAAA,QACP,MAAM,gBAAgB,wCAAwC,SAAS,QAAQ;AAAA,MACjF;AAAA,IACF,OAAO;AAGL,YAAM,OAAO,KAAK,OAAO,EAAE,KAAK,CAAC,UAAU,MAAM,IAAI,EAAE,SAAS,QAAQ,CAAC;AACzE,UAAI,KAAM,UAAS,KAAK,GAAI,MAAM,mBAAmB,MAAM,QAAQ,CAAE;AAAA,IACvE;AAAA,EACF,SAAS,QAAQ;AACf,aAAS;AAAA,MACP;AAAA,QACE;AAAA,QACA,kBAAkB,QAAQ,OAAO,UAAU,OAAO,MAAM;AAAA,QACxD,SAAS;AAAA,MACX;AAAA,IACF;AAAA,EACF,UAAE;AACA,UAAM,QAAQ,WAAW,CAAC,QAAQ,MAAM,GAAG,MAAM,MAAM,KAAK,QAAQ,QAAQ,CAAC,CAAC;AAAA,EAChF;AACA,SAAO;AACT;AAEO,SAAS,qBAAqB,UAA4D;AAC/F,SAAO,yBAAyB,UAAU,EAAE,eAAe,UAAU,CAAC;AACxE;;;AD9NA,SAASA,OAAM,MAAc,SAAiB,QAAwC;AACpF,SAAO,EAAE,UAAU,SAAS,MAAM,SAAS,OAAO;AACpD;AAEA,SAASC,SAAQ,MAAc,SAAiB,QAAwC;AACtF,SAAO,EAAE,UAAU,WAAW,MAAM,SAAS,OAAO;AACtD;AAGA,eAAsB,cACpB,UAAgC,CAAC,GACC;AAClC,QAAM,cAAuC,CAAC;AAC9C,QAAM,YAA8B,CAAC;AACrC,MAAI,QAAQ,aAAa,QAAQ,UAAU,SAAS,GAAG;AACrD,eAAW,QAAQ,QAAQ,WAAW;AACpC,UAAI;AACF,kBAAU,KAAK,MAAM,mBAAmB,IAAI,CAAC;AAAA,MAC/C,SAAS,QAAQ;AACf,YAAI,kBAAkB,uBAAwB,aAAY,KAAK,GAAG,OAAO,WAAW;AAAA;AAElF,sBAAY;AAAA,YACVD,OAAM,eAAe,kBAAkB,QAAQ,OAAO,UAAU,OAAO,MAAM,CAAC;AAAA,UAChF;AAAA,MACJ;AAAA,IACF;AAAA,EACF,OAAO;AACL,QAAI;AACF,YAAM,SAAS,MAAM;AAAA,QACnB,QAAQ,gBAAgB,QAAQ,qBAAqB;AAAA,MACvD;AACA,iBAAW,YAAY,OAAO,WAAW;AACvC,YAAI;AACF,gBAAM,SAAS,MAAM,mBAAmB,SAAS,UAAU,SAAS,GAAG;AACvE,cAAI,OAAO,SAAS,SAAS,MAAM;AACjC,wBAAY;AAAA,cACVA;AAAA,gBACE;AAAA,gBACA,iBAAiB,SAAS,IAAI,yBAAyB,OAAO,IAAI;AAAA,gBAClE,OAAO;AAAA,cACT;AAAA,YACF;AAAA,UACF;AACA,cAAI,CAAC,kBAAkB,OAAO,aAAa,SAAS,WAAW,GAAG;AAChE,wBAAY;AAAA,cACVA;AAAA,gBACE;AAAA,gBACA,6BAA6B,SAAS,IAAI;AAAA,gBAC1C,OAAO;AAAA,cACT;AAAA,YACF;AAAA,UACF;AACA,oBAAU,KAAK,MAAM;AAAA,QACvB,SAAS,QAAQ;AACf,cAAI,kBAAkB,uBAAwB,aAAY,KAAK,GAAG,OAAO,WAAW;AAAA;AAElF,wBAAY;AAAA,cACVA,OAAM,eAAe,kBAAkB,QAAQ,OAAO,UAAU,OAAO,MAAM,CAAC;AAAA,YAChF;AAAA,QACJ;AAAA,MACF;AAAA,IACF,SAAS,QAAQ;AACf,UAAI,kBAAkB,uBAAwB,aAAY,KAAK,GAAG,OAAO,WAAW;AAAA;AAElF,oBAAY;AAAA,UACVA,OAAM,eAAe,kBAAkB,QAAQ,OAAO,UAAU,OAAO,MAAM,CAAC;AAAA,QAChF;AAAA,IACJ;AAAA,EACF;AAEA,QAAM,QAAQ,oBAAI,IAAY;AAC9B,QAAM,OAAO,oBAAI,IAAY;AAC7B,aAAW,YAAY,WAAW;AAChC,QAAI,MAAM,IAAI,SAAS,IAAI,GAAG;AAC5B,kBAAY,KAAKA,OAAM,2BAA2B,uBAAuB,SAAS,IAAI,GAAG,CAAC;AAAA,IAC5F;AACA,QAAI,KAAK,IAAI,SAAS,GAAG,GAAG;AAC1B,kBAAY,KAAKA,OAAM,0BAA0B,sBAAsB,SAAS,GAAG,GAAG,CAAC;AAAA,IACzF;AACA,UAAM,IAAI,SAAS,IAAI;AACvB,SAAK,IAAI,SAAS,GAAG;AACrB,QAAI;AACF,YAAM,QAAQ,IAAI,CAAC,OAAO,SAAS,QAAQ,GAAG,OAAO,SAAS,QAAQ,CAAC,CAAC;AACxE,YAAM,OAAO,MAAM,SAAS,SAAS,UAAU,MAAM;AACrD,UAAI,CAAC,KAAK,SAAS,eAAe,KAAK,CAAC,KAAK,SAAS,8BAA8B,GAAG;AACrF,oBAAY;AAAA,UACVA;AAAA,YACE;AAAA,YACA;AAAA,YACA,SAAS;AAAA,UACX;AAAA,QACF;AAAA,MACF;AACA,UAAI,QAAQ,OAAQ,aAAY,KAAK,GAAI,MAAM,qBAAqB,QAAQ,CAAE;AAC9E,UAAI,CAAC,SAAS,YAAY,aAAa;AACrC,oBAAY;AAAA,UACVC;AAAA,YACE;AAAA,YACA,SAAS,SAAS,IAAI;AAAA,YACtB,SAAS;AAAA,UACX;AAAA,QACF;AAAA,MACF;AAAA,IACF,SAAS,QAAQ;AACf,kBAAY;AAAA,QACVD;AAAA,UACE;AAAA,UACA,kBAAkB,QAAQ,OAAO,UAAU,OAAO,MAAM;AAAA,UACxD,SAAS;AAAA,QACX;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,IAAI,CAAC,YAAY,KAAK,CAAC,eAAe,WAAW,aAAa,OAAO;AAAA,IACrE,MAAM,QAAQ,WAAW,OAAO,YAAY;AAAA,IAC5C;AAAA,IACA;AAAA,EACF;AACF;AAEO,SAAS,wBAAwB,QAAyC;AAC/E,QAAM,QAAQ,OAAO,YAAY,IAAI,CAAC,eAAe;AACnD,UAAM,SAAS,WAAW,aAAa,UAAU,UAAU;AAC3D,UAAM,SAAS,WAAW,SAAS,KAAK,WAAW,MAAM,MAAM;AAC/D,WAAO,GAAG,MAAM,IAAI,WAAW,IAAI,KAAK,WAAW,OAAO,GAAG,MAAM;AAAA,EACrE,CAAC;AACD,QAAM;AAAA,IACJ,GAAG,OAAO,KAAK,SAAS,MAAM,IAAI,OAAO,UAAU,MAAM,gBAAgB,OAAO,UAAU,WAAW,IAAI,KAAK,GAAG,KAAK,OAAO,SAAS,YAAY,mBAAmB,wDAAwD;AAAA,EAC/N;AACA,SAAO,GAAG,MAAM,KAAK,IAAI,CAAC;AAAA;AAC5B;","names":["error","warning"]}
@@ -0,0 +1,35 @@
1
+ import type { ConformanceDiagnostic, McpAppArtifact } from "../artifact/index.js";
2
+ interface FrameLike {
3
+ url(): string;
4
+ /**
5
+ * A string expression is evaluated through the debugger protocol rather than
6
+ * as page script, which is how axe reaches a View whose CSP withholds
7
+ * `unsafe-eval` — the same CSP a host enforces in production.
8
+ */
9
+ evaluate<T>(fn: (() => T) | string): Promise<T>;
10
+ }
11
+ interface PageLike {
12
+ on(event: "pageerror", handler: (error: Error) => void): void;
13
+ goto(url: string): Promise<unknown>;
14
+ frames(): FrameLike[];
15
+ }
16
+ export interface ConformanceBrowser {
17
+ newPage(): Promise<PageLike>;
18
+ close(): Promise<void>;
19
+ }
20
+ export interface ConformanceHost {
21
+ hostUrl: string;
22
+ close(): Promise<void>;
23
+ }
24
+ /** @internal Test seam for browser and host lifecycle failures. */
25
+ export interface RenderMcpAppArtifactDependencies {
26
+ launchBrowser(): Promise<ConformanceBrowser | undefined>;
27
+ startHost(artifact: McpAppArtifact): Promise<ConformanceHost>;
28
+ }
29
+ /**
30
+ * Exercise an App artifact through the real two-origin host and browser.
31
+ * Browser and host ownership stays local so every startup path is cleaned up.
32
+ */
33
+ export declare function renderMcpAppArtifactWith(artifact: McpAppArtifact, dependencies: RenderMcpAppArtifactDependencies): Promise<ConformanceDiagnostic[]>;
34
+ export declare function renderMcpAppArtifact(artifact: McpAppArtifact): Promise<ConformanceDiagnostic[]>;
35
+ export {};
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Spec types come straight from `@modelcontextprotocol/ext-apps` under their
3
+ * own names — `McpUiDisplayMode`, `McpUiHostContext`, and the rest. mountly
4
+ * deliberately does not rename them: the names in the MCP Apps spec (SEP-1865)
5
+ * are the vocabulary users already learned, and every alias is one more thing
6
+ * to translate and one more place to drift.
7
+ *
8
+ * Only genuinely mountly-specific types live here: the props a View's
9
+ * `mount()` receives, and the sidecar declaration written next to the built HTML.
10
+ */
11
+ export type { App, McpUiAppCapabilities, McpUiDisplayMode, McpUiHostCapabilities, McpUiHostContext, McpUiHostContextChangedNotification, McpUiResourceCsp, McpUiResourceMeta, McpUiResourcePermissions, McpUiToolCancelledNotification, McpUiToolInputNotification, McpUiToolInputPartialNotification, McpUiToolMeta, McpUiToolResultNotification, McpUiToolVisibility, } from "@modelcontextprotocol/ext-apps";
12
+ import type { App, McpUiDisplayMode, McpUiHostContext, McpUiResourceMeta, McpUiToolInputNotification, McpUiToolInputPartialNotification, McpUiToolResultNotification } from "@modelcontextprotocol/ext-apps";
13
+ import type { MCP_APPS_MIME, MCP_APPS_PROTOCOL_VERSION } from "./schema.js";
14
+ /**
15
+ * Props passed to an `McpView.mount(container, props)` call.
16
+ *
17
+ * The bridge surfaces the App handle plus the latest tool input/result and a
18
+ * snapshot of host context. Views receive new props through `update()` as
19
+ * notifications arrive — see `runBridge` in `bridge/index.ts`.
20
+ */
21
+ export interface McpViewProps {
22
+ /** The view-side ext-apps App; exposes the full spec-compliant API surface. */
23
+ mcp: App;
24
+ /** Tool arguments (after `ui/notifications/tool-input`). */
25
+ toolInput?: McpUiToolInputNotification["params"];
26
+ /**
27
+ * Streaming tool arguments (`ui/notifications/tool-input-partial`), only when
28
+ * the bridge runs with `streamToolInput`. Best-effort recovered JSON: fields
29
+ * may be missing or change between notifications, so never drive a critical
30
+ * operation from it. Cleared once the complete `toolInput` arrives.
31
+ */
32
+ toolInputPartial?: McpUiToolInputPartialNotification["params"];
33
+ /** Tool result (after `ui/notifications/tool-result`). */
34
+ toolResult?: McpUiToolResultNotification["params"];
35
+ /** Host context (theme, styles, displayMode, container dimensions, etc.). */
36
+ hostContext?: McpUiHostContext;
37
+ }
38
+ /**
39
+ * Resource metadata written to the `.meta.json` sidecar next to the emitted
40
+ * HTML, consumed by `mountly-mcp/server` when registering the `ui://`
41
+ * resource with `_meta.ui = {...}`.
42
+ */
43
+ export interface McpResourceDeclaration {
44
+ protocolVersion: typeof MCP_APPS_PROTOCOL_VERSION;
45
+ uri: string;
46
+ name: string;
47
+ description?: string;
48
+ mimeType: typeof MCP_APPS_MIME;
49
+ /** Whether `runBridge` defers mount until the first tool-result arrives. */
50
+ awaitToolResult: boolean;
51
+ displayModes: ReadonlyArray<McpUiDisplayMode>;
52
+ _meta: {
53
+ ui: McpUiResourceMeta;
54
+ };
55
+ }
56
+ /**
57
+ * A built MCP Apps View: the `mount`/`update`/`unmount` module the bridge drives
58
+ * inside the sandboxed iframe.
59
+ *
60
+ * Structurally this is core mountly's `WidgetModule` — core is a general-purpose
61
+ * UI platform and keeps its own vocabulary. `mountly-mcp` speaks the MCP Apps
62
+ * spec, where the iframe side of an App is the **View**, so that is the only
63
+ * name this package's public API uses.
64
+ */
65
+ export type { WidgetModule as McpView } from "mountly/adapter";
@@ -0,0 +1,23 @@
1
+ import type { Plugin } from "vite";
2
+ import type { BuildSelfContainedOptions } from "../build/index.js";
3
+ export interface MountlyMcpViewOptions extends Omit<BuildSelfContainedOptions, "entry" | "output" | "cssEntry"> {
4
+ entry: string;
5
+ output?: string;
6
+ cleanIntermediate?: boolean;
7
+ }
8
+ export interface MountlyMcpViewsOptions {
9
+ apps: ReadonlyArray<MountlyMcpViewOptions>;
10
+ /** Defaults to `<outDir>/mountly-mcp.manifest.json`; false disables it. */
11
+ manifest?: string | false;
12
+ }
13
+ export type MountlyMcpViteOptions = MountlyMcpViewOptions | MountlyMcpViewsOptions;
14
+ export interface MountlyMcpViteApi {
15
+ apps: ReadonlyArray<MountlyMcpViewOptions>;
16
+ manifest?: string | false;
17
+ }
18
+ /**
19
+ * Build one or many independent MCP Apps Views. Each View is a Vite build
20
+ * environment, so frameworks and CSS remain isolated while callers configure
21
+ * the collection once.
22
+ */
23
+ export declare function mountlyMcpViews(options: MountlyMcpViteOptions): Plugin;