mountly-mcp 0.0.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +283 -20
  3. package/dist/artifact/index.d.ts +49 -0
  4. package/dist/artifact/index.js +21 -0
  5. package/dist/artifact/index.js.map +1 -0
  6. package/dist/bridge/iframe-entry.d.ts +1 -0
  7. package/dist/bridge/iframe-entry.js +270 -220
  8. package/dist/bridge/index.d.ts +20 -15
  9. package/dist/bridge/index.js +2 -2
  10. package/dist/build/emit-html.d.ts +30 -0
  11. package/dist/build/emit-meta.d.ts +17 -0
  12. package/dist/build/index.d.ts +33 -48
  13. package/dist/build/index.js +9 -135
  14. package/dist/build/index.js.map +1 -1
  15. package/dist/chunk-33DMFKKD.js +111 -0
  16. package/dist/chunk-33DMFKKD.js.map +1 -0
  17. package/dist/chunk-4HC3OWAQ.js +185 -0
  18. package/dist/chunk-4HC3OWAQ.js.map +1 -0
  19. package/dist/chunk-5EHNJCMF.js +58 -0
  20. package/dist/chunk-5EHNJCMF.js.map +1 -0
  21. package/dist/chunk-6QHKKFO3.js +30 -0
  22. package/dist/chunk-6QHKKFO3.js.map +1 -0
  23. package/dist/chunk-AEVQKFXX.js +119 -0
  24. package/dist/chunk-AEVQKFXX.js.map +1 -0
  25. package/dist/chunk-DKK26TRS.js +199 -0
  26. package/dist/chunk-DKK26TRS.js.map +1 -0
  27. package/dist/chunk-JVQZGKBL.js +171 -0
  28. package/dist/chunk-JVQZGKBL.js.map +1 -0
  29. package/dist/chunk-KWVGW5Q7.js +582 -0
  30. package/dist/chunk-KWVGW5Q7.js.map +1 -0
  31. package/dist/{chunk-A64ZILAS.js → chunk-M4WFJR3C.js} +3 -2
  32. package/dist/{chunk-A64ZILAS.js.map → chunk-M4WFJR3C.js.map} +1 -1
  33. package/dist/chunk-MTJDRWGD.js +196 -0
  34. package/dist/chunk-MTJDRWGD.js.map +1 -0
  35. package/dist/chunk-SADNMGP6.js +16 -0
  36. package/dist/chunk-SADNMGP6.js.map +1 -0
  37. package/dist/chunk-UECZA34Q.js +52 -0
  38. package/dist/chunk-UECZA34Q.js.map +1 -0
  39. package/dist/chunk-YJA4H6IB.js +13 -0
  40. package/dist/chunk-YJA4H6IB.js.map +1 -0
  41. package/dist/cli.d.ts +2 -0
  42. package/dist/cli.js +297 -0
  43. package/dist/cli.js.map +1 -0
  44. package/dist/connect-server-7L4HGKKL.js +7 -0
  45. package/dist/connect-server-7L4HGKKL.js.map +1 -0
  46. package/dist/create.d.ts +10 -0
  47. package/dist/create.js +107 -0
  48. package/dist/create.js.map +1 -0
  49. package/dist/dev/connect-server.d.ts +13 -0
  50. package/dist/dev/host-entry.d.ts +13 -0
  51. package/dist/dev/host-entry.js +18330 -0
  52. package/dist/dev/index.d.ts +45 -0
  53. package/dist/dev/index.js +14 -0
  54. package/dist/dev/index.js.map +1 -0
  55. package/dist/dev/sandbox-entry.d.ts +7 -0
  56. package/dist/dev/sandbox-entry.js +80 -0
  57. package/dist/html.d.ts +4 -0
  58. package/dist/index.d.ts +7 -26
  59. package/dist/index.js +16 -3
  60. package/dist/json-render/app.d.ts +22 -0
  61. package/dist/json-render/app.js +10 -0
  62. package/dist/json-render/app.js.map +1 -0
  63. package/dist/json-render/generative-view.d.ts +57 -0
  64. package/dist/json-render/index.d.ts +10 -0
  65. package/dist/json-render/index.js +120 -0
  66. package/dist/json-render/index.js.map +1 -0
  67. package/dist/json-render/mcp.d.ts +49 -0
  68. package/dist/json-render/mcp.js +11 -0
  69. package/dist/json-render/mcp.js.map +1 -0
  70. package/dist/json-render/server.d.ts +72 -0
  71. package/dist/json-render/server.js +98 -0
  72. package/dist/json-render/server.js.map +1 -0
  73. package/dist/json-render/spec-stream.d.ts +26 -0
  74. package/dist/json-render/use-spec-stream.d.ts +26 -0
  75. package/dist/publish.d.ts +10 -0
  76. package/dist/react/context.d.ts +14 -0
  77. package/dist/react/create-view.d.ts +8 -0
  78. package/dist/react/hooks.d.ts +46 -0
  79. package/dist/react/index.d.ts +3 -0
  80. package/dist/react/index.js +36 -0
  81. package/dist/react/index.js.map +1 -0
  82. package/dist/sandbox/index.d.ts +16 -0
  83. package/dist/sandbox/index.js +8 -0
  84. package/dist/sandbox/index.js.map +1 -0
  85. package/dist/schema.d.ts +20 -0
  86. package/dist/server/index.d.ts +73 -0
  87. package/dist/server/index.js +235 -0
  88. package/dist/server/index.js.map +1 -0
  89. package/dist/svelte/index.d.ts +29 -0
  90. package/dist/svelte/index.js +13 -0
  91. package/dist/svelte/index.js.map +1 -0
  92. package/dist/testing/index.d.ts +27 -0
  93. package/dist/testing/index.js +312 -0
  94. package/dist/testing/index.js.map +1 -0
  95. package/dist/testing/render-artifact.d.ts +35 -0
  96. package/dist/types.d.ts +65 -0
  97. package/dist/vite/index.d.ts +23 -0
  98. package/dist/vite/index.js +211 -0
  99. package/dist/vite/index.js.map +1 -0
  100. package/dist/vue/index.d.ts +51 -0
  101. package/dist/vue/index.js +93 -0
  102. package/dist/vue/index.js.map +1 -0
  103. package/package.json +162 -20
  104. package/templates/react/.gitignore.tmpl +4 -0
  105. package/templates/react/README.md.tmpl +15 -0
  106. package/templates/react/mcp.fixtures.json.tmpl +4 -0
  107. package/templates/react/package.json.tmpl +27 -0
  108. package/templates/react/server.mjs.tmpl +70 -0
  109. package/templates/react/src/view.tsx.tmpl +160 -0
  110. package/templates/react/tsconfig.json.tmpl +15 -0
  111. package/templates/react/vite.config.ts.tmpl +22 -0
  112. package/templates/svelte/.gitignore.tmpl +4 -0
  113. package/templates/svelte/README.md.tmpl +14 -0
  114. package/templates/svelte/mcp.fixtures.json.tmpl +4 -0
  115. package/templates/svelte/package.json.tmpl +26 -0
  116. package/templates/svelte/server.mjs.tmpl +70 -0
  117. package/templates/svelte/src/Dashboard.svelte.tmpl +158 -0
  118. package/templates/svelte/src/view.ts.tmpl +14 -0
  119. package/templates/svelte/tsconfig.json.tmpl +14 -0
  120. package/templates/svelte/vite.config.ts.tmpl +22 -0
  121. package/templates/vue/.gitignore.tmpl +4 -0
  122. package/templates/vue/README.md.tmpl +14 -0
  123. package/templates/vue/mcp.fixtures.json.tmpl +4 -0
  124. package/templates/vue/package.json.tmpl +26 -0
  125. package/templates/vue/server.mjs.tmpl +70 -0
  126. package/templates/vue/src/Dashboard.vue.tmpl +159 -0
  127. package/templates/vue/src/view.ts.tmpl +14 -0
  128. package/templates/vue/tsconfig.json.tmpl +15 -0
  129. package/templates/vue/vite.config.ts.tmpl +22 -0
  130. package/dist/chunk-BUGGLMNA.js +0 -132
  131. package/dist/chunk-BUGGLMNA.js.map +0 -1
  132. package/dist/types-B9JAWm4W.d.ts +0 -62
@@ -0,0 +1,185 @@
1
+ // src/json-render/mcp.ts
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { z } from "zod";
4
+ import {
5
+ EXTENSION_ID,
6
+ RESOURCE_MIME_TYPE,
7
+ getUiCapability,
8
+ registerAppResource,
9
+ registerAppTool
10
+ } from "@modelcontextprotocol/ext-apps/server";
11
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
12
+ import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
13
+ var DEFAULT_RESOURCE_URI = "ui://json-render/view.html";
14
+ var DEFAULT_TOOL_NAME = "render_ui";
15
+ var LOOSE_SPEC_SCHEMA = z.looseObject({
16
+ root: z.string(),
17
+ elements: z.record(z.string(), z.unknown()),
18
+ state: z.unknown().optional()
19
+ });
20
+ function resolveInputSchema(options) {
21
+ if (options.inputSchema) return options.inputSchema;
22
+ const { catalog } = options;
23
+ if (typeof catalog.zodSchema !== "function") {
24
+ return { spec: LOOSE_SPEC_SCHEMA };
25
+ }
26
+ return { spec: z.union([catalog.zodSchema(), LOOSE_SPEC_SCHEMA]) };
27
+ }
28
+ function resolveDescription(options) {
29
+ if (options.description) return options.description;
30
+ const base = "Render an interactive UI. The `spec` argument must be a json-render spec conforming to the catalog.";
31
+ const prompt = typeof options.catalog.prompt === "function" ? options.catalog.prompt() : "";
32
+ return prompt ? `${base}
33
+
34
+ ${prompt}` : base;
35
+ }
36
+ function pickToolInputSpec(args) {
37
+ if (!args || typeof args !== "object") return args;
38
+ const record = args;
39
+ if (record.spec !== void 0) return record.spec;
40
+ return record;
41
+ }
42
+ function assertUiUri(uri) {
43
+ if (!uri.startsWith("ui://")) {
44
+ throw new Error(
45
+ `mountly-mcp/json-render: resourceUri must use 'ui://' scheme (received '${uri}')`
46
+ );
47
+ }
48
+ }
49
+ function registerJsonRenderResource(server, options) {
50
+ const uri = options.resourceUri ?? DEFAULT_RESOURCE_URI;
51
+ assertUiUri(uri);
52
+ const name = options.name ?? "json_render_ui";
53
+ const description = options.description ?? "Interactive json-render MCP App view.";
54
+ const registered = registerAppResource(
55
+ server,
56
+ name,
57
+ uri,
58
+ {
59
+ mimeType: RESOURCE_MIME_TYPE,
60
+ description,
61
+ _meta: {
62
+ ui: {
63
+ prefersBorder: true,
64
+ csp: { connectDomains: [], resourceDomains: [], frameDomains: [] }
65
+ }
66
+ }
67
+ },
68
+ async () => ({
69
+ contents: [
70
+ {
71
+ uri,
72
+ mimeType: RESOURCE_MIME_TYPE,
73
+ text: options.html,
74
+ _meta: {
75
+ ui: {
76
+ prefersBorder: true,
77
+ csp: { connectDomains: [], resourceDomains: [], frameDomains: [] }
78
+ }
79
+ }
80
+ }
81
+ ]
82
+ })
83
+ );
84
+ return { uri, remove: () => registered.remove() };
85
+ }
86
+ function registerJsonRenderTool(server, options) {
87
+ const name = options.name ?? DEFAULT_TOOL_NAME;
88
+ const resourceUri = options.resourceUri ?? DEFAULT_RESOURCE_URI;
89
+ assertUiUri(resourceUri);
90
+ const description = resolveDescription(options);
91
+ const visibility = options.visibility === void 0 ? ["model", "app"] : typeof options.visibility === "string" ? [options.visibility] : [...options.visibility];
92
+ const registered = registerAppTool(
93
+ server,
94
+ name,
95
+ {
96
+ title: options.title,
97
+ description,
98
+ inputSchema: resolveInputSchema(options),
99
+ _meta: {
100
+ ui: {
101
+ resourceUri,
102
+ visibility
103
+ }
104
+ }
105
+ },
106
+ (async (args) => {
107
+ const spec = pickToolInputSpec(args);
108
+ const validation = options.catalog.validate?.(spec);
109
+ const validSpec = validation?.success ? validation.data : spec;
110
+ return {
111
+ // Two shapes on purpose. `content[0].text` is the wire format
112
+ // `@json-render/mcp` emits and its iframe hook reads, so an app built
113
+ // against that package renders a Mountly server unchanged.
114
+ // `structuredContent` is what Mountly's own hosts and tests read.
115
+ content: [{ type: "text", text: JSON.stringify(validSpec) }],
116
+ structuredContent: { spec: validSpec }
117
+ };
118
+ })
119
+ );
120
+ return { name, remove: () => registered.remove() };
121
+ }
122
+ function createJsonRenderMcpApp(options) {
123
+ const configured = new McpServer(
124
+ { name: options.name, version: options.version },
125
+ { capabilities: { resources: {}, tools: {} } }
126
+ );
127
+ configured.server.registerCapabilities({
128
+ extensions: { [EXTENSION_ID]: { mimeTypes: [RESOURCE_MIME_TYPE] } }
129
+ });
130
+ const resourceUri = options.tool?.resourceUri ?? DEFAULT_RESOURCE_URI;
131
+ assertUiUri(resourceUri);
132
+ const toolName = options.tool?.name ?? DEFAULT_TOOL_NAME;
133
+ const resource = registerJsonRenderResource(configured, {
134
+ resourceUri,
135
+ html: options.html,
136
+ name: "json_render_ui"
137
+ });
138
+ const tool = registerJsonRenderTool(configured, {
139
+ catalog: options.catalog,
140
+ name: toolName,
141
+ title: options.tool?.title,
142
+ description: options.tool?.description,
143
+ resourceUri,
144
+ visibility: options.tool?.visibility,
145
+ inputSchema: options.tool?.inputSchema
146
+ });
147
+ const previousInitialized = configured.server.oninitialized;
148
+ const handleInitialized = () => {
149
+ previousInitialized?.();
150
+ const ui = getUiCapability(configured.server.getClientCapabilities());
151
+ if (ui?.mimeTypes?.includes(RESOURCE_MIME_TYPE)) return;
152
+ resource.remove();
153
+ tool.remove();
154
+ };
155
+ configured.server.oninitialized = handleInitialized;
156
+ return {
157
+ async connectInProcess(connectOptions) {
158
+ const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
159
+ await configured.connect(serverTransport);
160
+ const client = new Client(
161
+ { name: `${options.name}-inprocess-client`, version: options.version },
162
+ connectOptions?.ui ?? true ? {
163
+ capabilities: {
164
+ extensions: { [EXTENSION_ID]: { mimeTypes: [RESOURCE_MIME_TYPE] } }
165
+ }
166
+ } : void 0
167
+ );
168
+ await client.connect(clientTransport);
169
+ return { client };
170
+ },
171
+ async listen(transport) {
172
+ await configured.connect(transport);
173
+ },
174
+ async close() {
175
+ await configured.close();
176
+ }
177
+ };
178
+ }
179
+
180
+ export {
181
+ registerJsonRenderResource,
182
+ registerJsonRenderTool,
183
+ createJsonRenderMcpApp
184
+ };
185
+ //# sourceMappingURL=chunk-4HC3OWAQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/json-render/mcp.ts"],"sourcesContent":["import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\nimport {\n EXTENSION_ID,\n RESOURCE_MIME_TYPE,\n getUiCapability,\n registerAppResource,\n registerAppTool,\n} from \"@modelcontextprotocol/ext-apps/server\";\nimport type { McpUiToolVisibility } from \"@modelcontextprotocol/ext-apps\";\nimport type { Catalog } from \"@json-render/core\";\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { InMemoryTransport } from \"@modelcontextprotocol/sdk/inMemory.js\";\nimport type { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\nimport type { RunningMcpAppServer } from \"../server/index.js\";\n\nexport interface JsonRenderToolOptions {\n /** MCP tool name. */\n name?: string;\n /** Human-readable tool title. */\n title?: string;\n /** Tool description for model selection. */\n description?: string;\n /** ui:// resource linked to this tool. */\n resourceUri?: string;\n /** Tool visibility in MCP Apps hosts. */\n visibility?: McpUiToolVisibility | ReadonlyArray<McpUiToolVisibility>;\n /** Optional explicit input schema override. */\n inputSchema?: object;\n}\n\nexport interface RegisterJsonRenderResourceOptions {\n resourceUri?: string;\n name?: string;\n description?: string;\n html: string;\n}\n\nexport interface RegisterJsonRenderToolOptions {\n catalog: Catalog;\n name?: string;\n title?: string;\n description?: string;\n resourceUri?: string;\n visibility?: McpUiToolVisibility | ReadonlyArray<McpUiToolVisibility>;\n inputSchema?: object;\n}\n\nexport interface CreateJsonRenderMcpAppOptions {\n name: string;\n version: string;\n catalog: Catalog;\n html: string;\n tool?: JsonRenderToolOptions;\n}\n\nconst DEFAULT_RESOURCE_URI = \"ui://json-render/view.html\";\nconst DEFAULT_TOOL_NAME = \"render_ui\";\n\n/**\n * A spec shape loose enough to survive real model output. `@json-render/core`\n * generates a *strict* catalog schema: as of 0.20 `visible` is optional, but\n * `children` is still required on every element — the omission models make on\n * roughly a third of first attempts. Gating tool input on the strict schema\n * alone turns that into an SDK-level rejection before the handler can repair\n * anything. (`@json-render/mcp` passes the strict schema straight through and\n * inherits that failure.) Delete this branch once `children` is optional too.\n */\nconst LOOSE_SPEC_SCHEMA = z.looseObject({\n root: z.string(),\n elements: z.record(z.string(), z.unknown()),\n state: z.unknown().optional(),\n});\n\n/**\n * The tool's input shape, matching `@json-render/mcp`: a single `spec`\n * argument typed by the catalog. Must be a Zod *raw shape* (a plain object of\n * Zod types) — handing the SDK a bare `ZodType` yields an empty\n * `{ type: \"object\", properties: {} }` schema, which tells the model nothing.\n */\nfunction resolveInputSchema(options: RegisterJsonRenderToolOptions): object {\n if (options.inputSchema) return options.inputSchema;\n const { catalog } = options;\n if (typeof catalog.zodSchema !== \"function\") {\n return { spec: LOOSE_SPEC_SCHEMA };\n }\n // The catalog branch documents the exact component vocabulary to the model;\n // the loose branch keeps a near-miss spec from being rejected outright. The\n // handler still validates against the catalog and reports what it repaired.\n return { spec: z.union([catalog.zodSchema(), LOOSE_SPEC_SCHEMA]) };\n}\n\n/**\n * Catalogs carry their own model-facing prompt (component list, prop shapes,\n * authoring rules). Appending it is what makes the tool usable by a model —\n * a bare one-line description leaves it guessing at the component vocabulary.\n */\nfunction resolveDescription(options: RegisterJsonRenderToolOptions): string {\n if (options.description) return options.description;\n const base =\n \"Render an interactive UI. The `spec` argument must be a json-render spec conforming to the catalog.\";\n const prompt = typeof options.catalog.prompt === \"function\" ? options.catalog.prompt() : \"\";\n return prompt ? `${base}\\n\\n${prompt}` : base;\n}\n\n/**\n * Unwrap the tool arguments. The declared shape is `{ spec }`, but a spec\n * passed at the top level is accepted too — hosts and hand-written clients do\n * both, and a spec is identifiable by its `root`/`elements` pair.\n */\nfunction pickToolInputSpec(args: unknown): unknown {\n if (!args || typeof args !== \"object\") return args;\n const record = args as Record<string, unknown>;\n if (record.spec !== undefined) return record.spec;\n return record;\n}\n\nfunction assertUiUri(uri: string): void {\n if (!uri.startsWith(\"ui://\")) {\n throw new Error(\n `mountly-mcp/json-render: resourceUri must use 'ui://' scheme (received '${uri}')`,\n );\n }\n}\n\nexport function registerJsonRenderResource(\n server: McpServer,\n options: RegisterJsonRenderResourceOptions,\n): { uri: string; remove(): void } {\n const uri = options.resourceUri ?? DEFAULT_RESOURCE_URI;\n assertUiUri(uri);\n const name = options.name ?? \"json_render_ui\";\n const description = options.description ?? \"Interactive json-render MCP App view.\";\n const registered = registerAppResource(\n server,\n name,\n uri,\n {\n mimeType: RESOURCE_MIME_TYPE,\n description,\n _meta: {\n ui: {\n prefersBorder: true,\n csp: { connectDomains: [], resourceDomains: [], frameDomains: [] },\n },\n },\n },\n async () => ({\n contents: [\n {\n uri,\n mimeType: RESOURCE_MIME_TYPE,\n text: options.html,\n _meta: {\n ui: {\n prefersBorder: true,\n csp: { connectDomains: [], resourceDomains: [], frameDomains: [] },\n },\n },\n },\n ],\n }),\n );\n return { uri, remove: () => registered.remove() };\n}\n\nexport function registerJsonRenderTool(\n server: McpServer,\n options: RegisterJsonRenderToolOptions,\n): { name: string; remove(): void } {\n const name = options.name ?? DEFAULT_TOOL_NAME;\n const resourceUri = options.resourceUri ?? DEFAULT_RESOURCE_URI;\n assertUiUri(resourceUri);\n const description = resolveDescription(options);\n const visibility =\n options.visibility === undefined\n ? [\"model\", \"app\"]\n : typeof options.visibility === \"string\"\n ? [options.visibility]\n : [...options.visibility];\n\n const registered = registerAppTool(\n server,\n name,\n {\n title: options.title,\n description,\n inputSchema: resolveInputSchema(options) as never,\n _meta: {\n ui: {\n resourceUri,\n visibility,\n },\n },\n } as never,\n (async (args: unknown) => {\n const spec = pickToolInputSpec(args);\n // Repair-tolerant, like `@json-render/mcp`: a spec that fails catalog\n // validation is still rendered rather than dropped — the renderer skips\n // what it cannot resolve, and a partial UI beats a blank iframe.\n const validation = options.catalog.validate?.(spec);\n const validSpec = validation?.success ? validation.data : spec;\n return {\n // Two shapes on purpose. `content[0].text` is the wire format\n // `@json-render/mcp` emits and its iframe hook reads, so an app built\n // against that package renders a Mountly server unchanged.\n // `structuredContent` is what Mountly's own hosts and tests read.\n content: [{ type: \"text\", text: JSON.stringify(validSpec) }],\n structuredContent: { spec: validSpec },\n };\n }) as never,\n );\n return { name, remove: () => registered.remove() };\n}\n\nexport function createJsonRenderMcpApp(\n options: CreateJsonRenderMcpAppOptions,\n): RunningMcpAppServer {\n const configured = new McpServer(\n { name: options.name, version: options.version },\n { capabilities: { resources: {}, tools: {} } },\n );\n configured.server.registerCapabilities({\n extensions: { [EXTENSION_ID]: { mimeTypes: [RESOURCE_MIME_TYPE] } },\n } as never);\n\n const resourceUri = options.tool?.resourceUri ?? DEFAULT_RESOURCE_URI;\n assertUiUri(resourceUri);\n const toolName = options.tool?.name ?? DEFAULT_TOOL_NAME;\n const resource = registerJsonRenderResource(configured, {\n resourceUri,\n html: options.html,\n name: \"json_render_ui\",\n });\n const tool = registerJsonRenderTool(configured, {\n catalog: options.catalog,\n name: toolName,\n title: options.tool?.title,\n description: options.tool?.description,\n resourceUri,\n visibility: options.tool?.visibility,\n inputSchema: options.tool?.inputSchema,\n });\n const previousInitialized = configured.server.oninitialized;\n const handleInitialized = () => {\n previousInitialized?.();\n const ui = getUiCapability(configured.server.getClientCapabilities());\n if (ui?.mimeTypes?.includes(RESOURCE_MIME_TYPE)) return;\n resource.remove();\n tool.remove();\n };\n configured.server.oninitialized = handleInitialized;\n\n return {\n async connectInProcess(connectOptions) {\n const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();\n await configured.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: Transport) {\n await configured.connect(transport);\n },\n async close() {\n await configured.close();\n },\n };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,58 @@
1
+ // src/json-render/spec-stream.ts
2
+ import { createSpecStreamCompiler } from "@json-render/core";
3
+ import {
4
+ compileSpecStream,
5
+ createJsonRenderTransform,
6
+ createSpecStreamCompiler as createSpecStreamCompiler2,
7
+ parseSpecStreamLine
8
+ } from "@json-render/core";
9
+ async function* compileTextStreamToSpecs(textStream) {
10
+ const compiler = createSpecStreamCompiler();
11
+ let buffer = "";
12
+ for await (const delta of textStream) {
13
+ buffer += delta;
14
+ const lines = buffer.split("\n");
15
+ buffer = lines.pop() ?? "";
16
+ for (const line of lines) {
17
+ if (!line.trim()) continue;
18
+ const { result } = compiler.push(`${line}
19
+ `);
20
+ yield { ...result };
21
+ }
22
+ }
23
+ if (buffer.trim()) {
24
+ const { result } = compiler.push(`${buffer}
25
+ `);
26
+ yield { ...result };
27
+ }
28
+ return compiler.getResult();
29
+ }
30
+ function specToPatchLines(spec) {
31
+ const lines = [JSON.stringify({ op: "add", path: "/elements", value: {} })];
32
+ if (spec.state !== void 0) {
33
+ lines.push(JSON.stringify({ op: "add", path: "/state", value: spec.state }));
34
+ }
35
+ lines.push(JSON.stringify({ op: "add", path: "/root", value: spec.root }));
36
+ const seen = /* @__PURE__ */ new Set();
37
+ const queue = [spec.root];
38
+ while (queue.length) {
39
+ const key = queue.shift();
40
+ if (seen.has(key) || !spec.elements[key]) continue;
41
+ seen.add(key);
42
+ const el = spec.elements[key];
43
+ const path = `/elements/${key.replace(/~/g, "~0").replace(/\//g, "~1")}`;
44
+ lines.push(JSON.stringify({ op: "add", path, value: el }));
45
+ for (const child of el.children ?? []) queue.push(child);
46
+ }
47
+ return lines;
48
+ }
49
+
50
+ export {
51
+ compileTextStreamToSpecs,
52
+ specToPatchLines,
53
+ compileSpecStream,
54
+ createJsonRenderTransform,
55
+ createSpecStreamCompiler2 as createSpecStreamCompiler,
56
+ parseSpecStreamLine
57
+ };
58
+ //# sourceMappingURL=chunk-5EHNJCMF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/json-render/spec-stream.ts"],"sourcesContent":["import { createSpecStreamCompiler } from \"@json-render/core\";\nimport type { Spec } from \"@json-render/core\";\n\n// json-render's streaming primitives, re-exported so the consumer side\n// (compile patches → progressive spec) is one import away.\nexport {\n compileSpecStream,\n createJsonRenderTransform,\n createSpecStreamCompiler,\n parseSpecStreamLine,\n} from \"@json-render/core\";\n\n/**\n * Drive json-render's stream compiler from a raw text stream of JSONL patches,\n * yielding the progressively-built spec after each complete line. This is the\n * live \"model token → patch → spec\" path, decoupled from any AI SDK so it can\n * drive a real model (see `streamSpec` in `mountly-mcp/json-render/server`), replay\n * a saved transcript, bridge a custom transport, or be unit-tested with a\n * hand-written stream. Buffers partial lines across chunk boundaries. The\n * generator's *return* value is the final compiled spec (before `autoFixSpec`).\n *\n * ```ts\n * for await (const partial of compileTextStreamToSpecs(result.textStream)) {\n * render(partial); // each yield is one more element built\n * }\n * ```\n */\nexport async function* compileTextStreamToSpecs(\n textStream: AsyncIterable<string>,\n): AsyncGenerator<Spec, Spec, void> {\n const compiler = createSpecStreamCompiler<Spec>();\n let buffer = \"\";\n for await (const delta of textStream) {\n buffer += delta;\n const lines = buffer.split(\"\\n\");\n // Keep the trailing partial line for the next chunk.\n buffer = lines.pop() ?? \"\";\n for (const line of lines) {\n if (!line.trim()) continue;\n const { result } = compiler.push(`${line}\\n`);\n yield { ...result };\n }\n }\n if (buffer.trim()) {\n const { result } = compiler.push(`${buffer}\\n`);\n yield { ...result };\n }\n return compiler.getResult();\n}\n\n/**\n * Turn a finished spec into json-render's streaming patch format — ordered\n * RFC-6902 JSONL lines (`/elements` scaffold, `/state`, `/root`, then each\n * element parent-before-child). Feed these to a `createSpecStreamCompiler` to\n * replay the build progressively, so a known spec streams in the same way a\n * live model would emit it. Pure spec work — no AI SDK dependency.\n */\nexport function specToPatchLines(spec: Spec): string[] {\n const lines: string[] = [JSON.stringify({ op: \"add\", path: \"/elements\", value: {} })];\n if (spec.state !== undefined) {\n lines.push(JSON.stringify({ op: \"add\", path: \"/state\", value: spec.state }));\n }\n lines.push(JSON.stringify({ op: \"add\", path: \"/root\", value: spec.root }));\n\n // Breadth-first from the root so parents stream before their children.\n const seen = new Set<string>();\n const queue = [spec.root];\n while (queue.length) {\n const key = queue.shift() as string;\n if (seen.has(key) || !spec.elements[key]) continue;\n seen.add(key);\n const el = spec.elements[key];\n const path = `/elements/${key.replace(/~/g, \"~0\").replace(/\\//g, \"~1\")}`;\n lines.push(JSON.stringify({ op: \"add\", path, value: el }));\n for (const child of el.children ?? []) queue.push(child);\n }\n return lines;\n}\n"],"mappings":";AAAA,SAAS,gCAAgC;AAKzC;AAAA,EACE;AAAA,EACA;AAAA,EACA,4BAAAA;AAAA,EACA;AAAA,OACK;AAiBP,gBAAuB,yBACrB,YACkC;AAClC,QAAM,WAAW,yBAA+B;AAChD,MAAI,SAAS;AACb,mBAAiB,SAAS,YAAY;AACpC,cAAU;AACV,UAAM,QAAQ,OAAO,MAAM,IAAI;AAE/B,aAAS,MAAM,IAAI,KAAK;AACxB,eAAW,QAAQ,OAAO;AACxB,UAAI,CAAC,KAAK,KAAK,EAAG;AAClB,YAAM,EAAE,OAAO,IAAI,SAAS,KAAK,GAAG,IAAI;AAAA,CAAI;AAC5C,YAAM,EAAE,GAAG,OAAO;AAAA,IACpB;AAAA,EACF;AACA,MAAI,OAAO,KAAK,GAAG;AACjB,UAAM,EAAE,OAAO,IAAI,SAAS,KAAK,GAAG,MAAM;AAAA,CAAI;AAC9C,UAAM,EAAE,GAAG,OAAO;AAAA,EACpB;AACA,SAAO,SAAS,UAAU;AAC5B;AASO,SAAS,iBAAiB,MAAsB;AACrD,QAAM,QAAkB,CAAC,KAAK,UAAU,EAAE,IAAI,OAAO,MAAM,aAAa,OAAO,CAAC,EAAE,CAAC,CAAC;AACpF,MAAI,KAAK,UAAU,QAAW;AAC5B,UAAM,KAAK,KAAK,UAAU,EAAE,IAAI,OAAO,MAAM,UAAU,OAAO,KAAK,MAAM,CAAC,CAAC;AAAA,EAC7E;AACA,QAAM,KAAK,KAAK,UAAU,EAAE,IAAI,OAAO,MAAM,SAAS,OAAO,KAAK,KAAK,CAAC,CAAC;AAGzE,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,QAAQ,CAAC,KAAK,IAAI;AACxB,SAAO,MAAM,QAAQ;AACnB,UAAM,MAAM,MAAM,MAAM;AACxB,QAAI,KAAK,IAAI,GAAG,KAAK,CAAC,KAAK,SAAS,GAAG,EAAG;AAC1C,SAAK,IAAI,GAAG;AACZ,UAAM,KAAK,KAAK,SAAS,GAAG;AAC5B,UAAM,OAAO,aAAa,IAAI,QAAQ,MAAM,IAAI,EAAE,QAAQ,OAAO,IAAI,CAAC;AACtE,UAAM,KAAK,KAAK,UAAU,EAAE,IAAI,OAAO,MAAM,OAAO,GAAG,CAAC,CAAC;AACzD,eAAW,SAAS,GAAG,YAAY,CAAC,EAAG,OAAM,KAAK,KAAK;AAAA,EACzD;AACA,SAAO;AACT;","names":["createSpecStreamCompiler"]}
@@ -0,0 +1,30 @@
1
+ import {
2
+ escapeInlineScript,
3
+ serializeInlineScriptValue
4
+ } from "./chunk-YJA4H6IB.js";
5
+
6
+ // src/sandbox/index.ts
7
+ function sandboxProxyHtml(hostOrigin, options = {}) {
8
+ const runtime = options.sandboxEntryScript ?? "";
9
+ return `<!doctype html>
10
+ <html lang="en">
11
+ <head>
12
+ <meta charset="utf-8">
13
+ <title>mountly-mcp sandbox proxy</title>
14
+ <style>
15
+ html, body { margin: 0; padding: 0; width: 100%; height: 100%; background: transparent; }
16
+ #inner { width: 100%; height: 100%; border: 0; background: white; }
17
+ </style>
18
+ </head>
19
+ <body>
20
+ <script>globalThis.__mountlyMcpSandbox__ = ${serializeInlineScriptValue({ hostOrigin })};</script>
21
+ <script>${escapeInlineScript(runtime)}</script>
22
+ </body>
23
+ </html>
24
+ `;
25
+ }
26
+
27
+ export {
28
+ sandboxProxyHtml
29
+ };
30
+ //# sourceMappingURL=chunk-6QHKKFO3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/sandbox/index.ts"],"sourcesContent":["/**\n * Standalone sandbox proxy HTML for external hosts (Vercel AI SDK, etc.).\n *\n * The dev host already builds this internally, but external hosts need it too —\n * every host has to serve the proxy page on a separate origin (spec §8.4), and\n * hand-writing it is error-prone and drifts from the canonical CSP/permissions\n * logic in `sandbox-entry`.\n *\n * The proxy page is a thin HTML shell: it sets `__mountlyMcpSandbox__` with the\n * host origin and then runs the sandbox entry script (an IIFE). Hosts provide\n * the entry script themselves because loading strategy varies — some inline it,\n * others serve it as a separate `<script src>`.\n */\nimport { escapeInlineScript, serializeInlineScriptValue } from \"../html.js\";\n\nexport interface SandboxProxyHtmlOptions {\n /**\n * The compiled sandbox entry IIFE to inline. When omitted the page emits an\n * empty `<script>` — the caller must arrange for the runtime to load another\n * way (e.g. a `<script src>` tag).\n */\n sandboxEntryScript?: string;\n}\n\n/**\n * Return the complete sandbox proxy HTML document for the given host origin.\n *\n * This is the security boundary described in MCP Apps §8.4: it runs on an\n * origin distinct from the host, receives the view HTML over postMessage,\n * injects the CSP the sidecar declared, and relays messages.\n */\nexport function sandboxProxyHtml(\n hostOrigin: string,\n options: SandboxProxyHtmlOptions = {},\n): string {\n const runtime = options.sandboxEntryScript ?? \"\";\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<title>mountly-mcp sandbox proxy</title>\n<style>\n html, body { margin: 0; padding: 0; width: 100%; height: 100%; background: transparent; }\n #inner { width: 100%; height: 100%; border: 0; background: white; }\n</style>\n</head>\n<body>\n<script>globalThis.__mountlyMcpSandbox__ = ${serializeInlineScriptValue({ hostOrigin })};</script>\n<script>${escapeInlineScript(runtime)}</script>\n</body>\n</html>\n`;\n}\n"],"mappings":";;;;;;AA+BO,SAAS,iBACd,YACA,UAAmC,CAAC,GAC5B;AACR,QAAM,UAAU,QAAQ,sBAAsB;AAC9C,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,6CAWoC,2BAA2B,EAAE,WAAW,CAAC,CAAC;AAAA,UAC7E,mBAAmB,OAAO,CAAC;AAAA;AAAA;AAAA;AAIrC;","names":[]}
@@ -0,0 +1,119 @@
1
+ import {
2
+ publishMcpView
3
+ } from "./chunk-SADNMGP6.js";
4
+
5
+ // src/react/context.ts
6
+ import { createContext } from "react";
7
+ var McpContext = createContext(null);
8
+ McpContext.displayName = "McpContext";
9
+
10
+ // src/react/hooks.ts
11
+ import { useContext } from "react";
12
+ import {
13
+ useAutoResize as useAutoResizeBase,
14
+ useDocumentTheme as useDocumentThemeBase,
15
+ useHostFonts as useHostFontsBase,
16
+ useHostStyleVariables as useHostStyleVariablesBase,
17
+ useHostStyles as useHostStylesBase
18
+ } from "@modelcontextprotocol/ext-apps/react";
19
+ function useEnsuredContext() {
20
+ const ctx = useContext(McpContext);
21
+ if (!ctx) {
22
+ throw new Error(
23
+ "mountly-mcp/react: useMcpApp/useToolInput/etc must be used inside a View wrapped with createMcpView()."
24
+ );
25
+ }
26
+ return ctx;
27
+ }
28
+ function useMcpApp() {
29
+ return useEnsuredContext().app;
30
+ }
31
+ function useToolInput() {
32
+ return useEnsuredContext().toolInput;
33
+ }
34
+ function useToolInputPartial() {
35
+ return useEnsuredContext().toolInputPartial;
36
+ }
37
+ function useToolResult() {
38
+ return useEnsuredContext().toolResult;
39
+ }
40
+ function useHostContext() {
41
+ return useEnsuredContext().hostContext;
42
+ }
43
+ function useDisplayMode() {
44
+ return useHostContext()?.displayMode;
45
+ }
46
+ function useRequestDisplayMode() {
47
+ const { app, hostContext } = useEnsuredContext();
48
+ return async (mode) => {
49
+ const available = hostContext?.availableDisplayModes;
50
+ if (available && !available.includes(mode)) return hostContext?.displayMode;
51
+ const result = await app.requestDisplayMode({ mode });
52
+ return result.mode;
53
+ };
54
+ }
55
+ function useHostStyleVariables() {
56
+ const { app, hostContext } = useEnsuredContext();
57
+ useHostStyleVariablesBase(app, hostContext ?? null);
58
+ }
59
+ function useHostFonts() {
60
+ const { app, hostContext } = useEnsuredContext();
61
+ useHostFontsBase(app, hostContext ?? null);
62
+ }
63
+ function useHostStyles() {
64
+ const { app, hostContext } = useEnsuredContext();
65
+ useHostStylesBase(app, hostContext ?? null);
66
+ }
67
+ function useDocumentTheme() {
68
+ return useDocumentThemeBase();
69
+ }
70
+ function useUpdateModelContext() {
71
+ const { app } = useEnsuredContext();
72
+ return async (params) => {
73
+ await app.updateModelContext(params);
74
+ };
75
+ }
76
+ function useAutoResize(...args) {
77
+ return useAutoResizeBase(...args);
78
+ }
79
+
80
+ // src/react/create-view.ts
81
+ import { createElement } from "react";
82
+ import { createWidget } from "mountly-react";
83
+ function createMcpView(Component, options) {
84
+ const Wrapped = (props) => {
85
+ const { mcp, toolInput, toolInputPartial, toolResult, hostContext, ...rest } = props;
86
+ const value = {
87
+ app: mcp,
88
+ toolInput,
89
+ toolInputPartial,
90
+ toolResult,
91
+ hostContext
92
+ };
93
+ return createElement(
94
+ McpContext.Provider,
95
+ { value },
96
+ createElement(Component, rest)
97
+ );
98
+ };
99
+ return publishMcpView(createWidget(Wrapped, options));
100
+ }
101
+
102
+ export {
103
+ McpContext,
104
+ useMcpApp,
105
+ useToolInput,
106
+ useToolInputPartial,
107
+ useToolResult,
108
+ useHostContext,
109
+ useDisplayMode,
110
+ useRequestDisplayMode,
111
+ useHostStyleVariables,
112
+ useHostFonts,
113
+ useHostStyles,
114
+ useDocumentTheme,
115
+ useUpdateModelContext,
116
+ useAutoResize,
117
+ createMcpView
118
+ };
119
+ //# sourceMappingURL=chunk-AEVQKFXX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/react/context.ts","../src/react/hooks.ts","../src/react/create-view.ts"],"sourcesContent":["import { createContext } from \"react\";\nimport type {\n App,\n McpUiHostContext,\n McpUiToolInputNotification,\n McpUiToolInputPartialNotification,\n McpUiToolResultNotification,\n} from \"@modelcontextprotocol/ext-apps\";\n\n/**\n * Shared per-View context. The bridge populates this on each mount/update,\n * so consumers can synchronously read the initial values (no event subscription\n * race) and the React tree re-renders when the bridge calls `update()`.\n */\nexport interface McpContextValue {\n app: App;\n toolInput?: McpUiToolInputNotification[\"params\"];\n toolInputPartial?: McpUiToolInputPartialNotification[\"params\"];\n toolResult?: McpUiToolResultNotification[\"params\"];\n hostContext?: McpUiHostContext;\n}\n\nexport const McpContext = createContext<McpContextValue | null>(null);\nMcpContext.displayName = \"McpContext\";\n","import { useContext } from \"react\";\nimport {\n useAutoResize as useAutoResizeBase,\n useDocumentTheme as useDocumentThemeBase,\n useHostFonts as useHostFontsBase,\n useHostStyleVariables as useHostStyleVariablesBase,\n useHostStyles as useHostStylesBase,\n} from \"@modelcontextprotocol/ext-apps/react\";\nimport type {\n App,\n McpUiDisplayMode,\n McpUiHostContext,\n McpUiUpdateModelContextRequest,\n} from \"@modelcontextprotocol/ext-apps\";\nimport { McpContext } from \"./context.js\";\n\nfunction useEnsuredContext() {\n const ctx = useContext(McpContext);\n if (!ctx) {\n throw new Error(\n \"mountly-mcp/react: useMcpApp/useToolInput/etc must be used inside a View wrapped with createMcpView().\",\n );\n }\n return ctx;\n}\n\n/** The view-side ext-apps App. Use this to call server tools, send messages, open links, etc. */\nexport function useMcpApp(): App {\n return useEnsuredContext().app;\n}\n\n/** Complete notification params; read the tool arguments from `.arguments`. */\nexport function useToolInput<T = unknown>(): T | undefined {\n return useEnsuredContext().toolInput as T | undefined;\n}\n\n/**\n * Streaming tool arguments (`ui/notifications/tool-input-partial`), present only\n * when the bridge runs with `streamToolInput`. Best-effort recovered JSON —\n * fields may be missing or change between notifications, so render loading\n * states from it, never critical operations. Undefined once the complete\n * {@link useToolInput} arrives.\n */\nexport function useToolInputPartial<T = unknown>(): Partial<T> | undefined {\n return useEnsuredContext().toolInputPartial as Partial<T> | undefined;\n}\n\n/** Complete MCP tool result; structured data is available at `.structuredContent`. */\nexport function useToolResult<T = unknown>(): T | undefined {\n return useEnsuredContext().toolResult as T | undefined;\n}\n\n/** Latest host context snapshot (theme, styles, displayMode, container dimensions, etc.). */\nexport function useHostContext(): McpUiHostContext | undefined {\n return useEnsuredContext().hostContext;\n}\n\n/** Current display mode reported by the host. */\nexport function useDisplayMode(): McpUiDisplayMode | undefined {\n return useHostContext()?.displayMode;\n}\n\n/**\n * Request a display mode change, resolving to the mode the host actually\n * applied — which the spec allows to differ from the one asked for.\n *\n * Modes the host didn't list in `availableDisplayModes` are never requested\n * (the spec requires views to check first); the current mode comes back\n * unchanged instead.\n */\nexport function useRequestDisplayMode(): (\n mode: McpUiDisplayMode,\n) => Promise<McpUiDisplayMode | undefined> {\n const { app, hostContext } = useEnsuredContext();\n return async (mode) => {\n const available = hostContext?.availableDisplayModes;\n if (available && !available.includes(mode)) return hostContext?.displayMode;\n const result = await app.requestDisplayMode({ mode });\n return result.mode;\n };\n}\n\n/** Apply host CSS variables (and react to host-context changes). */\nexport function useHostStyleVariables(): void {\n const { app, hostContext } = useEnsuredContext();\n useHostStyleVariablesBase(app, hostContext ?? null);\n}\n\n/** Apply host-provided `@font-face`/`@import` rules. */\nexport function useHostFonts(): void {\n const { app, hostContext } = useEnsuredContext();\n useHostFontsBase(app, hostContext ?? null);\n}\n\n/** One-shot helper: apply both host style variables and host fonts. */\nexport function useHostStyles(): void {\n const { app, hostContext } = useEnsuredContext();\n useHostStylesBase(app, hostContext ?? null);\n}\n\n/** Track the document theme reactively. */\nexport function useDocumentTheme() {\n return useDocumentThemeBase();\n}\n\n/**\n * Return a function that calls `app.updateModelContext(params)`.\n *\n * Context updates are available to the model in future turns without\n * triggering an immediate response. Only the last update is kept.\n */\nexport function useUpdateModelContext(): (\n params: McpUiUpdateModelContextRequest[\"params\"],\n) => Promise<void> {\n const { app } = useEnsuredContext();\n return async (params) => {\n await app.updateModelContext(params);\n };\n}\n\n/** Manually control auto-resize (rarely needed — the bridge calls setupSizeChangedNotifications by default). */\nexport function useAutoResize(...args: Parameters<typeof useAutoResizeBase>) {\n return useAutoResizeBase(...args);\n}\n","/**\n * Wraps a React component as an MCP Apps View and publishes it for the bridge.\n * One call is enough — no manual global assign.\n */\nimport { createElement, type ComponentType } from \"react\";\nimport { createWidget } from \"mountly-react\";\nimport type { AdapterOptions } from \"mountly/adapter\";\nimport type { McpView, McpViewProps } from \"../types.js\";\nimport { publishMcpView } from \"../publish.js\";\nimport { McpContext, type McpContextValue } from \"./context.js\";\n\nexport function createMcpView<P extends object>(\n Component: ComponentType<P>,\n options?: AdapterOptions,\n): McpView {\n const Wrapped = (props: P & McpViewProps) => {\n const { mcp, toolInput, toolInputPartial, toolResult, hostContext, ...rest } =\n props as McpViewProps & P;\n const value: McpContextValue = {\n app: mcp,\n toolInput,\n toolInputPartial,\n toolResult,\n hostContext,\n };\n return createElement(\n McpContext.Provider,\n { value },\n createElement(Component as ComponentType, rest as P),\n );\n };\n return publishMcpView(createWidget(Wrapped as ComponentType<P & McpViewProps>, options));\n}\n"],"mappings":";;;;;AAAA,SAAS,qBAAqB;AAsBvB,IAAM,aAAa,cAAsC,IAAI;AACpE,WAAW,cAAc;;;ACvBzB,SAAS,kBAAkB;AAC3B;AAAA,EACE,iBAAiB;AAAA,EACjB,oBAAoB;AAAA,EACpB,gBAAgB;AAAA,EAChB,yBAAyB;AAAA,EACzB,iBAAiB;AAAA,OACZ;AASP,SAAS,oBAAoB;AAC3B,QAAM,MAAM,WAAW,UAAU;AACjC,MAAI,CAAC,KAAK;AACR,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAGO,SAAS,YAAiB;AAC/B,SAAO,kBAAkB,EAAE;AAC7B;AAGO,SAAS,eAA2C;AACzD,SAAO,kBAAkB,EAAE;AAC7B;AASO,SAAS,sBAA2D;AACzE,SAAO,kBAAkB,EAAE;AAC7B;AAGO,SAAS,gBAA4C;AAC1D,SAAO,kBAAkB,EAAE;AAC7B;AAGO,SAAS,iBAA+C;AAC7D,SAAO,kBAAkB,EAAE;AAC7B;AAGO,SAAS,iBAA+C;AAC7D,SAAO,eAAe,GAAG;AAC3B;AAUO,SAAS,wBAE2B;AACzC,QAAM,EAAE,KAAK,YAAY,IAAI,kBAAkB;AAC/C,SAAO,OAAO,SAAS;AACrB,UAAM,YAAY,aAAa;AAC/B,QAAI,aAAa,CAAC,UAAU,SAAS,IAAI,EAAG,QAAO,aAAa;AAChE,UAAM,SAAS,MAAM,IAAI,mBAAmB,EAAE,KAAK,CAAC;AACpD,WAAO,OAAO;AAAA,EAChB;AACF;AAGO,SAAS,wBAA8B;AAC5C,QAAM,EAAE,KAAK,YAAY,IAAI,kBAAkB;AAC/C,4BAA0B,KAAK,eAAe,IAAI;AACpD;AAGO,SAAS,eAAqB;AACnC,QAAM,EAAE,KAAK,YAAY,IAAI,kBAAkB;AAC/C,mBAAiB,KAAK,eAAe,IAAI;AAC3C;AAGO,SAAS,gBAAsB;AACpC,QAAM,EAAE,KAAK,YAAY,IAAI,kBAAkB;AAC/C,oBAAkB,KAAK,eAAe,IAAI;AAC5C;AAGO,SAAS,mBAAmB;AACjC,SAAO,qBAAqB;AAC9B;AAQO,SAAS,wBAEG;AACjB,QAAM,EAAE,IAAI,IAAI,kBAAkB;AAClC,SAAO,OAAO,WAAW;AACvB,UAAM,IAAI,mBAAmB,MAAM;AAAA,EACrC;AACF;AAGO,SAAS,iBAAiB,MAA4C;AAC3E,SAAO,kBAAkB,GAAG,IAAI;AAClC;;;ACvHA,SAAS,qBAAyC;AAClD,SAAS,oBAAoB;AAMtB,SAAS,cACd,WACA,SACS;AACT,QAAM,UAAU,CAAC,UAA4B;AAC3C,UAAM,EAAE,KAAK,WAAW,kBAAkB,YAAY,aAAa,GAAG,KAAK,IACzE;AACF,UAAM,QAAyB;AAAA,MAC7B,KAAK;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,WAAO;AAAA,MACL,WAAW;AAAA,MACX,EAAE,MAAM;AAAA,MACR,cAAc,WAA4B,IAAS;AAAA,IACrD;AAAA,EACF;AACA,SAAO,eAAe,aAAa,SAA4C,OAAO,CAAC;AACzF;","names":[]}
@@ -0,0 +1,199 @@
1
+ // src/artifact/index.ts
2
+ import { readFile, writeFile } from "fs/promises";
3
+ import { dirname, isAbsolute, relative, resolve } from "path";
4
+ import {
5
+ McpUiDisplayModeSchema,
6
+ McpUiResourceMetaSchema,
7
+ RESOURCE_MIME_TYPE,
8
+ LATEST_PROTOCOL_VERSION
9
+ } from "@modelcontextprotocol/ext-apps";
10
+ var MCP_APP_MANIFEST_VERSION = 1;
11
+ var MCP_APP_MANIFEST_FILE = "mountly-mcp.manifest.json";
12
+ var McpAppConformanceError = class extends Error {
13
+ constructor(diagnostics) {
14
+ super(diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n"));
15
+ this.name = "McpAppConformanceError";
16
+ this.diagnostics = diagnostics;
17
+ }
18
+ };
19
+ function object(value) {
20
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
21
+ }
22
+ function inspectMcpResourceDeclaration(value, source) {
23
+ const diagnostics = [];
24
+ const add = (code, message) => {
25
+ diagnostics.push({ severity: "error", code, message, source });
26
+ };
27
+ const candidate = object(value);
28
+ if (!candidate) {
29
+ add("artifact/not-object", "resource declaration must be an object");
30
+ return diagnostics;
31
+ }
32
+ if (candidate.protocolVersion !== LATEST_PROTOCOL_VERSION) {
33
+ add(
34
+ "artifact/protocol-version",
35
+ `sidecar protocolVersion must be '${LATEST_PROTOCOL_VERSION}' (received '${String(candidate.protocolVersion)}')`
36
+ );
37
+ }
38
+ if (typeof candidate.uri !== "string" || !candidate.uri.startsWith("ui://")) {
39
+ add("artifact/uri", `uri must use the 'ui://' scheme (received '${String(candidate.uri)}')`);
40
+ }
41
+ if (typeof candidate.name !== "string" || candidate.name.trim() === "") {
42
+ add("artifact/name", "name must be a non-empty string");
43
+ }
44
+ if (candidate.mimeType !== RESOURCE_MIME_TYPE) {
45
+ add(
46
+ "artifact/mime-type",
47
+ `sidecar mimeType must be '${RESOURCE_MIME_TYPE}' (received '${String(candidate.mimeType)}')`
48
+ );
49
+ }
50
+ if (typeof candidate.awaitToolResult !== "boolean") {
51
+ add("artifact/await-tool-result", "awaitToolResult must be a boolean");
52
+ }
53
+ if (!Array.isArray(candidate.displayModes) || candidate.displayModes.length === 0 || candidate.displayModes.some((mode) => !McpUiDisplayModeSchema.safeParse(mode).success)) {
54
+ add("artifact/display-modes", "displayModes must contain supported MCP Apps display modes");
55
+ }
56
+ const meta = object(candidate._meta);
57
+ const ui = meta && object(meta.ui);
58
+ if (!ui || !McpUiResourceMetaSchema.safeParse(ui).success) {
59
+ add("artifact/resource-meta", "_meta.ui must be valid MCP Apps resource metadata");
60
+ }
61
+ return diagnostics;
62
+ }
63
+ function parseMcpResourceDeclaration(value, source) {
64
+ const diagnostics = inspectMcpResourceDeclaration(value, source);
65
+ if (diagnostics.length > 0) throw new McpAppConformanceError(diagnostics);
66
+ return value;
67
+ }
68
+ async function readMcpAppArtifact(htmlPath, expectedUri) {
69
+ const absoluteHtmlPath = resolve(htmlPath);
70
+ const metaPath = `${absoluteHtmlPath}.meta.json`;
71
+ const [html, raw] = await Promise.all([
72
+ readFile(absoluteHtmlPath, "utf8"),
73
+ readFile(metaPath, "utf8")
74
+ ]);
75
+ if (!/^\s*<!doctype html>/i.test(html)) {
76
+ throw new McpAppConformanceError([
77
+ {
78
+ severity: "error",
79
+ code: "artifact/html5",
80
+ message: "View HTML must start with an HTML5 doctype",
81
+ source: absoluteHtmlPath
82
+ }
83
+ ]);
84
+ }
85
+ let decoded;
86
+ try {
87
+ decoded = JSON.parse(raw);
88
+ } catch (error) {
89
+ throw new McpAppConformanceError([
90
+ {
91
+ severity: "error",
92
+ code: "artifact/meta-json",
93
+ message: `metadata is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
94
+ source: metaPath
95
+ }
96
+ ]);
97
+ }
98
+ const declaration = parseMcpResourceDeclaration(decoded, metaPath);
99
+ if (expectedUri !== void 0 && declaration.uri !== expectedUri) {
100
+ throw new McpAppConformanceError([
101
+ {
102
+ severity: "error",
103
+ code: "artifact/uri-mismatch",
104
+ message: `registration uri '${expectedUri}' does not match sidecar uri '${declaration.uri}'`,
105
+ source: metaPath
106
+ }
107
+ ]);
108
+ }
109
+ return {
110
+ name: declaration.name,
111
+ uri: declaration.uri,
112
+ htmlPath: absoluteHtmlPath,
113
+ metaPath,
114
+ declaration
115
+ };
116
+ }
117
+ function manifestPath(path, base) {
118
+ return isAbsolute(path) ? path : resolve(base, path);
119
+ }
120
+ async function writeMcpAppManifest(path, artifacts) {
121
+ const absolutePath = resolve(path);
122
+ const base = dirname(absolutePath);
123
+ const names = /* @__PURE__ */ new Set();
124
+ const uris = /* @__PURE__ */ new Set();
125
+ for (const artifact of artifacts) {
126
+ if (names.has(artifact.name))
127
+ throw new Error(`mountly-mcp: duplicate app name '${artifact.name}'`);
128
+ if (uris.has(artifact.uri)) throw new Error(`mountly-mcp: duplicate app uri '${artifact.uri}'`);
129
+ names.add(artifact.name);
130
+ uris.add(artifact.uri);
131
+ }
132
+ const manifest = {
133
+ formatVersion: MCP_APP_MANIFEST_VERSION,
134
+ protocolVersion: LATEST_PROTOCOL_VERSION,
135
+ apps: artifacts.map((artifact) => ({
136
+ name: artifact.name,
137
+ uri: artifact.uri,
138
+ html: relative(base, artifact.htmlPath),
139
+ meta: relative(base, artifact.metaPath),
140
+ declaration: artifact.declaration
141
+ }))
142
+ };
143
+ await writeFile(absolutePath, JSON.stringify(manifest, null, 2), "utf8");
144
+ return manifest;
145
+ }
146
+ async function readMcpAppManifest(path) {
147
+ const absolutePath = resolve(path);
148
+ const base = dirname(absolutePath);
149
+ let decoded;
150
+ try {
151
+ decoded = JSON.parse(await readFile(absolutePath, "utf8"));
152
+ } catch (error) {
153
+ throw new Error(
154
+ `mountly-mcp: could not read manifest '${absolutePath}': ${error instanceof Error ? error.message : String(error)}`
155
+ );
156
+ }
157
+ const candidate = object(decoded);
158
+ if (candidate?.formatVersion !== MCP_APP_MANIFEST_VERSION || candidate.protocolVersion !== LATEST_PROTOCOL_VERSION || !Array.isArray(candidate.apps)) {
159
+ throw new Error(
160
+ `mountly-mcp: '${absolutePath}' is not a supported mountly-mcp manifest (expected formatVersion ${MCP_APP_MANIFEST_VERSION})`
161
+ );
162
+ }
163
+ const manifest = decoded;
164
+ const names = /* @__PURE__ */ new Set();
165
+ const uris = /* @__PURE__ */ new Set();
166
+ const artifacts = [];
167
+ for (const entry of manifest.apps) {
168
+ if (names.has(entry.name)) throw new Error(`mountly-mcp: duplicate app name '${entry.name}'`);
169
+ if (uris.has(entry.uri)) throw new Error(`mountly-mcp: duplicate app uri '${entry.uri}'`);
170
+ names.add(entry.name);
171
+ uris.add(entry.uri);
172
+ const declaration = parseMcpResourceDeclaration(entry.declaration, absolutePath);
173
+ if (declaration.name !== entry.name || declaration.uri !== entry.uri) {
174
+ throw new Error(
175
+ `mountly-mcp: manifest identity does not match declaration for '${entry.name}'`
176
+ );
177
+ }
178
+ artifacts.push({
179
+ name: entry.name,
180
+ uri: entry.uri,
181
+ htmlPath: manifestPath(entry.html, base),
182
+ metaPath: manifestPath(entry.meta, base),
183
+ declaration
184
+ });
185
+ }
186
+ return { path: absolutePath, manifest, artifacts };
187
+ }
188
+
189
+ export {
190
+ MCP_APP_MANIFEST_VERSION,
191
+ MCP_APP_MANIFEST_FILE,
192
+ McpAppConformanceError,
193
+ inspectMcpResourceDeclaration,
194
+ parseMcpResourceDeclaration,
195
+ readMcpAppArtifact,
196
+ writeMcpAppManifest,
197
+ readMcpAppManifest
198
+ };
199
+ //# sourceMappingURL=chunk-DKK26TRS.js.map