mountly-mcp 4.0.2 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/GLOSSARY.md +37 -0
  2. package/README.md +103 -190
  3. package/dist/add-DAFMA4F3.js +263 -0
  4. package/dist/add-DAFMA4F3.js.map +1 -0
  5. package/dist/add.d.ts +13 -0
  6. package/dist/chunk-EOWAWZH2.js +34 -0
  7. package/dist/chunk-EOWAWZH2.js.map +1 -0
  8. package/dist/{chunk-KWVGW5Q7.js → chunk-GN2IQYV3.js} +1 -1
  9. package/dist/chunk-GN2IQYV3.js.map +1 -0
  10. package/dist/{chunk-UECZA34Q.js → chunk-OWAXBNZA.js} +10 -5
  11. package/dist/chunk-OWAXBNZA.js.map +1 -0
  12. package/dist/cli.js +97 -25
  13. package/dist/cli.js.map +1 -1
  14. package/dist/compose/index.d.ts +48 -0
  15. package/dist/compose/index.js +141 -0
  16. package/dist/compose/index.js.map +1 -0
  17. package/dist/connect-server-P2UZ62YC.js +7 -0
  18. package/dist/create.js +43 -15
  19. package/dist/create.js.map +1 -1
  20. package/dist/dev/host-entry.js +7 -0
  21. package/dist/dev/index.js +2 -2
  22. package/dist/doctor-B54H6CRN.js +122 -0
  23. package/dist/doctor-B54H6CRN.js.map +1 -0
  24. package/dist/doctor.d.ts +16 -0
  25. package/dist/errors-4ERW7HZB.js +11 -0
  26. package/dist/errors-4ERW7HZB.js.map +1 -0
  27. package/dist/errors.d.ts +25 -0
  28. package/dist/json-render/index.js +4 -4
  29. package/package.json +17 -5
  30. package/templates/react/README.md.tmpl +83 -6
  31. package/templates/react/package.json.tmpl +2 -0
  32. package/templates/react/serve-stdio.mjs.tmpl +9 -0
  33. package/templates/react/server.mjs.tmpl +6 -1
  34. package/templates/react/src/view.tsx.tmpl +1 -1
  35. package/templates/svelte/README.md.tmpl +27 -4
  36. package/templates/svelte/package.json.tmpl +2 -0
  37. package/templates/svelte/serve-stdio.mjs.tmpl +5 -0
  38. package/templates/svelte/server.mjs.tmpl +6 -1
  39. package/templates/svelte/src/Dashboard.svelte.tmpl +1 -1
  40. package/templates/vanilla/README.md.tmpl +27 -11
  41. package/templates/vanilla/package.json.tmpl +2 -0
  42. package/templates/vanilla/serve-stdio.mjs.tmpl +5 -0
  43. package/templates/vanilla/server.mjs.tmpl +6 -1
  44. package/templates/vanilla/src/view.ts.tmpl +1 -1
  45. package/templates/vue/README.md.tmpl +27 -4
  46. package/templates/vue/package.json.tmpl +2 -0
  47. package/templates/vue/serve-stdio.mjs.tmpl +5 -0
  48. package/templates/vue/server.mjs.tmpl +6 -1
  49. package/templates/vue/src/Dashboard.vue.tmpl +1 -1
  50. package/dist/chunk-KWVGW5Q7.js.map +0 -1
  51. package/dist/chunk-UECZA34Q.js.map +0 -1
  52. package/dist/connect-server-7L4HGKKL.js +0 -7
  53. /package/dist/{connect-server-7L4HGKKL.js.map → connect-server-P2UZ62YC.js.map} +0 -0
@@ -0,0 +1,263 @@
1
+ import {
2
+ CLI_ERROR_CODES,
3
+ cliError
4
+ } from "./chunk-EOWAWZH2.js";
5
+
6
+ // src/add.ts
7
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
8
+ import { dirname, join, resolve } from "path";
9
+ function parseAddArgs(argv) {
10
+ const name = argv[0];
11
+ if (!name || name.startsWith("-")) {
12
+ throw cliError(
13
+ CLI_ERROR_CODES.UNKNOWN_OPTION,
14
+ "mountly-mcp add requires a View name",
15
+ "Example: mountly-mcp add settings --framework react"
16
+ );
17
+ }
18
+ const args = { name };
19
+ for (let i = 1; i < argv.length; i += 1) {
20
+ const arg = argv[i];
21
+ const next = () => {
22
+ const value = argv[i + 1];
23
+ if (value === void 0) {
24
+ throw cliError(
25
+ CLI_ERROR_CODES.UNKNOWN_OPTION,
26
+ `${arg} needs a value`,
27
+ "See: mountly-mcp --help"
28
+ );
29
+ }
30
+ i += 1;
31
+ return value;
32
+ };
33
+ if (arg === "--entry") args.entry = next();
34
+ else if (arg === "--uri") args.uri = next();
35
+ else if (arg === "--framework" || arg === "-f") {
36
+ const value = next();
37
+ if (!["react", "vue", "svelte", "vanilla"].includes(value)) {
38
+ throw cliError(
39
+ CLI_ERROR_CODES.UNKNOWN_OPTION,
40
+ `framework "${value}" not supported`,
41
+ "Use: react | vue | svelte | vanilla"
42
+ );
43
+ }
44
+ args.framework = value;
45
+ } else if (arg === "--config" || arg === "-c") args.config = next();
46
+ else {
47
+ throw cliError(
48
+ CLI_ERROR_CODES.UNKNOWN_OPTION,
49
+ `unknown add option '${arg}'`,
50
+ "Options: --entry --uri --framework --config"
51
+ );
52
+ }
53
+ }
54
+ return args;
55
+ }
56
+ function detectFramework(cwd) {
57
+ const pkgPath = join(cwd, "package.json");
58
+ if (!existsSync(pkgPath)) return "react";
59
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
60
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
61
+ if (deps.vue || deps["mountly-vue"]) return "vue";
62
+ if (deps.svelte || deps["mountly-svelte"]) return "svelte";
63
+ if (deps.react) return "react";
64
+ return "vanilla";
65
+ }
66
+ function findViteConfig(cwd, explicit) {
67
+ if (explicit) {
68
+ const path = resolve(cwd, explicit);
69
+ if (!existsSync(path)) {
70
+ throw cliError(
71
+ CLI_ERROR_CODES.NO_VITE_CONFIG,
72
+ `vite config not found: ${path}`,
73
+ "Pass --config path/to/vite.config.ts"
74
+ );
75
+ }
76
+ return path;
77
+ }
78
+ for (const name of ["vite.config.ts", "vite.config.mts", "vite.config.js", "vite.config.mjs"]) {
79
+ const path = join(cwd, name);
80
+ if (existsSync(path)) return path;
81
+ }
82
+ throw cliError(
83
+ CLI_ERROR_CODES.NO_VITE_CONFIG,
84
+ "no vite.config.* in this directory",
85
+ "Run from a Mountly MCP project, or: npx mountly-mcp create my-app --framework react"
86
+ );
87
+ }
88
+ function slugify(name) {
89
+ return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
90
+ }
91
+ function viewSource(framework, componentName) {
92
+ if (framework === "react") {
93
+ return `import { createMcpView, useToolResult } from "mountly-mcp/react";
94
+
95
+ interface Result {
96
+ structuredContent?: { title?: string };
97
+ }
98
+
99
+ function ${componentName}() {
100
+ const result = useToolResult<Result>();
101
+ const title = result?.structuredContent?.title ?? "${componentName}";
102
+ return (
103
+ <main style={{ fontFamily: "system-ui", padding: 20 }}>
104
+ <h1>{title}</h1>
105
+ <p>Edit <code>src/${slugify(componentName)}.tsx</code> \u2014 data comes from useToolResult().</p>
106
+ </main>
107
+ );
108
+ }
109
+
110
+ createMcpView(${componentName});
111
+ `;
112
+ }
113
+ if (framework === "vue") {
114
+ return `import { createMcpView } from "mountly-mcp/vue";
115
+ import ${componentName} from "./${componentName}.vue";
116
+
117
+ createMcpView(${componentName});
118
+ `;
119
+ }
120
+ if (framework === "svelte") {
121
+ return `import { createMcpView } from "mountly-mcp/svelte";
122
+ import ${componentName} from "./${componentName}.svelte";
123
+
124
+ createMcpView(${componentName});
125
+ `;
126
+ }
127
+ return `import { publishMcpView, type McpViewProps } from "mountly-mcp";
128
+
129
+ publishMcpView({
130
+ mount(el, props: McpViewProps) {
131
+ el.innerHTML = "<main style=\\"font-family:system-ui;padding:20px\\"><h1>${componentName}</h1><pre></pre></main>";
132
+ const pre = el.querySelector("pre");
133
+ if (pre) pre.textContent = JSON.stringify(props.toolResult?.structuredContent ?? {}, null, 2);
134
+ },
135
+ update(el, props: McpViewProps) {
136
+ const pre = el.querySelector("pre");
137
+ if (pre) pre.textContent = JSON.stringify(props.toolResult?.structuredContent ?? {}, null, 2);
138
+ },
139
+ unmount(el) {
140
+ el.innerHTML = "";
141
+ },
142
+ });
143
+ `;
144
+ }
145
+ function vueComponent(componentName) {
146
+ return `<script setup lang="ts">
147
+ import { computed } from "vue";
148
+ import { useToolResult } from "mountly-mcp/vue";
149
+
150
+ const result = useToolResult<{ structuredContent?: { title?: string } }>();
151
+ const title = computed(() => result.value?.structuredContent?.title ?? "${componentName}");
152
+ </script>
153
+
154
+ <template>
155
+ <main style="font-family: system-ui; padding: 20px">
156
+ <h1>{{ title }}</h1>
157
+ <p>Edit this View \u2014 data comes from useToolResult().</p>
158
+ </main>
159
+ </template>
160
+ `;
161
+ }
162
+ function svelteComponent(componentName) {
163
+ return `<script lang="ts">
164
+ let { toolResult } = $props();
165
+ const title = $derived(toolResult?.structuredContent?.title ?? "${componentName}");
166
+ </script>
167
+
168
+ <main style="font-family: system-ui; padding: 20px">
169
+ <h1>{title}</h1>
170
+ <p>Edit this View \u2014 toolResult arrives as a prop.</p>
171
+ </main>
172
+ `;
173
+ }
174
+ function addView(args, cwd = process.cwd()) {
175
+ const framework = args.framework ?? detectFramework(cwd);
176
+ const configPath = findViteConfig(cwd, args.config);
177
+ const configSource = readFileSync(configPath, "utf8");
178
+ if (!/mountlyMcpViews\s*\(/.test(configSource)) {
179
+ throw cliError(
180
+ CLI_ERROR_CODES.MISSING_VITE_PLUGIN,
181
+ `no mountlyMcpViews() in ${configPath}`,
182
+ `Add mountlyMcpViews({ apps: [...] }) from "mountly-mcp/vite" to ${configPath}`
183
+ );
184
+ }
185
+ const slug = slugify(args.name) || "view";
186
+ const pascal = slug.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
187
+ const uri = args.uri ?? `ui://app/${slug}`;
188
+ if (!uri.startsWith("ui://")) {
189
+ throw cliError(
190
+ CLI_ERROR_CODES.INVALID_UI_URI,
191
+ `uri must start with ui:// (received '${uri}')`,
192
+ "Example: --uri ui://my-server/settings"
193
+ );
194
+ }
195
+ const entry = args.entry ?? `src/${slug}.${framework === "react" ? "tsx" : "ts"}`;
196
+ const entryPath = resolve(cwd, entry);
197
+ if (existsSync(entryPath)) {
198
+ throw cliError(
199
+ CLI_ERROR_CODES.TARGET_EXISTS,
200
+ `entry already exists: ${entryPath}`,
201
+ "Pass a different --entry or remove the file first"
202
+ );
203
+ }
204
+ if (configSource.includes(JSON.stringify(uri)) || configSource.includes(JSON.stringify(slug))) {
205
+ throw cliError(
206
+ CLI_ERROR_CODES.TARGET_EXISTS,
207
+ `${configPath} already declares a View named '${slug}' or uri '${uri}'`,
208
+ "Pick another name, or pass --uri to use a different resource URI"
209
+ );
210
+ }
211
+ const appBlock = `{
212
+ entry: ${JSON.stringify(entry)},
213
+ uri: ${JSON.stringify(uri)},
214
+ name: ${JSON.stringify(slug)},
215
+ displayModes: ["inline", "fullscreen"],
216
+ prefersBorder: true,
217
+ awaitToolResult: true,
218
+ }`;
219
+ let next;
220
+ if (/apps:\s*\[\s*\]/.test(configSource)) {
221
+ next = configSource.replace(/apps:\s*\[\s*\]/, `apps: [
222
+ ${appBlock},
223
+ ]`);
224
+ } else if (/apps:\s*\[/.test(configSource)) {
225
+ next = configSource.replace(/apps:\s*\[/, `apps: [
226
+ ${appBlock},`);
227
+ } else {
228
+ throw cliError(
229
+ CLI_ERROR_CODES.MISSING_VITE_PLUGIN,
230
+ `could not find apps: [] in ${configPath}`,
231
+ "Add an apps array inside mountlyMcpViews({ ... })"
232
+ );
233
+ }
234
+ mkdirSync(dirname(entryPath), { recursive: true });
235
+ writeFileSync(entryPath, viewSource(framework, pascal));
236
+ if (framework === "vue") {
237
+ writeFileSync(join(dirname(entryPath), `${pascal}.vue`), vueComponent(pascal));
238
+ }
239
+ if (framework === "svelte") {
240
+ writeFileSync(join(dirname(entryPath), `${pascal}.svelte`), svelteComponent(pascal));
241
+ }
242
+ writeFileSync(configPath, next);
243
+ process.stdout.write(
244
+ [
245
+ "",
246
+ `\u2713 Added View "${slug}"`,
247
+ ` entry ${entry}`,
248
+ ` uri ${uri}`,
249
+ ` config ${configPath}`,
250
+ "",
251
+ "Next:",
252
+ " pnpm exec mountly-mcp build",
253
+ ` pnpm exec mountly-mcp dev --app ${slug}`,
254
+ " Register a tool with resourceUri matching the uri above via registerMcpApps.",
255
+ ""
256
+ ].join("\n")
257
+ );
258
+ }
259
+ export {
260
+ addView,
261
+ parseAddArgs
262
+ };
263
+ //# sourceMappingURL=add-DAFMA4F3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/add.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { CLI_ERROR_CODES, cliError } from \"./errors.js\";\n\nexport interface AddArgs {\n name: string;\n entry?: string;\n uri?: string;\n framework?: \"react\" | \"vue\" | \"svelte\" | \"vanilla\";\n config?: string;\n}\n\nexport function parseAddArgs(argv: ReadonlyArray<string>): AddArgs {\n const name = argv[0];\n if (!name || name.startsWith(\"-\")) {\n throw cliError(\n CLI_ERROR_CODES.UNKNOWN_OPTION,\n \"mountly-mcp add requires a View name\",\n \"Example: mountly-mcp add settings --framework react\",\n );\n }\n\n const args: AddArgs = { name };\n for (let i = 1; i < argv.length; i += 1) {\n const arg = argv[i];\n const next = (): string => {\n const value = argv[i + 1];\n if (value === undefined) {\n throw cliError(\n CLI_ERROR_CODES.UNKNOWN_OPTION,\n `${arg} needs a value`,\n \"See: mountly-mcp --help\",\n );\n }\n i += 1;\n return value;\n };\n if (arg === \"--entry\") args.entry = next();\n else if (arg === \"--uri\") args.uri = next();\n else if (arg === \"--framework\" || arg === \"-f\") {\n const value = next();\n if (![\"react\", \"vue\", \"svelte\", \"vanilla\"].includes(value)) {\n throw cliError(\n CLI_ERROR_CODES.UNKNOWN_OPTION,\n `framework \"${value}\" not supported`,\n \"Use: react | vue | svelte | vanilla\",\n );\n }\n args.framework = value as AddArgs[\"framework\"];\n } else if (arg === \"--config\" || arg === \"-c\") args.config = next();\n else {\n throw cliError(\n CLI_ERROR_CODES.UNKNOWN_OPTION,\n `unknown add option '${arg}'`,\n \"Options: --entry --uri --framework --config\",\n );\n }\n }\n return args;\n}\n\nfunction detectFramework(cwd: string): NonNullable<AddArgs[\"framework\"]> {\n const pkgPath = join(cwd, \"package.json\");\n if (!existsSync(pkgPath)) return \"react\";\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf8\")) as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n };\n const deps = { ...pkg.dependencies, ...pkg.devDependencies };\n if (deps.vue || deps[\"mountly-vue\"]) return \"vue\";\n if (deps.svelte || deps[\"mountly-svelte\"]) return \"svelte\";\n if (deps.react) return \"react\";\n return \"vanilla\";\n}\n\nfunction findViteConfig(cwd: string, explicit?: string): string {\n if (explicit) {\n const path = resolve(cwd, explicit);\n if (!existsSync(path)) {\n throw cliError(\n CLI_ERROR_CODES.NO_VITE_CONFIG,\n `vite config not found: ${path}`,\n \"Pass --config path/to/vite.config.ts\",\n );\n }\n return path;\n }\n for (const name of [\"vite.config.ts\", \"vite.config.mts\", \"vite.config.js\", \"vite.config.mjs\"]) {\n const path = join(cwd, name);\n if (existsSync(path)) return path;\n }\n throw cliError(\n CLI_ERROR_CODES.NO_VITE_CONFIG,\n \"no vite.config.* in this directory\",\n \"Run from a Mountly MCP project, or: npx mountly-mcp create my-app --framework react\",\n );\n}\n\nfunction slugify(name: string): string {\n return name\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n}\n\nfunction viewSource(framework: NonNullable<AddArgs[\"framework\"]>, componentName: string): string {\n if (framework === \"react\") {\n return `import { createMcpView, useToolResult } from \"mountly-mcp/react\";\n\ninterface Result {\n structuredContent?: { title?: string };\n}\n\nfunction ${componentName}() {\n const result = useToolResult<Result>();\n const title = result?.structuredContent?.title ?? \"${componentName}\";\n return (\n <main style={{ fontFamily: \"system-ui\", padding: 20 }}>\n <h1>{title}</h1>\n <p>Edit <code>src/${slugify(componentName)}.tsx</code> — data comes from useToolResult().</p>\n </main>\n );\n}\n\ncreateMcpView(${componentName});\n`;\n }\n if (framework === \"vue\") {\n return `import { createMcpView } from \"mountly-mcp/vue\";\nimport ${componentName} from \"./${componentName}.vue\";\n\ncreateMcpView(${componentName});\n`;\n }\n if (framework === \"svelte\") {\n return `import { createMcpView } from \"mountly-mcp/svelte\";\nimport ${componentName} from \"./${componentName}.svelte\";\n\ncreateMcpView(${componentName});\n`;\n }\n return `import { publishMcpView, type McpViewProps } from \"mountly-mcp\";\n\npublishMcpView({\n mount(el, props: McpViewProps) {\n el.innerHTML = \"<main style=\\\\\"font-family:system-ui;padding:20px\\\\\"><h1>${componentName}</h1><pre></pre></main>\";\n const pre = el.querySelector(\"pre\");\n if (pre) pre.textContent = JSON.stringify(props.toolResult?.structuredContent ?? {}, null, 2);\n },\n update(el, props: McpViewProps) {\n const pre = el.querySelector(\"pre\");\n if (pre) pre.textContent = JSON.stringify(props.toolResult?.structuredContent ?? {}, null, 2);\n },\n unmount(el) {\n el.innerHTML = \"\";\n },\n});\n`;\n}\n\nfunction vueComponent(componentName: string): string {\n return `<script setup lang=\"ts\">\nimport { computed } from \"vue\";\nimport { useToolResult } from \"mountly-mcp/vue\";\n\nconst result = useToolResult<{ structuredContent?: { title?: string } }>();\nconst title = computed(() => result.value?.structuredContent?.title ?? \"${componentName}\");\n</script>\n\n<template>\n <main style=\"font-family: system-ui; padding: 20px\">\n <h1>{{ title }}</h1>\n <p>Edit this View — data comes from useToolResult().</p>\n </main>\n</template>\n`;\n}\n\nfunction svelteComponent(componentName: string): string {\n return `<script lang=\"ts\">\n let { toolResult } = $props();\n const title = $derived(toolResult?.structuredContent?.title ?? \"${componentName}\");\n</script>\n\n<main style=\"font-family: system-ui; padding: 20px\">\n <h1>{title}</h1>\n <p>Edit this View — toolResult arrives as a prop.</p>\n</main>\n`;\n}\n\n/**\n * Add a second View to an existing Mountly MCP Vite project: entry file +\n * `apps[]` row in vite.config. Does not rewrite the user's server.\n */\nexport function addView(args: AddArgs, cwd = process.cwd()): void {\n const framework: NonNullable<AddArgs[\"framework\"]> = args.framework ?? detectFramework(cwd);\n const configPath = findViteConfig(cwd, args.config);\n const configSource = readFileSync(configPath, \"utf8\");\n if (!/mountlyMcpViews\\s*\\(/.test(configSource)) {\n throw cliError(\n CLI_ERROR_CODES.MISSING_VITE_PLUGIN,\n `no mountlyMcpViews() in ${configPath}`,\n `Add mountlyMcpViews({ apps: [...] }) from \"mountly-mcp/vite\" to ${configPath}`,\n );\n }\n\n const slug = slugify(args.name) || \"view\";\n const pascal = slug\n .split(\"-\")\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join(\"\");\n const uri = args.uri ?? `ui://app/${slug}`;\n if (!uri.startsWith(\"ui://\")) {\n throw cliError(\n CLI_ERROR_CODES.INVALID_UI_URI,\n `uri must start with ui:// (received '${uri}')`,\n \"Example: --uri ui://my-server/settings\",\n );\n }\n\n const entry = args.entry ?? `src/${slug}.${framework === \"react\" ? \"tsx\" : \"ts\"}`;\n const entryPath = resolve(cwd, entry);\n if (existsSync(entryPath)) {\n throw cliError(\n CLI_ERROR_CODES.TARGET_EXISTS,\n `entry already exists: ${entryPath}`,\n \"Pass a different --entry or remove the file first\",\n );\n }\n if (configSource.includes(JSON.stringify(uri)) || configSource.includes(JSON.stringify(slug))) {\n throw cliError(\n CLI_ERROR_CODES.TARGET_EXISTS,\n `${configPath} already declares a View named '${slug}' or uri '${uri}'`,\n \"Pick another name, or pass --uri to use a different resource URI\",\n );\n }\n\n const appBlock = `{\n entry: ${JSON.stringify(entry)},\n uri: ${JSON.stringify(uri)},\n name: ${JSON.stringify(slug)},\n displayModes: [\"inline\", \"fullscreen\"],\n prefersBorder: true,\n awaitToolResult: true,\n }`;\n\n // Rewrite the config in memory first: a failure here must not leave a View\n // entry on disk that nothing builds.\n let next: string;\n if (/apps:\\s*\\[\\s*\\]/.test(configSource)) {\n next = configSource.replace(/apps:\\s*\\[\\s*\\]/, `apps: [\\n ${appBlock},\\n ]`);\n } else if (/apps:\\s*\\[/.test(configSource)) {\n next = configSource.replace(/apps:\\s*\\[/, `apps: [\\n ${appBlock},`);\n } else {\n throw cliError(\n CLI_ERROR_CODES.MISSING_VITE_PLUGIN,\n `could not find apps: [] in ${configPath}`,\n \"Add an apps array inside mountlyMcpViews({ ... })\",\n );\n }\n\n mkdirSync(dirname(entryPath), { recursive: true });\n writeFileSync(entryPath, viewSource(framework, pascal));\n if (framework === \"vue\") {\n writeFileSync(join(dirname(entryPath), `${pascal}.vue`), vueComponent(pascal));\n }\n if (framework === \"svelte\") {\n writeFileSync(join(dirname(entryPath), `${pascal}.svelte`), svelteComponent(pascal));\n }\n writeFileSync(configPath, next);\n\n process.stdout.write(\n [\n \"\",\n `✓ Added View \"${slug}\"`,\n ` entry ${entry}`,\n ` uri ${uri}`,\n ` config ${configPath}`,\n \"\",\n \"Next:\",\n \" pnpm exec mountly-mcp build\",\n ` pnpm exec mountly-mcp dev --app ${slug}`,\n \" Register a tool with resourceUri matching the uri above via registerMcpApps.\",\n \"\",\n ].join(\"\\n\"),\n );\n}\n"],"mappings":"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","names":[]}
package/dist/add.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ export interface AddArgs {
2
+ name: string;
3
+ entry?: string;
4
+ uri?: string;
5
+ framework?: "react" | "vue" | "svelte" | "vanilla";
6
+ config?: string;
7
+ }
8
+ export declare function parseAddArgs(argv: ReadonlyArray<string>): AddArgs;
9
+ /**
10
+ * Add a second View to an existing Mountly MCP Vite project: entry file +
11
+ * `apps[]` row in vite.config. Does not rewrite the user's server.
12
+ */
13
+ export declare function addView(args: AddArgs, cwd?: string): void;
@@ -0,0 +1,34 @@
1
+ // src/errors.ts
2
+ var CLI_ERROR_CODES = {
3
+ MISSING_VITE_PLUGIN: "mountly-mcp/missing-vite-plugin",
4
+ NO_VITE_CONFIG: "mountly-mcp/no-vite-config",
5
+ UNKNOWN_VIEW: "mountly-mcp/unknown-view",
6
+ MULTI_VIEW_NEEDS_APP: "mountly-mcp/multi-view-needs-app",
7
+ INVALID_UI_URI: "mountly-mcp/invalid-ui-uri",
8
+ BAD_SERVER_EXPORT: "mountly-mcp/bad-server-export",
9
+ NO_TOOL_FOR_VIEW: "mountly-mcp/no-tool-for-view",
10
+ UNKNOWN_OPTION: "mountly-mcp/unknown-option",
11
+ UNKNOWN_COMMAND: "mountly-mcp/unknown-command",
12
+ TARGET_EXISTS: "mountly-mcp/target-exists",
13
+ MISSING_TEMPLATE: "mountly-mcp/missing-template",
14
+ DOCTOR_FAILED: "mountly-mcp/doctor-failed"
15
+ };
16
+ var MountlyMcpCliError = class extends Error {
17
+ constructor(code, message, next) {
18
+ super(`${code}: ${message}
19
+ Next: ${next}`);
20
+ this.name = "MountlyMcpCliError";
21
+ this.code = code;
22
+ this.next = next;
23
+ }
24
+ };
25
+ function cliError(code, message, next) {
26
+ return new MountlyMcpCliError(code, message, next);
27
+ }
28
+
29
+ export {
30
+ CLI_ERROR_CODES,
31
+ MountlyMcpCliError,
32
+ cliError
33
+ };
34
+ //# sourceMappingURL=chunk-EOWAWZH2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts"],"sourcesContent":["/**\n * LLM-shaped CLI errors: a stable code plus the next command or file to open.\n * Skills and agents should key off the `mountly-mcp/…` code prefix.\n */\n\nexport const CLI_ERROR_CODES = {\n MISSING_VITE_PLUGIN: \"mountly-mcp/missing-vite-plugin\",\n NO_VITE_CONFIG: \"mountly-mcp/no-vite-config\",\n UNKNOWN_VIEW: \"mountly-mcp/unknown-view\",\n MULTI_VIEW_NEEDS_APP: \"mountly-mcp/multi-view-needs-app\",\n INVALID_UI_URI: \"mountly-mcp/invalid-ui-uri\",\n BAD_SERVER_EXPORT: \"mountly-mcp/bad-server-export\",\n NO_TOOL_FOR_VIEW: \"mountly-mcp/no-tool-for-view\",\n UNKNOWN_OPTION: \"mountly-mcp/unknown-option\",\n UNKNOWN_COMMAND: \"mountly-mcp/unknown-command\",\n TARGET_EXISTS: \"mountly-mcp/target-exists\",\n MISSING_TEMPLATE: \"mountly-mcp/missing-template\",\n DOCTOR_FAILED: \"mountly-mcp/doctor-failed\",\n} as const;\n\nexport type CliErrorCode = (typeof CLI_ERROR_CODES)[keyof typeof CLI_ERROR_CODES];\n\nexport class MountlyMcpCliError extends Error {\n readonly code: CliErrorCode;\n readonly next: string;\n\n constructor(code: CliErrorCode, message: string, next: string) {\n super(`${code}: ${message}\\n Next: ${next}`);\n this.name = \"MountlyMcpCliError\";\n this.code = code;\n this.next = next;\n }\n}\n\nexport function cliError(code: CliErrorCode, message: string, next: string): MountlyMcpCliError {\n return new MountlyMcpCliError(code, message, next);\n}\n"],"mappings":";AAKO,IAAM,kBAAkB;AAAA,EAC7B,qBAAqB;AAAA,EACrB,gBAAgB;AAAA,EAChB,cAAc;AAAA,EACd,sBAAsB;AAAA,EACtB,gBAAgB;AAAA,EAChB,mBAAmB;AAAA,EACnB,kBAAkB;AAAA,EAClB,gBAAgB;AAAA,EAChB,iBAAiB;AAAA,EACjB,eAAe;AAAA,EACf,kBAAkB;AAAA,EAClB,eAAe;AACjB;AAIO,IAAM,qBAAN,cAAiC,MAAM;AAAA,EAI5C,YAAY,MAAoB,SAAiB,MAAc;AAC7D,UAAM,GAAG,IAAI,KAAK,OAAO;AAAA,UAAa,IAAI,EAAE;AAC5C,SAAK,OAAO;AACZ,SAAK,OAAO;AACZ,SAAK,OAAO;AAAA,EACd;AACF;AAEO,SAAS,SAAS,MAAoB,SAAiB,MAAkC;AAC9F,SAAO,IAAI,mBAAmB,MAAM,SAAS,IAAI;AACnD;","names":[]}
@@ -579,4 +579,4 @@ export {
579
579
  startDevHost,
580
580
  sandboxProxyHtml
581
581
  };
582
- //# sourceMappingURL=chunk-KWVGW5Q7.js.map
582
+ //# sourceMappingURL=chunk-GN2IQYV3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/dev/index.ts"],"sourcesContent":["/**\n * A local MCP Apps host, so a View can be developed without installing a\n * real host and restarting it on every change.\n *\n * It is a genuine host, not a preview shim: two origins as the spec requires\n * (§8.4), a sandbox proxy that injects the sidecar's CSP and permissions, the\n * full `ui/initialize` handshake, and tool-input/tool-result delivery. A View\n * that works here works in Claude, and one that reaches for `eval` fails here\n * for the same reason it would fail there.\n *\n * ```ts\n * const host = await startDevHost({ htmlPath: \"dist/view.html\", fixtures });\n * console.log(host.hostUrl);\n * ```\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { createServer, type Server } from \"node:http\";\nimport { fileURLToPath } from \"node:url\";\nimport type { McpResourceDeclaration } from \"../types.js\";\nimport { escapeInlineScript, serializeInlineScriptValue } from \"../html.js\";\n\nexport interface DevHostOptions {\n /** Built View HTML. Its `.meta.json` sidecar is read alongside. */\n htmlPath: string;\n /**\n * Named samples, each becoming a button in the host chrome.\n *\n * Without `callTool` the value is delivered straight through as\n * `structuredContent`. With `callTool` it is the tool's *arguments*, and\n * what the view receives is whatever the real tool returns.\n */\n fixtures?: Record<string, unknown>;\n /**\n * Run a real tool. Supplied when the developer points `dev` at their server,\n * and used both for the fixture buttons and for `tools/call` requests the\n * view makes itself — so app-only tools behave as they will in production.\n */\n callTool?: (name: string, args: unknown) => Promise<unknown>;\n /** Arguments delivered as `ui/notifications/tool-input`. */\n toolInput?: Record<string, unknown>;\n /** Shown in the host chrome. Defaults to the sidecar's resource name. */\n toolName?: string;\n /** Defaults to 5179, incrementing until a free port is found. */\n hostPort?: number;\n /** Defaults to `hostPort + 1`, incrementing until a free port is found. */\n sandboxPort?: number;\n}\n\nexport interface DevHost {\n hostUrl: string;\n sandboxUrl: string;\n /** Tell connected browsers to reload — call after a rebuild. */\n reload(): void;\n close(): Promise<void>;\n}\n\nfunction escapeHtml(value: string): string {\n return value.replace(\n /[&<>\"']/g,\n (character) =>\n ({ \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", '\"': \"&quot;\", \"'\": \"&#39;\" })[character]!,\n );\n}\n\nconst runtimeCache = new Map<string, Promise<string>>();\n\n/**\n * Load one of the browser-side runtimes (`host-entry`, `sandbox-entry`) as a\n * string to inline into a page.\n *\n * Installed, the built IIFE is a declared package export, so ask the module\n * resolver for it. Walking from `import.meta.url` breaks the moment a bundler\n * hoists this module into a shared chunk at a different depth — which is what\n * tsup does, leaving it looking in dist/ while the file sits in dist/dev/.\n */\nfunction loadRuntime(name: \"host-entry\" | \"sandbox-entry\"): Promise<string> {\n const cached = runtimeCache.get(name);\n if (cached) return cached;\n const loading = (async () => {\n try {\n return await readFile(\n fileURLToPath(import.meta.resolve(`mountly-mcp/dev/${name}.js`)),\n \"utf8\",\n );\n } catch {\n // Running from source in this repo, where nothing is built yet.\n }\n const entry = fileURLToPath(new URL(`./${name}.ts`, import.meta.url));\n if (!existsSync(entry)) {\n throw new Error(\n `mountly-mcp dev: ${name} runtime missing. Expected the packaged 'mountly-mcp/dev/${name}.js' export, or ${entry} when running from source.`,\n );\n }\n const { build } = await import(\"vite\");\n const result = await build({\n configFile: false,\n logLevel: \"silent\",\n build: {\n write: false,\n target: \"es2022\",\n minify: false,\n lib: { entry, formats: [\"iife\"], name: \"__mountlyMcpDevRuntime\" },\n },\n });\n const outputs = Array.isArray(result) ? result : [result];\n const chunk = outputs\n .flatMap((output) => (\"output\" in output ? output.output : []))\n .find((item) => item.type === \"chunk\");\n if (!chunk || chunk.type !== \"chunk\")\n throw new Error(`mountly-mcp dev: failed to bundle ${name}`);\n return chunk.code;\n })();\n runtimeCache.set(name, loading);\n return loading;\n}\n\nfunction listenOnFreePort(server: Server, port: number): Promise<number> {\n return new Promise((resolvePort, reject) => {\n const attempt = (candidate: number): void => {\n // Listeners are paired and torn down together: `listen()`'s callback form\n // leaves a stale one attached across retries, which then resolves with\n // the port that was already taken.\n const onError = (error: NodeJS.ErrnoException): void => {\n server.removeListener(\"listening\", onListening);\n // A linear probe is enough for a dev tool on a developer's laptop.\n if (error.code === \"EADDRINUSE\" && candidate < port + 50) attempt(candidate + 1);\n else reject(error);\n };\n const onListening = (): void => {\n server.removeListener(\"error\", onError);\n const address = server.address();\n resolvePort(typeof address === \"object\" && address !== null ? address.port : candidate);\n };\n server.once(\"error\", onError);\n server.once(\"listening\", onListening);\n server.listen(candidate);\n };\n attempt(port);\n });\n}\n\nexport async function startDevHost(options: DevHostOptions): Promise<DevHost> {\n const { htmlPath, fixtures = {}, toolInput = {}, callTool } = options;\n\n const metaPath = `${htmlPath}.meta.json`;\n const initialMeta = JSON.parse(await readFile(metaPath, \"utf8\")) as McpResourceDeclaration;\n const toolName = options.toolName ?? initialMeta.name;\n const [hostRuntime, sandboxRuntime] = await Promise.all([\n loadRuntime(\"host-entry\"),\n loadRuntime(\"sandbox-entry\"),\n ]);\n\n // Bumped on reload(); the browser polls it and reloads when it changes. The\n // View HTML is read per request rather than baked into the page, so a\n // rebuild is picked up with no server restart.\n let version = 1;\n\n // The two servers reference each other's origin, and each only needs the\n // other's once a request arrives — so the sandbox reads this after the host\n // has bound its port.\n let hostOrigin = \"\";\n\n const sandboxServer = createServer((_req, res) => {\n res.writeHead(200, { \"Content-Type\": \"text/html; charset=utf-8\", \"Cache-Control\": \"no-store\" });\n res.end(sandboxProxyShell(hostOrigin, sandboxRuntime));\n });\n const sandboxPort = await listenOnFreePort(\n sandboxServer,\n options.sandboxPort ?? (options.hostPort ?? 5179) + 1,\n );\n const sandboxOrigin = `http://localhost:${sandboxPort}`;\n\n const hostServer = createServer(async (req, res) => {\n const path = new URL(req.url ?? \"/\", \"http://localhost\").pathname;\n const send = (status: number, type: string, body: string | Buffer): void => {\n res.writeHead(status, { \"Content-Type\": type, \"Cache-Control\": \"no-store\" });\n res.end(body);\n };\n try {\n if (path === \"/call\" && req.method === \"POST\") {\n if (!callTool) return send(400, \"text/plain\", \"no server connected\");\n const body = await new Promise<string>((done, fail) => {\n let raw = \"\";\n req.on(\"data\", (chunk) => (raw += chunk));\n req.on(\"end\", () => done(raw));\n req.on(\"error\", fail);\n });\n const { name, arguments: args } = JSON.parse(body) as { name: string; arguments?: unknown };\n // Tool failures are the view's problem to render, not the dev server's\n // to crash on, so they come back as a normal result.\n const result = await callTool(name, args).catch((error: unknown) => ({\n isError: true,\n content: [{ type: \"text\", text: error instanceof Error ? error.message : String(error) }],\n }));\n return send(200, \"application/json\", JSON.stringify(result));\n }\n if (path === \"/\") return send(200, \"text/html; charset=utf-8\", hostPage());\n if (path === \"/host.js\") return send(200, \"text/javascript; charset=utf-8\", hostRuntime);\n if (path === \"/version\") return send(200, \"application/json\", String(version));\n if (path === \"/view.html\")\n return send(200, \"text/plain; charset=utf-8\", await readFile(htmlPath, \"utf8\"));\n if (path === \"/view.meta.json\")\n return send(200, \"application/json\", await readFile(metaPath, \"utf8\"));\n send(404, \"text/plain\", \"not found\");\n } catch (error) {\n // A rebuild in flight can briefly leave the file missing; say so rather\n // than crashing the dev server.\n send(500, \"text/plain\", error instanceof Error ? error.message : String(error));\n }\n });\n let hostPort: number;\n try {\n hostPort = await listenOnFreePort(hostServer, options.hostPort ?? 5179);\n } catch (caught) {\n await new Promise<void>((done) => sandboxServer.close(() => done()));\n throw caught;\n }\n hostOrigin = `http://localhost:${hostPort}`;\n\n function hostPage(): string {\n return devHostHtml({\n toolName,\n sandboxOrigin,\n fixtures,\n toolInput,\n uri: initialMeta.uri,\n hasServer: callTool !== undefined,\n });\n }\n\n return {\n hostUrl: hostOrigin,\n sandboxUrl: sandboxOrigin,\n reload() {\n version += 1;\n },\n async close() {\n await Promise.all([\n new Promise<void>((done) => hostServer.close(() => done())),\n new Promise<void>((done) => sandboxServer.close(() => done())),\n ]);\n },\n };\n}\n\ninterface HostPageOptions {\n toolName: string;\n sandboxOrigin: string;\n fixtures: Record<string, unknown>;\n toolInput: Record<string, unknown>;\n uri: string;\n hasServer: boolean;\n}\n\nfunction devHostHtml(options: HostPageOptions): string {\n const { toolName, sandboxOrigin, fixtures, toolInput, uri, hasServer } = options;\n const buttons = Object.keys(fixtures)\n .map(\n (name, index) =>\n `<button class=\"chip\" type=\"button\" data-fixture=\"${escapeHtml(name)}\"${index === 0 ? ' data-active=\"true\"' : \"\"}>${escapeHtml(name)}</button>`,\n )\n .join(\"\\n\");\n const firstFixture = Object.keys(fixtures)[0] ?? \"sample\";\n\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n<title>${escapeHtml(toolName)} · Mountly MCP</title>\n<style>\n :root {\n color-scheme: light;\n --bg: #f5f5f5;\n --bg-chat: #efefef;\n --fg: #171717;\n --muted: #737373;\n --accent: #0f766e;\n --accent-soft: #ccfbf1;\n --border: #e5e5e5;\n --surface: #ffffff;\n --user-bg: #171717;\n --user-fg: #fafafa;\n --assistant-fg: #171717;\n --composer: #ffffff;\n --shadow: 0 1px 2px rgb(0 0 0 / 4%), 0 8px 24px rgb(0 0 0 / 6%);\n }\n :root[data-theme=\"dark\"] {\n color-scheme: dark;\n --bg: #0a0a0a;\n --bg-chat: #111111;\n --fg: #f5f5f5;\n --muted: #a3a3a3;\n --accent: #2dd4bf;\n --accent-soft: #134e4a;\n --border: #262626;\n --surface: #171717;\n --user-bg: #e5e5e5;\n --user-fg: #0a0a0a;\n --assistant-fg: #f5f5f5;\n --composer: #171717;\n --shadow: 0 1px 2px rgb(0 0 0 / 35%), 0 12px 32px rgb(0 0 0 / 35%);\n }\n * { box-sizing: border-box; }\n html, body { height: 100%; }\n body {\n margin: 0;\n background: var(--bg);\n color: var(--fg);\n font: 15px/1.5 \"Segoe UI\", \"Helvetica Neue\", system-ui, sans-serif;\n }\n .app {\n display: grid;\n grid-template-rows: auto 1fr auto;\n min-height: 100%;\n max-width: 880px;\n margin: 0 auto;\n }\n .topbar {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px 18px;\n border-bottom: 1px solid var(--border);\n background: color-mix(in srgb, var(--surface) 92%, transparent);\n backdrop-filter: blur(10px);\n position: sticky;\n top: 0;\n z-index: 3;\n }\n .model {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n }\n .avatar {\n width: 28px;\n height: 28px;\n border-radius: 8px;\n background:\n linear-gradient(135deg, var(--accent-soft), transparent 60%),\n var(--surface);\n border: 1px solid var(--border);\n flex: 0 0 auto;\n }\n .model h1 {\n margin: 0;\n font-size: 14px;\n font-weight: 600;\n letter-spacing: -0.01em;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n .model .sub {\n color: var(--muted);\n font-size: 12px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n .spacer { flex: 1; }\n .icon-btn {\n appearance: none;\n border: 1px solid var(--border);\n background: var(--surface);\n color: var(--fg);\n width: 34px;\n height: 34px;\n border-radius: 10px;\n font: inherit;\n cursor: pointer;\n }\n .icon-btn:hover { border-color: var(--accent); color: var(--accent); }\n .chat {\n background:\n radial-gradient(900px 420px at 50% -8%, var(--accent-soft), transparent 55%),\n var(--bg-chat);\n padding: 28px 18px 18px;\n display: flex;\n flex-direction: column;\n gap: 22px;\n overflow: auto;\n }\n .turn {\n display: grid;\n grid-template-columns: 32px 1fr;\n gap: 12px;\n align-items: start;\n max-width: 720px;\n width: 100%;\n }\n .turn.user {\n margin-left: auto;\n grid-template-columns: 1fr 32px;\n }\n .turn.user .who { order: 2; }\n .turn.user .body { order: 1; text-align: right; }\n .who {\n width: 32px;\n height: 32px;\n border-radius: 999px;\n display: grid;\n place-items: center;\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.04em;\n border: 1px solid var(--border);\n background: var(--surface);\n color: var(--muted);\n }\n .turn.user .who {\n background: var(--user-bg);\n color: var(--user-fg);\n border-color: transparent;\n }\n .bubble {\n display: inline-block;\n text-align: left;\n background: var(--surface);\n border: 1px solid var(--border);\n border-radius: 18px;\n padding: 12px 14px;\n box-shadow: var(--shadow);\n color: var(--assistant-fg);\n }\n .turn.user .bubble {\n background: var(--user-bg);\n color: var(--user-fg);\n border-color: transparent;\n }\n .bubble p { margin: 0; }\n .artifact {\n margin-top: 10px;\n border: 1px solid var(--border);\n border-radius: 16px;\n overflow: hidden;\n background: var(--surface);\n box-shadow: var(--shadow);\n text-align: left;\n }\n .artifact-bar {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 12px;\n border-bottom: 1px solid var(--border);\n background: color-mix(in srgb, var(--bg-chat) 55%, var(--surface));\n color: var(--muted);\n font-size: 12px;\n }\n .artifact-bar .dot {\n width: 7px;\n height: 7px;\n border-radius: 50%;\n background: var(--accent);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);\n }\n iframe {\n display: block;\n width: 100%;\n height: 420px;\n border: 0;\n background: #fff;\n }\n .composer {\n border-top: 1px solid var(--border);\n background: color-mix(in srgb, var(--composer) 94%, transparent);\n backdrop-filter: blur(10px);\n padding: 12px 18px 16px;\n position: sticky;\n bottom: 0;\n }\n .composer-box {\n border: 1px solid var(--border);\n background: var(--surface);\n border-radius: 18px;\n padding: 10px 12px 12px;\n box-shadow: var(--shadow);\n }\n .composer-hint {\n color: var(--muted);\n font-size: 13px;\n margin: 0 0 10px;\n }\n .samples {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n align-items: center;\n }\n .samples .label {\n color: var(--muted);\n font-size: 12px;\n margin-right: 2px;\n }\n #fixtures { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }\n .chip {\n appearance: none;\n border: 1px solid var(--border);\n background: color-mix(in srgb, var(--bg-chat) 40%, var(--surface));\n color: var(--fg);\n padding: 7px 12px;\n border-radius: 999px;\n font: inherit;\n font-size: 13px;\n cursor: pointer;\n }\n .chip:hover { border-color: var(--accent); color: var(--accent); }\n .chip[data-active=\"true\"] {\n background: var(--accent);\n color: #fff;\n border-color: var(--accent);\n }\n :root[data-theme=\"dark\"] .chip[data-active=\"true\"] { color: #042f2e; }\n details.debug {\n margin-top: 10px;\n border: 1px dashed var(--border);\n border-radius: 14px;\n background: transparent;\n overflow: hidden;\n }\n details.debug > summary {\n cursor: pointer;\n list-style: none;\n padding: 10px 12px;\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n color: var(--muted);\n }\n details.debug > summary::-webkit-details-marker { display: none; }\n .debug-body {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 0;\n border-top: 1px solid var(--border);\n }\n .panel { padding: 12px; min-width: 0; }\n .panel + .panel { border-left: 1px solid var(--border); }\n .panel h2 {\n margin: 0 0 8px;\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n color: var(--muted);\n }\n pre {\n margin: 0;\n padding: 10px;\n border-radius: 8px;\n background: color-mix(in srgb, var(--bg-chat) 80%, transparent);\n font: 12px/1.45 ui-monospace, Menlo, Consolas, monospace;\n overflow: auto;\n max-height: 180px;\n }\n .log {\n font: 11px/1.5 ui-monospace, Menlo, Consolas, monospace;\n color: var(--muted);\n white-space: pre-wrap;\n max-height: 180px;\n overflow: auto;\n }\n @media (max-width: 720px) {\n .debug-body { grid-template-columns: 1fr; }\n .panel + .panel { border-left: 0; border-top: 1px solid var(--border); }\n .turn, .turn.user { grid-template-columns: 28px 1fr; }\n .turn.user .who { order: 0; }\n .turn.user .body { order: 0; text-align: left; }\n }\n</style>\n</head>\n<body>\n<div class=\"app\">\n <header class=\"topbar\">\n <div class=\"model\">\n <div class=\"avatar\" aria-hidden=\"true\"></div>\n <div>\n <h1>Mountly · ${escapeHtml(toolName)}</h1>\n <div class=\"sub\">${escapeHtml(uri)}</div>\n </div>\n </div>\n <span class=\"spacer\"></span>\n <button class=\"icon-btn\" id=\"theme\" type=\"button\" title=\"Toggle theme\" aria-label=\"Toggle theme\">◐</button>\n <button class=\"icon-btn\" id=\"teardown\" type=\"button\" title=\"Teardown\" aria-label=\"Teardown\">×</button>\n </header>\n\n <main class=\"chat\">\n <div class=\"turn user\">\n <div class=\"who\" aria-hidden=\"true\">You</div>\n <div class=\"body\">\n <div class=\"bubble\">\n <p>Call <strong>${escapeHtml(toolName)}</strong> with sample “${escapeHtml(firstFixture)}”.</p>\n </div>\n </div>\n </div>\n\n <div class=\"turn assistant\">\n <div class=\"who\" aria-hidden=\"true\">AI</div>\n <div class=\"body\">\n <div class=\"bubble\">\n <p>Here’s the interactive result from that tool.</p>\n <div class=\"artifact\">\n <div class=\"artifact-bar\"><span class=\"dot\" aria-hidden=\"true\"></span> MCP App · inline</div>\n <iframe id=\"sandbox\" sandbox=\"allow-scripts allow-same-origin\" title=\"mountly-mcp sandbox proxy\"></iframe>\n </div>\n </div>\n </div>\n </div>\n </main>\n\n <footer class=\"composer\">\n <div class=\"composer-box\">\n <p class=\"composer-hint\">Ask another sample — same as switching fixtures in a real host.</p>\n <div class=\"samples\">\n <span class=\"label\">Samples</span>\n <div id=\"fixtures\">${buttons || '<span class=\"label\">No fixtures — add mcp.fixtures.json</span>'}</div>\n </div>\n <details class=\"debug\">\n <summary>Protocol debug</summary>\n <div class=\"debug-body\">\n <section class=\"panel\">\n <h2>Last structuredContent</h2>\n <pre id=\"payload\">(none yet)</pre>\n </section>\n <section class=\"panel\">\n <h2>Channel log</h2>\n <div class=\"log\" id=\"log\"></div>\n </section>\n </div>\n </details>\n </div>\n </footer>\n</div>\n<script>\nglobalThis.__mountlyMcpDevHost__ = ${serializeInlineScriptValue({\n toolName,\n sandboxOrigin,\n fixtures,\n toolInput,\n uri,\n hasServer,\n sandboxPath: \"/sandbox-proxy.html\",\n })};\nconst HAS_SERVER = ${hasServer ? \"true\" : \"false\"};\n</script>\n<script src=\"/host.js\"></script>\n</body>\n</html>\n`;\n}\n/**\n * The proxy document: a shell around the compiled sandbox runtime.\n *\n * Kept separate from {@link sandboxProxyHtml} so the dev server can load the\n * runtime once at startup and still stamp in the host origin per request,\n * which it only learns after binding a port.\n */\nfunction sandboxProxyShell(hostOrigin: string, runtime: string): string {\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<title>mountly-mcp sandbox proxy</title>\n<style>\n html, body { margin: 0; padding: 0; width: 100%; height: 100%; background: transparent; }\n #inner { width: 100%; height: 100%; border: 0; background: white; }\n</style>\n</head>\n<body>\n<script>globalThis.__mountlyMcpSandbox__ = ${serializeInlineScriptValue({ hostOrigin })};</script>\n<script>${escapeInlineScript(runtime)}</script>\n</body>\n</html>\n`;\n}\n\n/**\n * The MCP Apps sandbox proxy document (spec §8.4), ready to serve from an\n * origin distinct from the host page.\n *\n * Exported because this is the security boundary — it builds the CSP from the\n * sidecar and decides what the view may execute and reach. Anything standing\n * up a host, including the example harnesses in this repo, should serve this\n * rather than keep a copy that drifts from it.\n */\nexport async function sandboxProxyHtml(hostOrigin: string): Promise<string> {\n return sandboxProxyShell(hostOrigin, await loadRuntime(\"sandbox-entry\"));\n}\n\nexport { connectMcpServer, type ConnectedMcpServer } from \"./connect-server.js\";\n"],"mappings":"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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,2BAuTL,WAAW,QAAQ,CAAC;AAAA,2BACjB,WAAW,GAAG,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,4BAad,WAAW,QAAQ,CAAC,+BAA0B,WAAW,YAAY,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,6BAwBrE,WAAW,qEAAgE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qCAmBnE,2BAA2B;AAAA,IAC5D;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,aAAa;AAAA,EACf,CAAC,CAAC;AAAA,qBACiB,YAAY,SAAS,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAMjD;AAQA,SAAS,kBAAkB,YAAoB,SAAyB;AACtE,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,6CAWoC,2BAA2B,EAAE,WAAW,CAAC,CAAC;AAAA,UAC7E,mBAAmB,OAAO,CAAC;AAAA;AAAA;AAAA;AAIrC;AAWA,eAAsB,iBAAiB,YAAqC;AAC1E,SAAO,kBAAkB,YAAY,MAAM,YAAY,eAAe,CAAC;AACzE;","names":[]}
@@ -2,10 +2,13 @@
2
2
  async function connectMcpServer(path) {
3
3
  const { pathToFileURL } = await import("url");
4
4
  const { resolve } = await import("path");
5
+ const { CLI_ERROR_CODES, cliError } = await import("./errors-4ERW7HZB.js");
5
6
  const mod = await import(pathToFileURL(resolve(path)).href);
6
7
  if (mod.default === void 0) {
7
- throw new Error(
8
- `mountly-mcp: ${path} has no default export. Export your server (or a function returning it) as default.`
8
+ throw cliError(
9
+ CLI_ERROR_CODES.BAD_SERVER_EXPORT,
10
+ `${path} has no default export`,
11
+ `Export an unconnected McpServer (or a factory returning one) as default. See: docs/mcp-apps/production-integration/`
9
12
  );
10
13
  }
11
14
  const value = typeof mod.default === "function" ? await mod.default() : mod.default;
@@ -26,8 +29,10 @@ async function connectMcpServer(path) {
26
29
  });
27
30
  await client.connect(clientTransport);
28
31
  } else {
29
- throw new TypeError(
30
- `mountly-mcp: ${path} must default-export an MCP server, RunningMcpAppServer, or a function returning one`
32
+ throw cliError(
33
+ CLI_ERROR_CODES.BAD_SERVER_EXPORT,
34
+ `${path} must default-export an MCP server, RunningMcpAppServer, or a function returning one`,
35
+ "Use an unconnected McpServer factory. Do not connect() before exporting. Example: export default async function createServer() { ... return server; }"
31
36
  );
32
37
  }
33
38
  return {
@@ -49,4 +54,4 @@ async function connectMcpServer(path) {
49
54
  export {
50
55
  connectMcpServer
51
56
  };
52
- //# sourceMappingURL=chunk-UECZA34Q.js.map
57
+ //# sourceMappingURL=chunk-OWAXBNZA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/dev/connect-server.ts"],"sourcesContent":["import type { McpUiToolMeta } from \"@modelcontextprotocol/ext-apps\";\n\nexport interface ConnectedMcpServer {\n callTool: (name: string, args: unknown) => Promise<unknown>;\n /** The model-visible tool bound to `uri` — the one a host would call. */\n toolFor(uri: string): Promise<string | undefined>;\n close(): Promise<void>;\n}\n\n/**\n * Connect a server module in-process for a development session.\n *\n * The module default-exports either the server or a function returning it: a\n * `RunningMcpAppServer` from `createMcpAppServer`, or a raw `McpServer`.\n */\nexport async function connectMcpServer(path: string): Promise<ConnectedMcpServer> {\n const { pathToFileURL } = await import(\"node:url\");\n const { resolve } = await import(\"node:path\");\n const { CLI_ERROR_CODES, cliError } = await import(\"../errors.js\");\n const mod = (await import(pathToFileURL(resolve(path)).href)) as { default?: unknown };\n if (mod.default === undefined) {\n throw cliError(\n CLI_ERROR_CODES.BAD_SERVER_EXPORT,\n `${path} has no default export`,\n `Export an unconnected McpServer (or a factory returning one) as default. See: docs/mcp-apps/production-integration/`,\n );\n }\n const value = typeof mod.default === \"function\" ? await mod.default() : mod.default;\n\n let client: import(\"@modelcontextprotocol/sdk/client/index.js\").Client;\n if (\n value !== null &&\n typeof value === \"object\" &&\n \"connectInProcess\" in value &&\n typeof value.connectInProcess === \"function\"\n ) {\n const running = value as { connectInProcess(): Promise<{ client: typeof client }> };\n ({ client } = await running.connectInProcess());\n } else if (\n value !== null &&\n typeof value === \"object\" &&\n \"connect\" in value &&\n typeof value.connect === \"function\"\n ) {\n const [{ Client }, { InMemoryTransport }, { EXTENSION_ID, RESOURCE_MIME_TYPE }] =\n await Promise.all([\n import(\"@modelcontextprotocol/sdk/client/index.js\"),\n import(\"@modelcontextprotocol/sdk/inMemory.js\"),\n import(\"@modelcontextprotocol/ext-apps/server\"),\n ]);\n const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();\n await (value as { connect(transport: unknown): Promise<void> }).connect(serverTransport);\n client = new Client({ name: \"mountly-mcp-dev\", version: \"0.0.1\" }, {\n capabilities: { extensions: { [EXTENSION_ID]: { mimeTypes: [RESOURCE_MIME_TYPE] } } },\n } as never);\n await client.connect(clientTransport);\n } else {\n throw cliError(\n CLI_ERROR_CODES.BAD_SERVER_EXPORT,\n `${path} must default-export an MCP server, RunningMcpAppServer, or a function returning one`,\n \"Use an unconnected McpServer factory. Do not connect() before exporting. Example: export default async function createServer() { ... return server; }\",\n );\n }\n\n return {\n callTool: (name, args) =>\n client.callTool({ name, arguments: (args ?? {}) as Record<string, unknown> }),\n async toolFor(uri) {\n const { tools } = await client.listTools();\n const uiMeta = (tool: { _meta?: unknown }): McpUiToolMeta | undefined =>\n (tool._meta as { ui?: McpUiToolMeta } | undefined)?.ui;\n const bound = tools.filter((tool) => uiMeta(tool)?.resourceUri === uri);\n const visible = bound.find((tool) => {\n const visibility = uiMeta(tool)?.visibility;\n return visibility === undefined || visibility.includes(\"model\");\n });\n return (visible ?? bound[0])?.name;\n },\n close: () => client.close(),\n };\n}\n"],"mappings":";AAeA,eAAsB,iBAAiB,MAA2C;AAChF,QAAM,EAAE,cAAc,IAAI,MAAM,OAAO,KAAU;AACjD,QAAM,EAAE,QAAQ,IAAI,MAAM,OAAO,MAAW;AAC5C,QAAM,EAAE,iBAAiB,SAAS,IAAI,MAAM,OAAO,sBAAc;AACjE,QAAM,MAAO,MAAM,OAAO,cAAc,QAAQ,IAAI,CAAC,EAAE;AACvD,MAAI,IAAI,YAAY,QAAW;AAC7B,UAAM;AAAA,MACJ,gBAAgB;AAAA,MAChB,GAAG,IAAI;AAAA,MACP;AAAA,IACF;AAAA,EACF;AACA,QAAM,QAAQ,OAAO,IAAI,YAAY,aAAa,MAAM,IAAI,QAAQ,IAAI,IAAI;AAE5E,MAAI;AACJ,MACE,UAAU,QACV,OAAO,UAAU,YACjB,sBAAsB,SACtB,OAAO,MAAM,qBAAqB,YAClC;AACA,UAAM,UAAU;AAChB,KAAC,EAAE,OAAO,IAAI,MAAM,QAAQ,iBAAiB;AAAA,EAC/C,WACE,UAAU,QACV,OAAO,UAAU,YACjB,aAAa,SACb,OAAO,MAAM,YAAY,YACzB;AACA,UAAM,CAAC,EAAE,OAAO,GAAG,EAAE,kBAAkB,GAAG,EAAE,cAAc,mBAAmB,CAAC,IAC5E,MAAM,QAAQ,IAAI;AAAA,MAChB,OAAO,2CAA2C;AAAA,MAClD,OAAO,uCAAuC;AAAA,MAC9C,OAAO,uCAAuC;AAAA,IAChD,CAAC;AACH,UAAM,CAAC,iBAAiB,eAAe,IAAI,kBAAkB,iBAAiB;AAC9E,UAAO,MAAyD,QAAQ,eAAe;AACvF,aAAS,IAAI,OAAO,EAAE,MAAM,mBAAmB,SAAS,QAAQ,GAAG;AAAA,MACjE,cAAc,EAAE,YAAY,EAAE,CAAC,YAAY,GAAG,EAAE,WAAW,CAAC,kBAAkB,EAAE,EAAE,EAAE;AAAA,IACtF,CAAU;AACV,UAAM,OAAO,QAAQ,eAAe;AAAA,EACtC,OAAO;AACL,UAAM;AAAA,MACJ,gBAAgB;AAAA,MAChB,GAAG,IAAI;AAAA,MACP;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,UAAU,CAAC,MAAM,SACf,OAAO,SAAS,EAAE,MAAM,WAAY,QAAQ,CAAC,EAA8B,CAAC;AAAA,IAC9E,MAAM,QAAQ,KAAK;AACjB,YAAM,EAAE,MAAM,IAAI,MAAM,OAAO,UAAU;AACzC,YAAM,SAAS,CAAC,SACb,KAAK,OAA8C;AACtD,YAAM,QAAQ,MAAM,OAAO,CAAC,SAAS,OAAO,IAAI,GAAG,gBAAgB,GAAG;AACtE,YAAM,UAAU,MAAM,KAAK,CAAC,SAAS;AACnC,cAAM,aAAa,OAAO,IAAI,GAAG;AACjC,eAAO,eAAe,UAAa,WAAW,SAAS,OAAO;AAAA,MAChE,CAAC;AACD,cAAQ,WAAW,MAAM,CAAC,IAAI;AAAA,IAChC;AAAA,IACA,OAAO,MAAM,OAAO,MAAM;AAAA,EAC5B;AACF;","names":[]}