mountly-mcp 3.1.0 → 4.0.1

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 (95) hide show
  1. package/README.md +59 -29
  2. package/dist/bridge/iframe-entry.js +24 -16
  3. package/dist/bridge/index.d.ts +5 -5
  4. package/dist/bridge/index.js +2 -2
  5. package/dist/build/index.d.ts +3 -3
  6. package/dist/build/index.js +2 -2
  7. package/dist/{chunk-NLQDVNZP.js → chunk-AEVQKFXX.js} +12 -8
  8. package/dist/chunk-AEVQKFXX.js.map +1 -0
  9. package/dist/{chunk-SNWNF2XI.js → chunk-JVQZGKBL.js} +13 -13
  10. package/dist/chunk-JVQZGKBL.js.map +1 -0
  11. package/dist/chunk-KWVGW5Q7.js +582 -0
  12. package/dist/chunk-KWVGW5Q7.js.map +1 -0
  13. package/dist/{chunk-DQLQOMDM.js → chunk-M4WFJR3C.js} +2 -2
  14. package/dist/{chunk-DQLQOMDM.js.map → chunk-M4WFJR3C.js.map} +1 -1
  15. package/dist/{chunk-4WAKOYMK.js → chunk-MTJDRWGD.js} +2 -2
  16. package/dist/chunk-MTJDRWGD.js.map +1 -0
  17. package/dist/chunk-SADNMGP6.js +16 -0
  18. package/dist/chunk-SADNMGP6.js.map +1 -0
  19. package/dist/cli.js +21 -10
  20. package/dist/cli.js.map +1 -1
  21. package/dist/create.d.ts +10 -0
  22. package/dist/create.js +113 -0
  23. package/dist/create.js.map +1 -0
  24. package/dist/dev/host-entry.js +2 -2
  25. package/dist/dev/index.d.ts +1 -1
  26. package/dist/dev/index.js +1 -1
  27. package/dist/index.d.ts +1 -0
  28. package/dist/index.js +10 -2
  29. package/dist/json-render/{widget.d.ts → generative-view.d.ts} +9 -11
  30. package/dist/json-render/index.d.ts +1 -1
  31. package/dist/json-render/index.js +9 -8
  32. package/dist/json-render/index.js.map +1 -1
  33. package/dist/publish.d.ts +10 -0
  34. package/dist/react/context.d.ts +1 -1
  35. package/dist/react/create-view.d.ts +8 -0
  36. package/dist/react/hooks.d.ts +1 -1
  37. package/dist/react/index.d.ts +1 -1
  38. package/dist/react/index.js +6 -5
  39. package/dist/schema.d.ts +2 -2
  40. package/dist/server/index.d.ts +2 -2
  41. package/dist/server/index.js.map +1 -1
  42. package/dist/svelte/index.d.ts +10 -16
  43. package/dist/svelte/index.js +7 -3
  44. package/dist/svelte/index.js.map +1 -1
  45. package/dist/testing/index.d.ts +1 -1
  46. package/dist/testing/index.js +1 -1
  47. package/dist/testing/index.js.map +1 -1
  48. package/dist/types.d.ts +14 -4
  49. package/dist/vite/index.d.ts +6 -6
  50. package/dist/vite/index.js +7 -7
  51. package/dist/vite/index.js.map +1 -1
  52. package/dist/vue/index.d.ts +12 -17
  53. package/dist/vue/index.js +10 -6
  54. package/dist/vue/index.js.map +1 -1
  55. package/package.json +11 -15
  56. package/templates/react/.gitignore.tmpl +4 -0
  57. package/templates/react/README.md.tmpl +15 -0
  58. package/templates/react/mcp.fixtures.json.tmpl +4 -0
  59. package/templates/react/package.json.tmpl +27 -0
  60. package/templates/react/server.mjs.tmpl +70 -0
  61. package/templates/react/src/view.tsx.tmpl +160 -0
  62. package/templates/react/tsconfig.json.tmpl +15 -0
  63. package/templates/react/vite.config.ts.tmpl +22 -0
  64. package/templates/svelte/.gitignore.tmpl +4 -0
  65. package/templates/svelte/README.md.tmpl +14 -0
  66. package/templates/svelte/mcp.fixtures.json.tmpl +4 -0
  67. package/templates/svelte/package.json.tmpl +26 -0
  68. package/templates/svelte/server.mjs.tmpl +70 -0
  69. package/templates/svelte/src/Dashboard.svelte.tmpl +158 -0
  70. package/templates/svelte/src/view.ts.tmpl +14 -0
  71. package/templates/svelte/tsconfig.json.tmpl +14 -0
  72. package/templates/svelte/vite.config.ts.tmpl +22 -0
  73. package/templates/vanilla/.gitignore.tmpl +4 -0
  74. package/templates/vanilla/README.md.tmpl +20 -0
  75. package/templates/vanilla/mcp.fixtures.json.tmpl +4 -0
  76. package/templates/vanilla/package.json.tmpl +22 -0
  77. package/templates/vanilla/server.mjs.tmpl +70 -0
  78. package/templates/vanilla/src/view.ts.tmpl +177 -0
  79. package/templates/vanilla/tsconfig.json.tmpl +14 -0
  80. package/templates/vanilla/vite.config.ts.tmpl +20 -0
  81. package/templates/vue/.gitignore.tmpl +4 -0
  82. package/templates/vue/README.md.tmpl +14 -0
  83. package/templates/vue/mcp.fixtures.json.tmpl +4 -0
  84. package/templates/vue/package.json.tmpl +26 -0
  85. package/templates/vue/server.mjs.tmpl +70 -0
  86. package/templates/vue/src/Dashboard.vue.tmpl +159 -0
  87. package/templates/vue/src/view.ts.tmpl +14 -0
  88. package/templates/vue/tsconfig.json.tmpl +15 -0
  89. package/templates/vue/vite.config.ts.tmpl +22 -0
  90. package/dist/chunk-4WAKOYMK.js.map +0 -1
  91. package/dist/chunk-KRAS266U.js +0 -260
  92. package/dist/chunk-KRAS266U.js.map +0 -1
  93. package/dist/chunk-NLQDVNZP.js.map +0 -1
  94. package/dist/chunk-SNWNF2XI.js.map +0 -1
  95. package/dist/react/create-widget.d.ts +0 -11
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/json-render/widget.tsx","../../src/json-render/index.ts","../../src/json-render/use-spec-stream.ts"],"sourcesContent":["import { createElement } from \"react\";\nimport { type ComponentMap, createRenderer } from \"@json-render/react\";\nimport type { Catalog, SchemaDefinition, Spec } from \"@json-render/core\";\nimport { createMcpWidget, useMcpHost, useToolResult } from \"../react/index.js\";\nimport type { App } from \"@modelcontextprotocol/ext-apps\";\nimport type { AdapterOptions, WidgetModule } from \"mountly/adapter\";\n\n/** The catalog-data constraint createRenderer infers against. */\ntype CatalogData = { components: Record<string, { props: unknown }> };\n\n/**\n * Route a generated UI's actions back to the MCP host. Called with the action\n * name, its (resolved) params, and the view-side `App` handle.\n */\nexport type ActionRouter = (\n name: string,\n params: Record<string, unknown> | undefined,\n mcp: App,\n) => void;\n\n/**\n * Default router: an `ask` action with a string `prompt` param sends a\n * follow-up user turn to the model via `App.sendMessage`. This is the agent\n * loop json-render alone cannot do — it has no concept of an MCP host.\n */\nexport const defaultActionRouter: ActionRouter = (name, params, mcp) => {\n if (name !== \"ask\") return;\n const prompt = params?.prompt;\n const text = typeof prompt === \"string\" ? prompt : \"\";\n if (text) {\n void mcp.sendMessage({ role: \"user\", content: [{ type: \"text\", text }] });\n }\n};\n\n/**\n * Identity helper that types a components map against a catalog (so it can be\n * defined once and reused by both `createGenerativeWidget` and `createRenderer`)\n * without losing per-component prop inference.\n *\n * ```ts\n * export const components = defineComponents(catalog, {\n * Metric: ({ element }) => <span>{element.props.value}</span>,\n * });\n * ```\n */\nexport function defineComponents<TDef extends SchemaDefinition, TCatalog extends CatalogData>(\n _catalog: Catalog<TDef, TCatalog>,\n components: ComponentMap<TCatalog[\"components\"]>,\n): ComponentMap<TCatalog[\"components\"]> {\n return components;\n}\n\nexport interface GenerativeWidgetOptions<\n TDef extends SchemaDefinition,\n TCatalog extends CatalogData,\n> extends AdapterOptions {\n /** The json-render catalog — the vocabulary the agent may compose. */\n catalog: Catalog<TDef, TCatalog>;\n /** Native component implementations for each catalog type. */\n components: ComponentMap<TCatalog[\"components\"]>;\n /**\n * How a generated UI's actions reach the agent. Defaults to\n * {@link defaultActionRouter} (`ask` → `App.sendMessage`).\n */\n onAction?: ActionRouter;\n}\n\ninterface ToolResultShape {\n structuredContent?: { spec?: Spec };\n}\n\n/**\n * Turn a json-render catalog + components into a mountly MCP Apps widget.\n *\n * The returned widget reads the spec from the tool result, renders it natively\n * (resolving `$state` bindings from `spec.state`), and routes the rendered UI's\n * actions back to the MCP host. One call replaces the manual\n * catalog→registry→renderer→state→action-bridge wiring.\n *\n * ```ts\n * const widget = createGenerativeWidget({ catalog, components, styles, shadow: true });\n * (globalThis as { __mountlyMcpWidget__?: unknown }).__mountlyMcpWidget__ = widget;\n * ```\n */\nexport function createGenerativeWidget<TDef extends SchemaDefinition, TCatalog extends CatalogData>(\n options: GenerativeWidgetOptions<TDef, TCatalog>,\n): WidgetModule {\n const { catalog, components, onAction = defaultActionRouter, ...adapter } = options;\n const Rendered = createRenderer(catalog, components);\n\n function GenerativeView() {\n const mcp = useMcpHost();\n const result = useToolResult<ToolResultShape>();\n const spec = result?.structuredContent?.spec ?? null;\n if (!spec) return null;\n return createElement(Rendered, {\n spec,\n state: spec.state,\n onAction: (name: string, params?: Record<string, unknown>) => onAction(name, params, mcp),\n });\n }\n\n return createMcpWidget(GenerativeView, adapter);\n}\n","export {\n type ActionRouter,\n type GenerativeWidgetOptions,\n createGenerativeWidget,\n defaultActionRouter,\n defineComponents,\n} from \"./widget.js\";\nexport {\n type JsonRenderToolOptions,\n type RegisterJsonRenderResourceOptions,\n type RegisterJsonRenderToolOptions,\n type CreateJsonRenderMcpAppOptions,\n createJsonRenderMcpApp,\n registerJsonRenderTool,\n registerJsonRenderResource,\n} from \"./mcp.js\";\nexport {\n type BuildAppHtmlOptions,\n type UseJsonRenderAppOptions,\n type UseJsonRenderAppReturn,\n buildAppHtml,\n useJsonRenderApp,\n} from \"./app.js\";\n\n// Convenience re-exports for rendering the same catalog natively (non-MCP) —\n// previews, tests, or a plain mountly feature. Pair with `mountly-react`'s\n// `createWidget` to mount a spec outside an MCP host.\nexport { createRenderer } from \"@json-render/react\";\nexport type { ComponentMap, ComponentRenderProps } from \"@json-render/react\";\n\n// Streaming primitives (json-render's own): replay a known spec as a patch\n// stream, compile patches back into a progressively-built spec, and the AI-SDK\n// transform that classifies a model's UI-message stream into spec patches.\nexport {\n compileSpecStream,\n compileTextStreamToSpecs,\n createJsonRenderTransform,\n createSpecStreamCompiler,\n parseSpecStreamLine,\n specToPatchLines,\n} from \"./spec-stream.js\";\nexport type { Spec } from \"@json-render/core\";\n\n// --- Two ways to stream ---\n//\n// Replay (known spec): `useSpecStream` takes a finished spec — e.g. the one an\n// MCP tool delivered as `structuredContent` — and replays it as a live build,\n// with the compiler, loading flag, and cancellation included.\nexport {\n type SpecStreamState,\n type UseSpecStreamOptions,\n useSpecStream,\n} from \"./use-spec-stream.js\";\n\n// Live (from a model): `useUIStream` / `useChatUI` are json-render's own client\n// hooks — POST a prompt to an endpoint that streams JSONL patches (see\n// `streamSpec` in `mountly-mcp/json-render/server`) and render the spec as it\n// builds. Re-exported so the live path is one import away.\nexport { useChatUI, useUIStream } from \"@json-render/react\";\nexport type { UseUIStreamOptions, UseUIStreamReturn } from \"@json-render/react\";\n","import { useEffect, useState } from \"react\";\nimport type { Spec } from \"@json-render/core\";\nimport { createSpecStreamCompiler } from \"@json-render/core\";\nimport { specToPatchLines } from \"./spec-stream.js\";\n\nexport interface UseSpecStreamOptions {\n /** Delay between patches, ms. Default 90. Set 0 to render instantly. */\n intervalMs?: number;\n}\n\nexport interface SpecStreamState {\n /** The progressively-built spec (partial while streaming, full when done). */\n spec: Spec | null;\n /** Convenience: `spec?.state`, to pass straight to the renderer. */\n state: Record<string, unknown> | undefined;\n /** True while patches are still being applied. */\n loading: boolean;\n}\n\n/**\n * Replay a finished spec as a live json-render stream — and own all the\n * ceremony. Give it the target spec; it scaffolds a `createSpecStreamCompiler`,\n * replays the patches with a delay, exposes the partial spec + `loading` after\n * each step, and cancels cleanly when the target changes (so a self-driving UI\n * that swaps specs mid-stream just works). Re-streams whenever `target` changes.\n *\n * ```tsx\n * const { spec, state, loading } = useSpecStream(specs[view]);\n * return <Renderer spec={spec} state={state} loading={loading} onAction={...} />;\n * ```\n */\nexport function useSpecStream(\n target: Spec | null | undefined,\n options: UseSpecStreamOptions = {},\n): SpecStreamState {\n const { intervalMs = 90 } = options;\n const [spec, setSpec] = useState<Spec | null>(null);\n const [loading, setLoading] = useState(false);\n\n useEffect(() => {\n if (!target) {\n setSpec(null);\n setLoading(false);\n return;\n }\n let cancelled = false;\n const compiler = createSpecStreamCompiler<Spec>();\n const lines = specToPatchLines(target);\n setLoading(true);\n\n void (async () => {\n for (const line of lines) {\n if (cancelled) return;\n const { result } = compiler.push(`${line}\\n`);\n setSpec({ ...result });\n if (intervalMs > 0) {\n await new Promise((r) => setTimeout(r, intervalMs));\n }\n }\n if (cancelled) return;\n setSpec(compiler.getResult());\n setLoading(false);\n })();\n\n return () => {\n cancelled = true;\n };\n }, [target, intervalMs]);\n\n return { spec, state: spec?.state, loading };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,qBAAqB;AAC9B,SAA4B,sBAAsB;AAwB3C,IAAM,sBAAoC,CAAC,MAAM,QAAQ,QAAQ;AACtE,MAAI,SAAS,MAAO;AACpB,QAAM,SAAS,QAAQ;AACvB,QAAM,OAAO,OAAO,WAAW,WAAW,SAAS;AACnD,MAAI,MAAM;AACR,SAAK,IAAI,YAAY,EAAE,MAAM,QAAQ,SAAS,CAAC,EAAE,MAAM,QAAQ,KAAK,CAAC,EAAE,CAAC;AAAA,EAC1E;AACF;AAaO,SAAS,iBACd,UACA,YACsC;AACtC,SAAO;AACT;AAkCO,SAAS,uBACd,SACc;AACd,QAAM,EAAE,SAAS,YAAY,WAAW,qBAAqB,GAAG,QAAQ,IAAI;AAC5E,QAAM,WAAW,eAAe,SAAS,UAAU;AAEnD,WAAS,iBAAiB;AACxB,UAAM,MAAM,WAAW;AACvB,UAAM,SAAS,cAA+B;AAC9C,UAAM,OAAO,QAAQ,mBAAmB,QAAQ;AAChD,QAAI,CAAC,KAAM,QAAO;AAClB,WAAO,cAAc,UAAU;AAAA,MAC7B;AAAA,MACA,OAAO,KAAK;AAAA,MACZ,UAAU,CAAC,MAAc,WAAqC,SAAS,MAAM,QAAQ,GAAG;AAAA,IAC1F,CAAC;AAAA,EACH;AAEA,SAAO,gBAAgB,gBAAgB,OAAO;AAChD;;;AC5EA,SAAS,kBAAAA,uBAAsB;;;AC3B/B,SAAS,WAAW,gBAAgB;AAEpC,SAAS,4BAAAC,iCAAgC;AA6BlC,SAAS,cACd,QACA,UAAgC,CAAC,GAChB;AACjB,QAAM,EAAE,aAAa,GAAG,IAAI;AAC5B,QAAM,CAAC,MAAM,OAAO,IAAI,SAAsB,IAAI;AAClD,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAE5C,YAAU,MAAM;AACd,QAAI,CAAC,QAAQ;AACX,cAAQ,IAAI;AACZ,iBAAW,KAAK;AAChB;AAAA,IACF;AACA,QAAI,YAAY;AAChB,UAAM,WAAWC,0BAA+B;AAChD,UAAM,QAAQ,iBAAiB,MAAM;AACrC,eAAW,IAAI;AAEf,UAAM,YAAY;AAChB,iBAAW,QAAQ,OAAO;AACxB,YAAI,UAAW;AACf,cAAM,EAAE,OAAO,IAAI,SAAS,KAAK,GAAG,IAAI;AAAA,CAAI;AAC5C,gBAAQ,EAAE,GAAG,OAAO,CAAC;AACrB,YAAI,aAAa,GAAG;AAClB,gBAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,UAAU,CAAC;AAAA,QACpD;AAAA,MACF;AACA,UAAI,UAAW;AACf,cAAQ,SAAS,UAAU,CAAC;AAC5B,iBAAW,KAAK;AAAA,IAClB,GAAG;AAEH,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,QAAQ,UAAU,CAAC;AAEvB,SAAO,EAAE,MAAM,OAAO,MAAM,OAAO,QAAQ;AAC7C;;;ADZA,SAAS,WAAW,mBAAmB;","names":["createRenderer","createSpecStreamCompiler","createSpecStreamCompiler"]}
1
+ {"version":3,"sources":["../../src/json-render/generative-view.tsx","../../src/json-render/index.ts","../../src/json-render/use-spec-stream.ts"],"sourcesContent":["import { createElement } from \"react\";\nimport { type ComponentMap, createRenderer } from \"@json-render/react\";\nimport type { Catalog, SchemaDefinition, Spec } from \"@json-render/core\";\nimport { createMcpView, useMcpApp, useToolResult } from \"../react/index.js\";\nimport type { App } from \"@modelcontextprotocol/ext-apps\";\nimport type { AdapterOptions } from \"mountly/adapter\";\nimport type { McpView } from \"../types.js\";\n\n/** The catalog-data constraint createRenderer infers against. */\ntype CatalogData = { components: Record<string, { props: unknown }> };\n\n/**\n * Route a generated UI's actions back to the MCP host. Called with the action\n * name, its (resolved) params, and the view-side `App` handle.\n */\nexport type ActionRouter = (\n name: string,\n params: Record<string, unknown> | undefined,\n mcp: App,\n) => void;\n\n/**\n * Default router: an `ask` action with a string `prompt` param sends a\n * follow-up user turn to the model via `App.sendMessage`. This is the agent\n * loop json-render alone cannot do — it has no concept of an MCP host.\n */\nexport const defaultActionRouter: ActionRouter = (name, params, mcp) => {\n if (name !== \"ask\") return;\n const prompt = params?.prompt;\n const text = typeof prompt === \"string\" ? prompt : \"\";\n if (text) {\n void mcp.sendMessage({ role: \"user\", content: [{ type: \"text\", text }] });\n }\n};\n\n/**\n * Identity helper that types a components map against a catalog (so it can be\n * defined once and reused by both `createGenerativeView` and `createRenderer`)\n * without losing per-component prop inference.\n *\n * ```ts\n * export const components = defineComponents(catalog, {\n * Metric: ({ element }) => <span>{element.props.value}</span>,\n * });\n * ```\n */\nexport function defineComponents<TDef extends SchemaDefinition, TCatalog extends CatalogData>(\n _catalog: Catalog<TDef, TCatalog>,\n components: ComponentMap<TCatalog[\"components\"]>,\n): ComponentMap<TCatalog[\"components\"]> {\n return components;\n}\n\nexport interface GenerativeViewOptions<\n TDef extends SchemaDefinition,\n TCatalog extends CatalogData,\n> extends AdapterOptions {\n /** The json-render catalog — the vocabulary the agent may compose. */\n catalog: Catalog<TDef, TCatalog>;\n /** Native component implementations for each catalog type. */\n components: ComponentMap<TCatalog[\"components\"]>;\n /**\n * How a generated UI's actions reach the agent. Defaults to\n * {@link defaultActionRouter} (`ask` → `App.sendMessage`).\n */\n onAction?: ActionRouter;\n}\n\ninterface ToolResultShape {\n structuredContent?: { spec?: Spec };\n}\n\n/**\n * Turn a json-render catalog + components into a mountly MCP Apps View.\n *\n * Reads the spec from the tool result, renders it natively (resolving `$state`\n * from `spec.state`), and routes actions back to the MCP host.\n *\n * ```ts\n * createGenerativeView({ catalog, components, styles, shadow: true });\n * ```\n */\nexport function createGenerativeView<TDef extends SchemaDefinition, TCatalog extends CatalogData>(\n options: GenerativeViewOptions<TDef, TCatalog>,\n): McpView {\n const { catalog, components, onAction = defaultActionRouter, ...adapter } = options;\n const Rendered = createRenderer(catalog, components);\n\n function GenerativeView() {\n const mcp = useMcpApp();\n const result = useToolResult<ToolResultShape>();\n const spec = result?.structuredContent?.spec ?? null;\n if (!spec) return null;\n return createElement(Rendered, {\n spec,\n state: spec.state,\n onAction: (name: string, params?: Record<string, unknown>) => onAction(name, params, mcp),\n });\n }\n\n return createMcpView(GenerativeView, adapter);\n}\n","export {\n type ActionRouter,\n type GenerativeViewOptions,\n createGenerativeView,\n defaultActionRouter,\n defineComponents,\n} from \"./generative-view.js\";\nexport {\n type JsonRenderToolOptions,\n type RegisterJsonRenderResourceOptions,\n type RegisterJsonRenderToolOptions,\n type CreateJsonRenderMcpAppOptions,\n createJsonRenderMcpApp,\n registerJsonRenderTool,\n registerJsonRenderResource,\n} from \"./mcp.js\";\nexport {\n type BuildAppHtmlOptions,\n type UseJsonRenderAppOptions,\n type UseJsonRenderAppReturn,\n buildAppHtml,\n useJsonRenderApp,\n} from \"./app.js\";\n\n// Convenience re-exports for rendering the same catalog natively (non-MCP) —\n// previews, tests, or a plain mountly feature. Pair with `mountly-react`'s\n// `createWidget` to mount a spec outside an MCP host.\nexport { createRenderer } from \"@json-render/react\";\nexport type { ComponentMap, ComponentRenderProps } from \"@json-render/react\";\n\n// Streaming primitives (json-render's own): replay a known spec as a patch\n// stream, compile patches back into a progressively-built spec, and the AI-SDK\n// transform that classifies a model's UI-message stream into spec patches.\nexport {\n compileSpecStream,\n compileTextStreamToSpecs,\n createJsonRenderTransform,\n createSpecStreamCompiler,\n parseSpecStreamLine,\n specToPatchLines,\n} from \"./spec-stream.js\";\nexport type { Spec } from \"@json-render/core\";\n\n// --- Two ways to stream ---\n//\n// Replay (known spec): `useSpecStream` takes a finished spec — e.g. the one an\n// MCP tool delivered as `structuredContent` — and replays it as a live build,\n// with the compiler, loading flag, and cancellation included.\nexport {\n type SpecStreamState,\n type UseSpecStreamOptions,\n useSpecStream,\n} from \"./use-spec-stream.js\";\n\n// Live (from a model): `useUIStream` / `useChatUI` are json-render's own client\n// hooks — POST a prompt to an endpoint that streams JSONL patches (see\n// `streamSpec` in `mountly-mcp/json-render/server`) and render the spec as it\n// builds. Re-exported so the live path is one import away.\nexport { useChatUI, useUIStream } from \"@json-render/react\";\nexport type { UseUIStreamOptions, UseUIStreamReturn } from \"@json-render/react\";\n","import { useEffect, useState } from \"react\";\nimport type { Spec } from \"@json-render/core\";\nimport { createSpecStreamCompiler } from \"@json-render/core\";\nimport { specToPatchLines } from \"./spec-stream.js\";\n\nexport interface UseSpecStreamOptions {\n /** Delay between patches, ms. Default 90. Set 0 to render instantly. */\n intervalMs?: number;\n}\n\nexport interface SpecStreamState {\n /** The progressively-built spec (partial while streaming, full when done). */\n spec: Spec | null;\n /** Convenience: `spec?.state`, to pass straight to the renderer. */\n state: Record<string, unknown> | undefined;\n /** True while patches are still being applied. */\n loading: boolean;\n}\n\n/**\n * Replay a finished spec as a live json-render stream — and own all the\n * ceremony. Give it the target spec; it scaffolds a `createSpecStreamCompiler`,\n * replays the patches with a delay, exposes the partial spec + `loading` after\n * each step, and cancels cleanly when the target changes (so a self-driving UI\n * that swaps specs mid-stream just works). Re-streams whenever `target` changes.\n *\n * ```tsx\n * const { spec, state, loading } = useSpecStream(specs[view]);\n * return <Renderer spec={spec} state={state} loading={loading} onAction={...} />;\n * ```\n */\nexport function useSpecStream(\n target: Spec | null | undefined,\n options: UseSpecStreamOptions = {},\n): SpecStreamState {\n const { intervalMs = 90 } = options;\n const [spec, setSpec] = useState<Spec | null>(null);\n const [loading, setLoading] = useState(false);\n\n useEffect(() => {\n if (!target) {\n setSpec(null);\n setLoading(false);\n return;\n }\n let cancelled = false;\n const compiler = createSpecStreamCompiler<Spec>();\n const lines = specToPatchLines(target);\n setLoading(true);\n\n void (async () => {\n for (const line of lines) {\n if (cancelled) return;\n const { result } = compiler.push(`${line}\\n`);\n setSpec({ ...result });\n if (intervalMs > 0) {\n await new Promise((r) => setTimeout(r, intervalMs));\n }\n }\n if (cancelled) return;\n setSpec(compiler.getResult());\n setLoading(false);\n })();\n\n return () => {\n cancelled = true;\n };\n }, [target, intervalMs]);\n\n return { spec, state: spec?.state, loading };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,qBAAqB;AAC9B,SAA4B,sBAAsB;AAyB3C,IAAM,sBAAoC,CAAC,MAAM,QAAQ,QAAQ;AACtE,MAAI,SAAS,MAAO;AACpB,QAAM,SAAS,QAAQ;AACvB,QAAM,OAAO,OAAO,WAAW,WAAW,SAAS;AACnD,MAAI,MAAM;AACR,SAAK,IAAI,YAAY,EAAE,MAAM,QAAQ,SAAS,CAAC,EAAE,MAAM,QAAQ,KAAK,CAAC,EAAE,CAAC;AAAA,EAC1E;AACF;AAaO,SAAS,iBACd,UACA,YACsC;AACtC,SAAO;AACT;AA+BO,SAAS,qBACd,SACS;AACT,QAAM,EAAE,SAAS,YAAY,WAAW,qBAAqB,GAAG,QAAQ,IAAI;AAC5E,QAAM,WAAW,eAAe,SAAS,UAAU;AAEnD,WAAS,iBAAiB;AACxB,UAAM,MAAM,UAAU;AACtB,UAAM,SAAS,cAA+B;AAC9C,UAAM,OAAO,QAAQ,mBAAmB,QAAQ;AAChD,QAAI,CAAC,KAAM,QAAO;AAClB,WAAO,cAAc,UAAU;AAAA,MAC7B;AAAA,MACA,OAAO,KAAK;AAAA,MACZ,UAAU,CAAC,MAAc,WAAqC,SAAS,MAAM,QAAQ,GAAG;AAAA,IAC1F,CAAC;AAAA,EACH;AAEA,SAAO,cAAc,gBAAgB,OAAO;AAC9C;;;AC1EA,SAAS,kBAAAA,uBAAsB;;;AC3B/B,SAAS,WAAW,gBAAgB;AAEpC,SAAS,4BAAAC,iCAAgC;AA6BlC,SAAS,cACd,QACA,UAAgC,CAAC,GAChB;AACjB,QAAM,EAAE,aAAa,GAAG,IAAI;AAC5B,QAAM,CAAC,MAAM,OAAO,IAAI,SAAsB,IAAI;AAClD,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAE5C,YAAU,MAAM;AACd,QAAI,CAAC,QAAQ;AACX,cAAQ,IAAI;AACZ,iBAAW,KAAK;AAChB;AAAA,IACF;AACA,QAAI,YAAY;AAChB,UAAM,WAAWC,0BAA+B;AAChD,UAAM,QAAQ,iBAAiB,MAAM;AACrC,eAAW,IAAI;AAEf,UAAM,YAAY;AAChB,iBAAW,QAAQ,OAAO;AACxB,YAAI,UAAW;AACf,cAAM,EAAE,OAAO,IAAI,SAAS,KAAK,GAAG,IAAI;AAAA,CAAI;AAC5C,gBAAQ,EAAE,GAAG,OAAO,CAAC;AACrB,YAAI,aAAa,GAAG;AAClB,gBAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,UAAU,CAAC;AAAA,QACpD;AAAA,MACF;AACA,UAAI,UAAW;AACf,cAAQ,SAAS,UAAU,CAAC;AAC5B,iBAAW,KAAK;AAAA,IAClB,GAAG;AAEH,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,QAAQ,UAAU,CAAC;AAEvB,SAAO,EAAE,MAAM,OAAO,MAAM,OAAO,QAAQ;AAC7C;;;ADZA,SAAS,WAAW,mBAAmB;","names":["createRenderer","createSpecStreamCompiler","createSpecStreamCompiler"]}
@@ -0,0 +1,10 @@
1
+ import type { McpView } from "./types.js";
2
+ /** Internal bridge key — prefer {@link publishMcpView} / `createMcpView`. */
3
+ export declare const MCP_VIEW_GLOBAL_KEY: "__mountlyMcpView__";
4
+ /**
5
+ * Publish a View module for the iframe bridge. Call once from your View entry
6
+ * (or rely on `createMcpView`, which does this for you).
7
+ */
8
+ export declare function publishMcpView(view: McpView): McpView;
9
+ /** Read the published View module, if any. */
10
+ export declare function getPublishedMcpView(): McpView | undefined;
@@ -1,6 +1,6 @@
1
1
  import type { App, McpUiHostContext, McpUiToolInputNotification, McpUiToolInputPartialNotification, McpUiToolResultNotification } from "@modelcontextprotocol/ext-apps";
2
2
  /**
3
- * Shared per-widget context. The bridge populates this on each mount/update,
3
+ * Shared per-View context. The bridge populates this on each mount/update,
4
4
  * so consumers can synchronously read the initial values (no event subscription
5
5
  * race) and the React tree re-renders when the bridge calls `update()`.
6
6
  */
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Wraps a React component as an MCP Apps View and publishes it for the bridge.
3
+ * One call is enough — no manual global assign.
4
+ */
5
+ import { type ComponentType } from "react";
6
+ import type { AdapterOptions } from "mountly/adapter";
7
+ import type { McpView } from "../types.js";
8
+ export declare function createMcpView<P extends object>(Component: ComponentType<P>, options?: AdapterOptions): McpView;
@@ -1,7 +1,7 @@
1
1
  import { useAutoResize as useAutoResizeBase } from "@modelcontextprotocol/ext-apps/react";
2
2
  import type { App, McpUiDisplayMode, McpUiHostContext, McpUiUpdateModelContextRequest } from "@modelcontextprotocol/ext-apps";
3
3
  /** The view-side ext-apps App. Use this to call server tools, send messages, open links, etc. */
4
- export declare function useMcpHost(): App;
4
+ export declare function useMcpApp(): App;
5
5
  /** Complete notification params; read the tool arguments from `.arguments`. */
6
6
  export declare function useToolInput<T = unknown>(): T | undefined;
7
7
  /**
@@ -1,3 +1,3 @@
1
1
  export * from "./context.js";
2
2
  export * from "./hooks.js";
3
- export * from "./create-widget.js";
3
+ export * from "./create-view.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  McpContext,
3
- createMcpWidget,
3
+ createMcpView,
4
4
  useAutoResize,
5
5
  useDisplayMode,
6
6
  useDocumentTheme,
@@ -8,16 +8,17 @@ import {
8
8
  useHostFonts,
9
9
  useHostStyleVariables,
10
10
  useHostStyles,
11
- useMcpHost,
11
+ useMcpApp,
12
12
  useRequestDisplayMode,
13
13
  useToolInput,
14
14
  useToolInputPartial,
15
15
  useToolResult,
16
16
  useUpdateModelContext
17
- } from "../chunk-NLQDVNZP.js";
17
+ } from "../chunk-AEVQKFXX.js";
18
+ import "../chunk-SADNMGP6.js";
18
19
  export {
19
20
  McpContext,
20
- createMcpWidget,
21
+ createMcpView,
21
22
  useAutoResize,
22
23
  useDisplayMode,
23
24
  useDocumentTheme,
@@ -25,7 +26,7 @@ export {
25
26
  useHostFonts,
26
27
  useHostStyleVariables,
27
28
  useHostStyles,
28
- useMcpHost,
29
+ useMcpApp,
29
30
  useRequestDisplayMode,
30
31
  useToolInput,
31
32
  useToolInputPartial,
package/dist/schema.d.ts CHANGED
@@ -8,13 +8,13 @@ export declare const MCP_APPS_URI_SCHEME: "ui://";
8
8
  /**
9
9
  * Implementation-specific error codes for mountly-mcp's host/bridge layer.
10
10
  * The spec doesn't standardize these; they appear in `notifications/message`
11
- * params so hosts can categorize widget failures.
11
+ * params so hosts can categorize View failures.
12
12
  */
13
13
  export declare const MCP_ERROR_CODES: {
14
14
  readonly INITIALIZE_FAILED: "mountly-mcp/initialize-failed";
15
15
  readonly INITIALIZE_TIMEOUT: "mountly-mcp/initialize-timeout";
16
16
  readonly TOOL_CALL_FAILED: "mountly-mcp/tool-call-failed";
17
- readonly WIDGET_MOUNT_THREW: "mountly-mcp/widget-mount-threw";
17
+ readonly VIEW_MOUNT_THREW: "mountly-mcp/view-mount-threw";
18
18
  readonly INVALID_ORIGIN: "mountly-mcp/invalid-origin";
19
19
  };
20
20
  export type McpErrorCode = (typeof MCP_ERROR_CODES)[keyof typeof MCP_ERROR_CODES];
@@ -4,7 +4,7 @@ import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
4
4
  import { EXTENSION_ID, RESOURCE_MIME_TYPE, type McpUiAppToolConfig } from "@modelcontextprotocol/ext-apps/server";
5
5
  import type { McpUiToolVisibility } from "@modelcontextprotocol/ext-apps";
6
6
  import { type McpAppArtifact } from "../artifact/index.js";
7
- export interface McpWidgetToolResult {
7
+ export interface McpAppToolResult {
8
8
  structuredContent?: unknown;
9
9
  content?: ReadonlyArray<{
10
10
  type: string;
@@ -33,7 +33,7 @@ export interface McpAppToolRegistration<Args = unknown> {
33
33
  config?: McpAppToolConfig;
34
34
  /** Convenience override for `_meta.ui.visibility`. */
35
35
  visibility?: McpUiToolVisibility | ReadonlyArray<McpUiToolVisibility>;
36
- handler: (args: Args, extra?: unknown) => Promise<McpWidgetToolResult>;
36
+ handler: (args: Args, extra?: unknown) => Promise<McpAppToolResult>;
37
37
  }
38
38
  export interface RegisterMcpAppsOptions {
39
39
  views: ReadonlyArray<McpAppViewRegistration>;
@@ -1 +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 McpWidgetToolResult {\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<McpWidgetToolResult>;\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: McpWidgetToolResult): McpWidgetToolResult {\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":";;;;;AAAA,SAAS,gBAAgB;AACzB,SAAS,cAAc;AACvB,SAAS,iBAAiB;AAC1B,SAAS,yBAAyB;AAElC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAEP,SAAS,SAAS;AAsElB,SAAS,oBACP,YACmC;AACnC,MAAI,eAAe,OAAW,QAAO;AACrC,SAAO,OAAO,eAAe,WAAW,CAAC,UAAU,IAAI,CAAC,GAAG,UAAU;AACvE;AAEA,SAAS,qBAAqB,OAAyB;AACrD,SAAO,OAAO,UAAU,YAAY,UAAU,SAAS,eAAe,SAAS,UAAU;AAC3F;AAGA,SAAS,oBAAoB,QAAgD;AAC3E,MAAI,CAAC,UAAU,OAAO,KAAK,MAAM,EAAE,WAAW,EAAG,QAAO;AACxD,QAAM,YAAY;AAClB,MAAI,qBAAqB,SAAS,KAAK,OAAO,OAAO,SAAS,EAAE,KAAK,oBAAoB,GAAG;AAC1F,WAAO;AAAA,EACT;AACA,QAAM,iBAAiB;AAAA,IACrB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,SAAO,eAAe,KAAK,CAAC,QAAQ,OAAO,SAAS,IAChD,EAAE,eAAe,MAAe,IAChC;AACN;AAEA,SAAS,iBAAiB,QAAkD;AAC1E,MAAI,OAAO,WAAW,OAAO,QAAQ,SAAS,EAAG,QAAO;AACxD,MAAI,OAAO,sBAAsB,OAAW,QAAO;AACnD,SAAO;AAAA,IACL,GAAG;AAAA,IACH,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,OAAO,iBAAiB,EAAE,CAAC;AAAA,EAC5E;AACF;AAEA,SAAS,kBAAkB,MAAwD;AACjF,QAAM,OAAO,EAAE,GAAG,KAAK;AACvB,SAAO,KAAK;AACZ,SAAO,KAAK,gBAAgB;AAC5B,SAAO;AACT;AAEA,SAAS,mBAAmB,MAI1B;AACA,QAAM,OAAO,EAAE,GAAI,KAAK,QAAQ,SAAS,CAAC,EAAG;AAC7C,QAAM,aACJ,OAAO,KAAK,OAAO,YAAY,KAAK,OAAO,OAAQ,KAAK,KAAgC,CAAC;AAC3F,QAAM,aAAa,oBAAoB,KAAK,UAAU,KAAK;AAAA,IACzD,GAAI,WAAW,cAAc,CAAC,SAAS,KAAK;AAAA,EAC9C;AACA,QAAM,OAAO;AAAA,IACX,GAAG;AAAA,IACH,IAAI,EAAE,GAAG,YAAY,aAAa,KAAK,aAAa,WAAW;AAAA,EACjE;AACA,SAAO,EAAE,MAAM,WAAW,kBAAkB,IAAI,GAAG,WAAW;AAChE;AAEA,eAAe,UACb,eACoC;AACpC,QAAM,SAAuB,CAAC;AAC9B,QAAM,QAAQ,oBAAI,IAAY;AAC9B,QAAM,OAAO,oBAAI,IAAY;AAC7B,aAAW,gBAAgB,eAAe;AACxC,QAAI,aAAa,QAAQ,UAAa,CAAC,aAAa,IAAI,WAAW,OAAO,GAAG;AAC3E,YAAM,IAAI;AAAA,QACR,+EAA+E,aAAa,GAAG;AAAA,MACjG;AAAA,IACF;AACA,UAAM,WAAW,OAAO,aAAa,aAAa,WAAW,SAAY,aAAa;AACtF,UAAM,WAAW,MAAM;AAAA,MACrB,OAAO,aAAa,aAAa,WAC7B,aAAa,WACb,aAAa,SAAS;AAAA,MAC1B,aAAa,OAAO,UAAU;AAAA,IAChC;AACA,QAAI,YAAY,SAAS,SAAS,SAAS,MAAM;AAC/C,YAAM,IAAI;AAAA,QACR,wCAAwC,SAAS,IAAI,wCAAwC,SAAS,IAAI;AAAA,MAC5G;AAAA,IACF;AACA,QAAI,MAAM,IAAI,SAAS,IAAI;AACzB,YAAM,IAAI,MAAM,oCAAoC,SAAS,IAAI,GAAG;AACtE,QAAI,KAAK,IAAI,SAAS,GAAG,EAAG,OAAM,IAAI,MAAM,mCAAmC,SAAS,GAAG,GAAG;AAC9F,UAAM,IAAI,SAAS,IAAI;AACvB,SAAK,IAAI,SAAS,GAAG;AACrB,WAAO,KAAK,EAAE,UAAU,MAAM,MAAM,SAAS,SAAS,UAAU,MAAM,EAAE,CAAC;AAAA,EAC3E;AACA,SAAO;AACT;AAOA,eAAsB,gBACpB,QACA,SAC4B;AAC5B,MAAI,OAAO,YAAY,GAAG;AACxB,UAAM,IAAI,MAAM,oEAAoE;AAAA,EACtF;AACA,MAAI,QAAQ,MAAM,WAAW,GAAG;AAC9B,UAAM,IAAI,MAAM,2DAA2D;AAAA,EAC7E;AACA,QAAM,QAAQ,MAAM,UAAU,QAAQ,KAAK;AAC3C,QAAM,QAAQ,IAAI,IAAI,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,SAAS,KAAK,IAAI,CAAC,CAAC;AACpE,aAAW,QAAQ,QAAQ,SAAS,CAAC,GAAG;AACtC,QAAI,CAAC,MAAM,IAAI,KAAK,WAAW,GAAG;AAChC,YAAM,IAAI;AAAA,QACR,sBAAsB,KAAK,IAAI,iCAAiC,KAAK,WAAW;AAAA,MAClF;AAAA,IACF;AAAA,EACF;AAEA,SAAO,OAAO,qBAAqB;AAAA,IACjC,YAAY,EAAE,CAAC,YAAY,GAAG,EAAE,WAAW,CAAC,kBAAkB,EAAE,EAAE;AAAA,EACpE,CAAU;AAEV,QAAM,YAAY,MAAM;AAAA,IAAI,CAAC,EAAE,UAAU,KAAK,MAC5C;AAAA,MACE;AAAA,MACA,SAAS;AAAA,MACT,SAAS;AAAA,MACT;AAAA,QACE,UAAU,SAAS,YAAY;AAAA,QAC/B,aAAa,SAAS,YAAY;AAAA,QAClC,OAAO,EAAE,IAAI,SAAS,YAAY,MAAM,GAAG;AAAA,MAC7C;AAAA,MACA,aAAa;AAAA,QACX,UAAU;AAAA,UACR;AAAA,YACE,KAAK,SAAS;AAAA,YACd,UAAU,SAAS,YAAY;AAAA,YAC/B,MAAM;AAAA,YACN,OAAO,EAAE,IAAI,SAAS,YAAY,MAAM,GAAG;AAAA,UAC7C;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,SAA0B,QAAQ,SAAS,CAAC,GAAG,IAAI,CAAC,iBAAiB;AACzE,UAAM,EAAE,MAAM,WAAW,WAAW,IAAI,mBAAmB,YAAY;AACvE,UAAM,SAAS,aAAa,UAAU,CAAC;AACvC,UAAM,aAAa;AAAA,MACjB;AAAA,MACA,aAAa;AAAA,MACb;AAAA,QACE,GAAG;AAAA,QACH,aAAa,oBAAoB,OAAO,WAAW;AAAA,QACnD,cAAc,oBAAoB,OAAO,YAAY;AAAA,QACrD,OAAO;AAAA,MACT;AAAA,OACC,OAAO,MAAe,UACrB,iBAAiB,MAAM,aAAa,QAAQ,MAAM,KAAK,CAAC;AAAA,IAC5D;AACA,WAAO,EAAE,cAAc,YAAY,mBAAmB,WAAW,WAAW;AAAA,EAC9E,CAAC;AAED,QAAM,sBAAsB,OAAO,OAAO;AAC1C,QAAM,oBAAoB,MAAM;AAC9B,0BAAsB;AACtB,UAAM,KAAK,gBAAgB,OAAO,OAAO,sBAAsB,CAAC;AAChE,QAAI,IAAI,WAAW,SAAS,kBAAkB,EAAG;AACjD,eAAW,YAAY,UAAW,UAAS,OAAO;AAClD,eAAW,QAAQ,OAAO;AACxB,UAAI,KAAK,WAAW,SAAS,OAAO,GAAG;AACrC,aAAK,WAAW,OAAO,EAAE,OAAO,KAAK,kBAAkB,CAAC;AAAA,MAC1D,OAAO;AACL,aAAK,WAAW,OAAO;AAAA,MACzB;AAAA,IACF;AAAA,EACF;AACA,SAAO,OAAO,gBAAgB;AAE9B,SAAO;AAAA,IACL,WAAW,MAAM,IAAI,CAAC,SAAS,KAAK,QAAQ;AAAA,IAC5C,SAAS;AACP,iBAAW,YAAY,UAAW,UAAS,OAAO;AAClD,iBAAW,QAAQ,MAAO,MAAK,WAAW,OAAO;AACjD,UAAI,OAAO,OAAO,kBAAkB,mBAAmB;AACrD,eAAO,OAAO,gBAAgB;AAAA,MAChC;AAAA,IACF;AAAA,EACF;AACF;AAQA,SAAS,uBAAuB,SAA+C;AAC7E,SAAO,IAAI;AAAA,IACT,EAAE,MAAM,QAAQ,MAAM,SAAS,QAAQ,QAAQ;AAAA,IAC/C,EAAE,cAAc,EAAE,WAAW,CAAC,GAAG,OAAO,CAAC,EAAE,EAAE;AAAA,EAC/C;AACF;AAGO,SAAS,mBAAmB,SAAyD;AAC1F,MAAI;AACJ,iBAAe,QAA4B;AACzC,UAAM,OAAO,uBAAuB,OAAO;AAC3C,UAAM,gBAAgB,MAAM,EAAE,OAAO,QAAQ,OAAO,OAAO,QAAQ,MAAM,CAAC;AAC1E,aAAS;AACT,WAAO;AAAA,EACT;AACA,SAAO;AAAA,IACL,MAAM,iBAAiB,gBAAgB;AACrC,YAAM,OAAO,MAAM,MAAM;AACzB,YAAM,CAAC,iBAAiB,eAAe,IAAI,kBAAkB,iBAAiB;AAC9E,YAAM,KAAK,QAAQ,eAAe;AAClC,YAAM,SAAS,IAAI;AAAA,QACjB,EAAE,MAAM,GAAG,QAAQ,IAAI,qBAAqB,SAAS,QAAQ,QAAQ;AAAA,QACpE,gBAAgB,MAAM,OAClB;AAAA,UACC,cAAc;AAAA,YACZ,YAAY,EAAE,CAAC,YAAY,GAAG,EAAE,WAAW,CAAC,kBAAkB,EAAE,EAAE;AAAA,UACpE;AAAA,QACF,IACA;AAAA,MACN;AACA,YAAM,OAAO,QAAQ,eAAe;AACpC,aAAO,EAAE,OAAO;AAAA,IAClB;AAAA,IACA,MAAM,OAAO,WAAW;AACtB,YAAM,OAAO,MAAM,MAAM;AACzB,YAAM,KAAK,QAAQ,SAAS;AAAA,IAC9B;AAAA,IACA,MAAM,QAAQ;AACZ,YAAM,QAAQ,MAAM;AAAA,IACtB;AAAA,EACF;AACF;AAEA,eAAsB,WAAW,SAAkE;AACjG,QAAM,EAAE,qBAAqB,IAAI,MAAM,OAAO,2CAA2C;AACzF,QAAM,SAAS,mBAAmB,OAAO;AACzC,QAAM,OAAO,OAAO,IAAI,qBAAqB,CAAC;AAC9C,SAAO;AACT;","names":[]}
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":[]}
@@ -1,18 +1,17 @@
1
1
  /**
2
- * Svelte entry point: turn a Svelte component into an MCP App view.
2
+ * Svelte entry point: turn a Svelte component into an MCP App View.
3
3
  *
4
4
  * ```ts
5
- * import { createMcpWidget } from "mountly-mcp/svelte";
5
+ * import { createMcpView } from "mountly-mcp/svelte";
6
6
  * import Dashboard from "./Dashboard.svelte";
7
7
  *
8
- * const widget = createMcpWidget(Dashboard);
9
- * (globalThis as { __mountlyMcpWidget__?: unknown }).__mountlyMcpWidget__ = widget;
8
+ * createMcpView(Dashboard);
10
9
  * ```
11
10
  *
12
11
  * ```svelte
13
12
  * <script lang="ts">
14
- * import type { McpWidgetProps } from "mountly-mcp";
15
- * let { mcp, toolResult, hostContext }: McpWidgetProps = $props();
13
+ * import type { McpViewProps } from "mountly-mcp";
14
+ * let { mcp, toolResult, hostContext }: McpViewProps = $props();
16
15
  * </script>
17
16
  * ```
18
17
  *
@@ -23,13 +22,8 @@
23
22
  * them.
24
23
  */
25
24
  import { createWidget } from "mountly-svelte";
26
- import type { AdapterOptions, WidgetModule } from "mountly/adapter";
27
- import type { McpWidgetProps } from "../types.js";
28
- /**
29
- * Wraps a Svelte component as a mountly `WidgetModule` driven by the
30
- * mountly-mcp bridge. The component receives `mcp`, `toolInput`,
31
- * `toolInputPartial`, `toolResult` and `hostContext` as props, alongside any
32
- * pass-through props.
33
- */
34
- export declare function createMcpWidget<P extends object>(Component: Parameters<typeof createWidget<P & McpWidgetProps>>[0], options?: AdapterOptions): WidgetModule;
35
- export type { McpWidgetProps };
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 };
@@ -1,9 +1,13 @@
1
+ import {
2
+ publishMcpView
3
+ } from "../chunk-SADNMGP6.js";
4
+
1
5
  // src/svelte/index.ts
2
6
  import { createWidget } from "mountly-svelte";
3
- function createMcpWidget(Component, options) {
4
- return createWidget(Component, options);
7
+ function createMcpView(Component, options) {
8
+ return publishMcpView(createWidget(Component, options));
5
9
  }
6
10
  export {
7
- createMcpWidget
11
+ createMcpView
8
12
  };
9
13
  //# sourceMappingURL=index.js.map
@@ -1 +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 { createMcpWidget } from \"mountly-mcp/svelte\";\n * import Dashboard from \"./Dashboard.svelte\";\n *\n * const widget = createMcpWidget(Dashboard);\n * (globalThis as { __mountlyMcpWidget__?: unknown }).__mountlyMcpWidget__ = widget;\n * ```\n *\n * ```svelte\n * <script lang=\"ts\">\n * import type { McpWidgetProps } from \"mountly-mcp\";\n * let { mcp, toolResult, hostContext }: McpWidgetProps = $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, WidgetModule } from \"mountly/adapter\";\nimport type { McpWidgetProps } from \"../types.js\";\n\n/**\n * Wraps a Svelte component as a mountly `WidgetModule` driven by the\n * mountly-mcp bridge. The component receives `mcp`, `toolInput`,\n * `toolInputPartial`, `toolResult` and `hostContext` as props, alongside any\n * pass-through props.\n */\nexport function createMcpWidget<P extends object>(\n Component: Parameters<typeof createWidget<P & McpWidgetProps>>[0],\n options?: AdapterOptions,\n): WidgetModule {\n return createWidget<P & McpWidgetProps>(Component, options);\n}\n\nexport type { McpWidgetProps };\n"],"mappings":";AAwBA,SAAS,oBAAoB;AAUtB,SAAS,gBACd,WACA,SACc;AACd,SAAO,aAAiC,WAAW,OAAO;AAC5D;","names":[]}
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":[]}
@@ -5,7 +5,7 @@ export interface VerifyMcpAppsOptions {
5
5
  /** Transitional single-View inputs when no manifest exists. */
6
6
  htmlPaths?: ReadonlyArray<string>;
7
7
  /**
8
- * Load each View in a real browser and confirm the widget mounts with content.
8
+ * Load each View in a real browser and confirm the View mounts with content.
9
9
  *
10
10
  * Off by default: it needs Playwright and a Chromium download, which the
11
11
  * static checks do not. Turn it on in CI. Everything else here inspects
@@ -149,7 +149,7 @@ async function renderMcpAppArtifactWith(artifact, dependencies) {
149
149
  failures.push(
150
150
  error(
151
151
  "render/timeout",
152
- "View never mounted: the handshake or widget mount did not complete",
152
+ "View never mounted: the handshake or View mount did not complete",
153
153
  artifact.htmlPath
154
154
  )
155
155
  );
@@ -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 widget 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 widget 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"]}
package/dist/types.d.ts CHANGED
@@ -5,20 +5,20 @@
5
5
  * are the vocabulary users already learned, and every alias is one more thing
6
6
  * to translate and one more place to drift.
7
7
  *
8
- * Only genuinely mountly-specific types live here: the props a widget's
8
+ * Only genuinely mountly-specific types live here: the props a View's
9
9
  * `mount()` receives, and the sidecar declaration written next to the built HTML.
10
10
  */
11
11
  export type { App, McpUiAppCapabilities, McpUiDisplayMode, McpUiHostCapabilities, McpUiHostContext, McpUiHostContextChangedNotification, McpUiResourceCsp, McpUiResourceMeta, McpUiResourcePermissions, McpUiToolCancelledNotification, McpUiToolInputNotification, McpUiToolInputPartialNotification, McpUiToolMeta, McpUiToolResultNotification, McpUiToolVisibility, } from "@modelcontextprotocol/ext-apps";
12
12
  import type { App, McpUiDisplayMode, McpUiHostContext, McpUiResourceMeta, McpUiToolInputNotification, McpUiToolInputPartialNotification, McpUiToolResultNotification } from "@modelcontextprotocol/ext-apps";
13
13
  import type { MCP_APPS_MIME, MCP_APPS_PROTOCOL_VERSION } from "./schema.js";
14
14
  /**
15
- * Props passed to a `WidgetModule.mount(container, props)` call.
15
+ * Props passed to an `McpView.mount(container, props)` call.
16
16
  *
17
17
  * The bridge surfaces the App handle plus the latest tool input/result and a
18
- * snapshot of host context. Widgets receive new props through `update()` as
18
+ * snapshot of host context. Views receive new props through `update()` as
19
19
  * notifications arrive — see `runBridge` in `bridge/index.ts`.
20
20
  */
21
- export interface McpWidgetProps {
21
+ export interface McpViewProps {
22
22
  /** The view-side ext-apps App; exposes the full spec-compliant API surface. */
23
23
  mcp: App;
24
24
  /** Tool arguments (after `ui/notifications/tool-input`). */
@@ -53,3 +53,13 @@ export interface McpResourceDeclaration {
53
53
  ui: McpUiResourceMeta;
54
54
  };
55
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";
@@ -1,18 +1,18 @@
1
1
  import type { Plugin } from "vite";
2
2
  import type { BuildSelfContainedOptions } from "../build/index.js";
3
- export interface MountlyMcpWidgetOptions extends Omit<BuildSelfContainedOptions, "entry" | "output" | "cssEntry"> {
3
+ export interface MountlyMcpViewOptions extends Omit<BuildSelfContainedOptions, "entry" | "output" | "cssEntry"> {
4
4
  entry: string;
5
5
  output?: string;
6
6
  cleanIntermediate?: boolean;
7
7
  }
8
- export interface MountlyMcpWidgetsOptions {
9
- apps: ReadonlyArray<MountlyMcpWidgetOptions>;
8
+ export interface MountlyMcpViewsOptions {
9
+ apps: ReadonlyArray<MountlyMcpViewOptions>;
10
10
  /** Defaults to `<outDir>/mountly-mcp.manifest.json`; false disables it. */
11
11
  manifest?: string | false;
12
12
  }
13
- export type MountlyMcpViteOptions = MountlyMcpWidgetOptions | MountlyMcpWidgetsOptions;
13
+ export type MountlyMcpViteOptions = MountlyMcpViewOptions | MountlyMcpViewsOptions;
14
14
  export interface MountlyMcpViteApi {
15
- apps: ReadonlyArray<MountlyMcpWidgetOptions>;
15
+ apps: ReadonlyArray<MountlyMcpViewOptions>;
16
16
  manifest?: string | false;
17
17
  }
18
18
  /**
@@ -20,4 +20,4 @@ export interface MountlyMcpViteApi {
20
20
  * environment, so frameworks and CSS remain isolated while callers configure
21
21
  * the collection once.
22
22
  */
23
- export declare function mountlyMcpWidget(options: MountlyMcpViteOptions): Plugin;
23
+ export declare function mountlyMcpViews(options: MountlyMcpViteOptions): Plugin;
@@ -4,8 +4,8 @@ import {
4
4
  } from "../chunk-DKK26TRS.js";
5
5
  import {
6
6
  buildMcpResourceFromSource
7
- } from "../chunk-4WAKOYMK.js";
8
- import "../chunk-DQLQOMDM.js";
7
+ } from "../chunk-MTJDRWGD.js";
8
+ import "../chunk-M4WFJR3C.js";
9
9
  import "../chunk-YJA4H6IB.js";
10
10
 
11
11
  // src/vite/index.ts
@@ -22,7 +22,7 @@ function outputPath(root, outDir, app) {
22
22
  if (!app.output) return join(outDir, `${basename(app.name)}.html`);
23
23
  return isAbsolute(app.output) ? app.output : resolve(root, app.output);
24
24
  }
25
- function mountlyMcpWidget(options) {
25
+ function mountlyMcpViews(options) {
26
26
  const configuredApps = isCollection(options) ? [...options.apps] : [options];
27
27
  const selectedForDev = process.env.MOUNTLY_MCP_SELECTED_APP;
28
28
  const apps = selectedForDev ? configuredApps.filter((app) => app.name === selectedForDev) : configuredApps;
@@ -44,8 +44,8 @@ function mountlyMcpWidget(options) {
44
44
  const environments = apps.map((app, index) => ({
45
45
  app,
46
46
  name: safeBuildName(app.name, index),
47
- jsFile: `widget-${index}-${basename(app.name)}.js`,
48
- cssFile: `widget-${index}-${basename(app.name)}.css`
47
+ jsFile: `view-${index}-${basename(app.name)}.js`,
48
+ cssFile: `view-${index}-${basename(app.name)}.css`
49
49
  }));
50
50
  const byEnvironment = new Map(environments.map((environment) => [environment.name, environment]));
51
51
  const emitted = /* @__PURE__ */ new Map();
@@ -58,7 +58,7 @@ function mountlyMcpWidget(options) {
58
58
  return (name ? byEnvironment.get(name) : void 0) ?? (environments.length === 1 ? environments[0] : void 0);
59
59
  }
60
60
  const plugin = {
61
- name: "mountly-mcp-widget",
61
+ name: "mountly-mcp-views",
62
62
  api: {
63
63
  // The CLI consumes this documented plugin interface instead of scraping
64
64
  // Vite's resolved build settings.
@@ -206,6 +206,6 @@ function mountlyMcpWidget(options) {
206
206
  return plugin;
207
207
  }
208
208
  export {
209
- mountlyMcpWidget
209
+ mountlyMcpViews
210
210
  };
211
211
  //# sourceMappingURL=index.js.map