mountly-mcp 4.0.2 → 4.1.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 (45) hide show
  1. package/GLOSSARY.md +37 -0
  2. package/README.md +75 -197
  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-UECZA34Q.js → chunk-OWAXBNZA.js} +10 -5
  9. package/dist/chunk-OWAXBNZA.js.map +1 -0
  10. package/dist/cli.js +96 -24
  11. package/dist/cli.js.map +1 -1
  12. package/dist/connect-server-P2UZ62YC.js +7 -0
  13. package/dist/create.js +43 -15
  14. package/dist/create.js.map +1 -1
  15. package/dist/dev/index.js +1 -1
  16. package/dist/doctor-B54H6CRN.js +122 -0
  17. package/dist/doctor-B54H6CRN.js.map +1 -0
  18. package/dist/doctor.d.ts +16 -0
  19. package/dist/errors-4ERW7HZB.js +11 -0
  20. package/dist/errors-4ERW7HZB.js.map +1 -0
  21. package/dist/errors.d.ts +25 -0
  22. package/package.json +9 -6
  23. package/templates/react/README.md.tmpl +83 -6
  24. package/templates/react/package.json.tmpl +2 -0
  25. package/templates/react/serve-stdio.mjs.tmpl +9 -0
  26. package/templates/react/server.mjs.tmpl +6 -1
  27. package/templates/react/src/view.tsx.tmpl +1 -1
  28. package/templates/svelte/README.md.tmpl +27 -4
  29. package/templates/svelte/package.json.tmpl +2 -0
  30. package/templates/svelte/serve-stdio.mjs.tmpl +5 -0
  31. package/templates/svelte/server.mjs.tmpl +6 -1
  32. package/templates/svelte/src/Dashboard.svelte.tmpl +1 -1
  33. package/templates/vanilla/README.md.tmpl +27 -11
  34. package/templates/vanilla/package.json.tmpl +2 -0
  35. package/templates/vanilla/serve-stdio.mjs.tmpl +5 -0
  36. package/templates/vanilla/server.mjs.tmpl +6 -1
  37. package/templates/vanilla/src/view.ts.tmpl +1 -1
  38. package/templates/vue/README.md.tmpl +27 -4
  39. package/templates/vue/package.json.tmpl +2 -0
  40. package/templates/vue/serve-stdio.mjs.tmpl +5 -0
  41. package/templates/vue/server.mjs.tmpl +6 -1
  42. package/templates/vue/src/Dashboard.vue.tmpl +1 -1
  43. package/dist/chunk-UECZA34Q.js.map +0 -1
  44. package/dist/connect-server-7L4HGKKL.js +0 -7
  45. /package/dist/{connect-server-7L4HGKKL.js.map → connect-server-P2UZ62YC.js.map} +0 -0
@@ -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":[]}
@@ -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":[]}
package/dist/cli.js CHANGED
@@ -1,8 +1,12 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ CLI_ERROR_CODES,
4
+ cliError
5
+ } from "./chunk-EOWAWZH2.js";
2
6
  import {
3
7
  startDevHost
4
8
  } from "./chunk-KWVGW5Q7.js";
5
- import "./chunk-UECZA34Q.js";
9
+ import "./chunk-OWAXBNZA.js";
6
10
  import "./chunk-YJA4H6IB.js";
7
11
 
8
12
  // src/cli.ts
@@ -14,7 +18,13 @@ function parseArgs(argv) {
14
18
  const arg = argv[i];
15
19
  const next = () => {
16
20
  const value = argv[i + 1];
17
- if (value === void 0) throw new Error(`mountly-mcp: ${arg} needs a value`);
21
+ if (value === void 0) {
22
+ throw cliError(
23
+ CLI_ERROR_CODES.UNKNOWN_OPTION,
24
+ `${arg} needs a value`,
25
+ "See: mountly-mcp --help"
26
+ );
27
+ }
18
28
  i += 1;
19
29
  return value;
20
30
  };
@@ -27,7 +37,13 @@ function parseArgs(argv) {
27
37
  else if (arg === "--help" || arg === "-h") {
28
38
  printUsage();
29
39
  process.exit(0);
30
- } else throw new Error(`mountly-mcp: unknown option '${arg}'`);
40
+ } else {
41
+ throw cliError(
42
+ CLI_ERROR_CODES.UNKNOWN_OPTION,
43
+ `unknown option '${arg}'`,
44
+ "See: mountly-mcp --help"
45
+ );
46
+ }
31
47
  }
32
48
  return args;
33
49
  }
@@ -37,7 +53,13 @@ function parseVerifyArgs(argv) {
37
53
  const arg = argv[i];
38
54
  const next = () => {
39
55
  const value = argv[i + 1];
40
- if (value === void 0) throw new Error(`mountly-mcp: ${arg} needs a value`);
56
+ if (value === void 0) {
57
+ throw cliError(
58
+ CLI_ERROR_CODES.UNKNOWN_OPTION,
59
+ `${arg} needs a value`,
60
+ "See: mountly-mcp --help"
61
+ );
62
+ }
41
63
  i += 1;
42
64
  return value;
43
65
  };
@@ -46,10 +68,20 @@ function parseVerifyArgs(argv) {
46
68
  else if (arg === "--strict") args.strict = true;
47
69
  else if (arg === "--render") args.render = true;
48
70
  else if (arg === "--json") args.json = true;
49
- else throw new Error(`mountly-mcp: unknown verify option '${arg}'`);
71
+ else {
72
+ throw cliError(
73
+ CLI_ERROR_CODES.UNKNOWN_OPTION,
74
+ `unknown verify option '${arg}'`,
75
+ "Options: --manifest --html --strict --render --json"
76
+ );
77
+ }
50
78
  }
51
79
  if (args.manifest && args.html.length > 0) {
52
- throw new Error("mountly-mcp: verify accepts either --manifest or --html, not both");
80
+ throw cliError(
81
+ CLI_ERROR_CODES.UNKNOWN_OPTION,
82
+ "verify accepts either --manifest or --html, not both",
83
+ "Use: mountly-mcp verify OR mountly-mcp verify --html dist/dashboard.html"
84
+ );
53
85
  }
54
86
  return args;
55
87
  }
@@ -59,26 +91,46 @@ function parseBuildArgs(argv) {
59
91
  const arg = argv[i];
60
92
  if (arg === "--config" || arg === "-c") {
61
93
  const value = argv[i + 1];
62
- if (value === void 0) throw new Error(`mountly-mcp: ${arg} needs a value`);
94
+ if (value === void 0) {
95
+ throw cliError(
96
+ CLI_ERROR_CODES.UNKNOWN_OPTION,
97
+ `${arg} needs a value`,
98
+ "Example: mountly-mcp build --config ./vite.config.ts"
99
+ );
100
+ }
63
101
  args.config = value;
64
102
  i += 1;
65
- } else throw new Error(`mountly-mcp: unknown build option '${arg}'`);
103
+ } else {
104
+ throw cliError(
105
+ CLI_ERROR_CODES.UNKNOWN_OPTION,
106
+ `unknown build option '${arg}'`,
107
+ "Options: --config"
108
+ );
109
+ }
66
110
  }
67
111
  return args;
68
112
  }
69
113
  function printUsage() {
70
- process.stdout.write(`mountly-mcp \u2014 create, build, develop, and verify MCP Apps
114
+ process.stdout.write(`mountly-mcp \u2014 create, add, build, develop, doctor, and verify MCP Apps
71
115
 
72
116
  Usage:
73
117
  mountly-mcp create <name> [options]
118
+ mountly-mcp add <name> [options]
74
119
  mountly-mcp build [options]
75
120
  mountly-mcp dev [options]
76
121
  mountly-mcp verify [options]
122
+ mountly-mcp doctor
77
123
 
78
124
  Create options:
79
- -f, --framework <name> react | vue | svelte (default: react)
125
+ -f, --framework <name> react | vue | svelte | vanilla (default: react)
80
126
  --dir <path> output directory (default: ./<name>)
81
127
 
128
+ Add options (second View in an existing project):
129
+ -f, --framework <name> react | vue | svelte | vanilla (default: detect)
130
+ --entry <path> View entry (default: src/<name>.tsx|ts)
131
+ --uri <ui://...> resource URI (default: ui://app/<name>)
132
+ -c, --config <path> vite config (default: auto)
133
+
82
134
  Dev / build options:
83
135
  -c, --config <path> vite config to read the View from (default: auto)
84
136
  -f, --fixtures <path> JSON of named samples (default: mcp.fixtures.json)
@@ -126,8 +178,10 @@ async function loadViewOptions(configFile, appName) {
126
178
  const { loadConfigFromFile } = await import("vite");
127
179
  const loaded = await loadConfigFromFile({ command: "build", mode: "development" }, configFile);
128
180
  if (!loaded) {
129
- throw new Error(
130
- "mountly-mcp: no vite config found. Pass --config, or add mountlyMcpViews() to vite.config.ts."
181
+ throw cliError(
182
+ CLI_ERROR_CODES.NO_VITE_CONFIG,
183
+ "no vite config found",
184
+ "Pass --config, or add mountlyMcpViews() to vite.config.ts. Or: npx mountly-mcp create my-app"
131
185
  );
132
186
  }
133
187
  const plugins = (loaded.config.plugins ?? []).flat(
@@ -135,20 +189,26 @@ async function loadViewOptions(configFile, appName) {
135
189
  );
136
190
  const plugin = plugins.find((p) => p?.api?.mountlyMcp);
137
191
  if (!plugin?.api?.mountlyMcp) {
138
- throw new Error(
139
- `mountly-mcp: no mountlyMcpViews() plugin in ${loaded.path}. Add it to the plugins array.`
192
+ throw cliError(
193
+ CLI_ERROR_CODES.MISSING_VITE_PLUGIN,
194
+ `no mountlyMcpViews() plugin in ${loaded.path}`,
195
+ `Add mountlyMcpViews({ apps: [...] }) from "mountly-mcp/vite" to ${loaded.path}`
140
196
  );
141
197
  }
142
198
  const apps = plugin.api.mountlyMcp.apps;
143
199
  if (apps.length > 1 && appName === void 0) {
144
- throw new Error(
145
- `mountly-mcp: this build contains several Views; pass --app <name>. Available: ${apps.map((app) => app.name).join(", ")}`
200
+ throw cliError(
201
+ CLI_ERROR_CODES.MULTI_VIEW_NEEDS_APP,
202
+ "this build contains several Views",
203
+ `Pass --app <name>. Available: ${apps.map((app) => app.name).join(", ")}`
146
204
  );
147
205
  }
148
206
  const selected = appName === void 0 ? apps[0] : apps.find((app) => app.name === appName);
149
207
  if (!selected) {
150
- throw new Error(
151
- `mountly-mcp: no View named '${String(appName)}'. Available: ${apps.map((app) => app.name).join(", ")}`
208
+ throw cliError(
209
+ CLI_ERROR_CODES.UNKNOWN_VIEW,
210
+ `no View named '${String(appName)}'`,
211
+ `Available: ${apps.map((app) => app.name).join(", ")}`
152
212
  );
153
213
  }
154
214
  return {
@@ -220,12 +280,14 @@ async function dev(args) {
220
280
  void (async () => {
221
281
  await waitForFile(`${htmlPath}.meta.json`);
222
282
  if (args.server) {
223
- const { connectMcpServer } = await import("./connect-server-7L4HGKKL.js");
283
+ const { connectMcpServer } = await import("./connect-server-P2UZ62YC.js");
224
284
  server = await connectMcpServer(args.server);
225
285
  toolName = await server.toolFor(options.uri);
226
286
  if (!toolName) {
227
- throw new Error(
228
- `mountly-mcp: server '${args.server}' has no tool linked to View '${options.uri}'`
287
+ throw cliError(
288
+ CLI_ERROR_CODES.NO_TOOL_FOR_VIEW,
289
+ `server '${args.server}' has no tool linked to View '${options.uri}'`,
290
+ `In registerMcpApps tools[], set resourceUri: "${options.uri}" on a model-visible tool`
229
291
  );
230
292
  }
231
293
  }
@@ -282,12 +344,22 @@ async function main() {
282
344
  const { createProject, parseCreateArgs } = await import("./create.js");
283
345
  return createProject(parseCreateArgs(rest));
284
346
  }
347
+ if (command === "add") {
348
+ const { addView, parseAddArgs } = await import("./add-DAFMA4F3.js");
349
+ return addView(parseAddArgs(rest));
350
+ }
351
+ if (command === "doctor") {
352
+ const { doctorCommand } = await import("./doctor-B54H6CRN.js");
353
+ return doctorCommand();
354
+ }
285
355
  if (command === "dev") return dev(parseArgs(rest));
286
356
  if (command === "build") return buildApps(parseBuildArgs(rest));
287
357
  if (command === "verify") return verify(parseVerifyArgs(rest));
288
- process.stderr.write(`mountly-mcp: unknown command '${command}'. Try 'mountly-mcp --help'.
289
- `);
290
- process.exit(1);
358
+ throw cliError(
359
+ CLI_ERROR_CODES.UNKNOWN_COMMAND,
360
+ `unknown command '${command}'`,
361
+ "Try: mountly-mcp create | add | build | dev | verify | doctor"
362
+ );
291
363
  }
292
364
  main().catch((error) => {
293
365
  process.stderr.write(`${error instanceof Error ? error.message : String(error)}
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `mountly-mcp dev` — build the View, serve it in a real sandboxed MCP Apps\n * host, and rebuild on change.\n *\n * Config comes from the `mountlyMcpViews()` plugin already in vite.config.ts,\n * so there is nothing new to configure.\n */\nimport { readFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport { startDevHost } from \"./dev/index.js\";\nimport type { ConnectedMcpServer } from \"./dev/connect-server.js\";\nimport type { MountlyMcpViteApi, MountlyMcpViewOptions } from \"./vite/index.js\";\n\ninterface Args {\n config?: string;\n fixtures?: string;\n server?: string;\n app?: string;\n port?: number;\n open: boolean;\n}\n\ninterface VerifyArgs {\n manifest?: string;\n html: string[];\n strict: boolean;\n render: boolean;\n json: boolean;\n}\n\ninterface BuildArgs {\n config?: string;\n}\n\nfunction parseArgs(argv: ReadonlyArray<string>): Args {\n const args: Args = { open: true };\n for (let i = 0; i < argv.length; i += 1) {\n const arg = argv[i];\n const next = (): string => {\n const value = argv[i + 1];\n if (value === undefined) throw new Error(`mountly-mcp: ${arg} needs a value`);\n i += 1;\n return value;\n };\n if (arg === \"--config\" || arg === \"-c\") args.config = next();\n else if (arg === \"--fixtures\" || arg === \"-f\") args.fixtures = next();\n else if (arg === \"--server\" || arg === \"-s\") args.server = next();\n else if (arg === \"--app\" || arg === \"-a\") args.app = next();\n else if (arg === \"--port\" || arg === \"-p\") args.port = Number(next());\n else if (arg === \"--no-open\") args.open = false;\n else if (arg === \"--help\" || arg === \"-h\") {\n printUsage();\n process.exit(0);\n } else throw new Error(`mountly-mcp: unknown option '${arg}'`);\n }\n return args;\n}\n\nfunction parseVerifyArgs(argv: ReadonlyArray<string>): VerifyArgs {\n const args: VerifyArgs = { html: [], strict: false, render: false, json: false };\n for (let i = 0; i < argv.length; i += 1) {\n const arg = argv[i];\n const next = (): string => {\n const value = argv[i + 1];\n if (value === undefined) throw new Error(`mountly-mcp: ${arg} needs a value`);\n i += 1;\n return value;\n };\n if (arg === \"--manifest\" || arg === \"-m\") args.manifest = next();\n else if (arg === \"--html\") args.html.push(next());\n else if (arg === \"--strict\") args.strict = true;\n else if (arg === \"--render\") args.render = true;\n else if (arg === \"--json\") args.json = true;\n else throw new Error(`mountly-mcp: unknown verify option '${arg}'`);\n }\n if (args.manifest && args.html.length > 0) {\n throw new Error(\"mountly-mcp: verify accepts either --manifest or --html, not both\");\n }\n return args;\n}\n\nfunction parseBuildArgs(argv: ReadonlyArray<string>): BuildArgs {\n const args: BuildArgs = {};\n for (let i = 0; i < argv.length; i += 1) {\n const arg = argv[i];\n if (arg === \"--config\" || arg === \"-c\") {\n const value = argv[i + 1];\n if (value === undefined) throw new Error(`mountly-mcp: ${arg} needs a value`);\n args.config = value;\n i += 1;\n } else throw new Error(`mountly-mcp: unknown build option '${arg}'`);\n }\n return args;\n}\n\nfunction printUsage(): void {\n process.stdout.write(`mountly-mcp — create, build, develop, and verify MCP Apps\n\nUsage:\n mountly-mcp create <name> [options]\n mountly-mcp build [options]\n mountly-mcp dev [options]\n mountly-mcp verify [options]\n\nCreate options:\n -f, --framework <name> react | vue | svelte (default: react)\n --dir <path> output directory (default: ./<name>)\n\nDev / build options:\n -c, --config <path> vite config to read the View from (default: auto)\n -f, --fixtures <path> JSON of named samples (default: mcp.fixtures.json)\n -s, --server <path> module default-exporting your MCP server, to call real tools\n -a, --app <name> View to develop (required when the manifest has several)\n -p, --port <number> host port (default: 5179; the sandbox takes the next one)\n --no-open don't open a browser\n -h, --help show this\n\nVerify options:\n -m, --manifest <path> manifest to verify (default: dist/mountly-mcp.manifest.json)\n --html <path> transitional single-View HTML; repeat for several\n --strict fail when warnings are present\n --render require each View to mount with content in Chromium (needs playwright)\n --json output the conformance report as JSON\n\nGreenfield: prefer \\`mountly-mcp create\\` — do not clone the Mountly monorepo.\n\nThe View's entry, uri and name come from the mountlyMcpViews() plugin in\nyour vite config, so there is nothing extra to configure.\n\nWithout --server each fixture value is delivered as structuredContent. With\n--server it is the tool's arguments instead, the view receives what the real\ntool returns, and tool calls the view makes itself are routed to your server.\n`);\n}\n\nasync function buildApps(args: BuildArgs): Promise<void> {\n const { createBuilder } = await import(\"vite\");\n const builder = await createBuilder({ configFile: args.config }, false);\n await builder.buildApp();\n}\n\nasync function verify(args: VerifyArgs): Promise<void> {\n const { formatConformanceReport, verifyMcpApps } = await import(\"./testing/index.js\");\n const report = await verifyMcpApps({\n manifestPath: args.manifest,\n htmlPaths: args.html.length > 0 ? args.html : undefined,\n render: args.render,\n });\n process.stdout.write(\n args.json ? JSON.stringify(report, null, 2) : formatConformanceReport(report),\n );\n if (!report.ok || (args.strict && report.diagnostics.length > 0)) process.exitCode = 1;\n}\n\n/**\n * Read the View's options back out of the vite config. The plugin publishes\n * them on `api.mountlyMcp`, so this stays correct if its option shape changes.\n */\nasync function loadViewOptions(\n configFile: string | undefined,\n appName: string | undefined,\n): Promise<{ options: MountlyMcpViewOptions; outDir: string; root: string }> {\n const { loadConfigFromFile } = await import(\"vite\");\n const loaded = await loadConfigFromFile({ command: \"build\", mode: \"development\" }, configFile);\n if (!loaded) {\n throw new Error(\n \"mountly-mcp: no vite config found. Pass --config, or add mountlyMcpViews() to vite.config.ts.\",\n );\n }\n // Flattened as unknown[]: vite's PluginOption is recursively nested, and\n // asking TS to flatten it by its own type sends the checker infinite.\n const plugins = ((loaded.config.plugins ?? []) as unknown[]).flat(\n Number.POSITIVE_INFINITY,\n ) as Array<{ name?: string; api?: { mountlyMcp?: MountlyMcpViteApi } } | null>;\n const plugin = plugins.find((p) => p?.api?.mountlyMcp);\n if (!plugin?.api?.mountlyMcp) {\n throw new Error(\n `mountly-mcp: no mountlyMcpViews() plugin in ${loaded.path}. Add it to the plugins array.`,\n );\n }\n const apps = plugin.api.mountlyMcp.apps;\n if (apps.length > 1 && appName === undefined) {\n throw new Error(\n `mountly-mcp: this build contains several Views; pass --app <name>. Available: ${apps.map((app) => app.name).join(\", \")}`,\n );\n }\n const selected = appName === undefined ? apps[0] : apps.find((app) => app.name === appName);\n if (!selected) {\n throw new Error(\n `mountly-mcp: no View named '${String(appName)}'. Available: ${apps.map((app) => app.name).join(\", \")}`,\n );\n }\n return {\n options: selected,\n outDir: (loaded.config.build?.outDir as string | undefined) ?? \"dist\",\n root: (loaded.config.root as string | undefined) ?? process.cwd(),\n };\n}\n\n/**\n * Vite empties the out dir at the start of every build and the plugin writes\n * the resource in `closeBundle`, so the watcher's END event can arrive while\n * the files are still missing. Wait for the artifact rather than trusting the\n * event ordering — this also covers a build that failed and wrote nothing.\n */\nasync function waitForFile(path: string, timeoutMs = 15_000): Promise<void> {\n const deadline = Date.now() + timeoutMs;\n for (;;) {\n try {\n await readFile(path);\n return;\n } catch {\n if (Date.now() > deadline) throw new Error(`mountly-mcp: timed out waiting for ${path}`);\n await new Promise((done) => setTimeout(done, 50));\n }\n }\n}\n\nasync function loadFixtures(path: string | undefined): Promise<Record<string, unknown>> {\n const file = path ?? \"mcp.fixtures.json\";\n try {\n return JSON.parse(await readFile(resolve(file), \"utf8\")) as Record<string, unknown>;\n } catch (error) {\n // An explicit --fixtures that doesn't load is a mistake worth reporting;\n // the default one simply being absent is not.\n if (path) throw new Error(`mountly-mcp: could not read fixtures '${file}': ${String(error)}`);\n return {};\n }\n}\n\nasync function dev(args: Args): Promise<void> {\n const { options, outDir, root } = await loadViewOptions(args.config, args.app);\n const fixtures = await loadFixtures(args.fixtures);\n\n // Connected after the first build, not here: a server module typically reads\n // the View it serves, which does not exist yet on a cold checkout.\n let server: ConnectedMcpServer | undefined;\n let toolName: string | undefined;\n\n const { build } = await import(\"vite\");\n const htmlPath = options.output\n ? resolve(root, options.output)\n : resolve(root, outDir, `${options.name}.html`);\n\n // A watching build: vite rebuilds on change, and each rebuild bumps the\n // version the browser is polling, which reloads it.\n let host: Awaited<ReturnType<typeof startDevHost>> | undefined;\n let started = false;\n\n const previousSelectedApp = process.env.MOUNTLY_MCP_SELECTED_APP;\n process.env.MOUNTLY_MCP_SELECTED_APP = options.name;\n const watcher = await build({\n configFile: args.config ?? undefined,\n build: { watch: {} },\n logLevel: \"warn\",\n }).finally(() => {\n if (previousSelectedApp === undefined) delete process.env.MOUNTLY_MCP_SELECTED_APP;\n else process.env.MOUNTLY_MCP_SELECTED_APP = previousSelectedApp;\n });\n\n // `build({ watch })` returns a rollup watcher; the first BUNDLE_END means the\n // View exists on disk and the host has something to serve.\n const rollupWatcher = watcher as unknown as {\n on(event: \"event\", cb: (e: { code: string; error?: Error }) => void): void;\n close(): Promise<void>;\n };\n\n rollupWatcher.on(\"event\", (event) => {\n if (event.code === \"ERROR\") {\n process.stderr.write(`mountly-mcp: build failed — ${event.error?.message ?? \"unknown\"}\\n`);\n return;\n }\n if (event.code !== \"END\") return;\n if (started) {\n void waitForFile(`${htmlPath}.meta.json`).then(\n () => {\n host?.reload();\n process.stdout.write(\"mountly-mcp: rebuilt\\n\");\n },\n (error: unknown) => process.stderr.write(`${String(error)}\\n`),\n );\n return;\n }\n started = true;\n void (async () => {\n await waitForFile(`${htmlPath}.meta.json`);\n if (args.server) {\n const { connectMcpServer } = await import(\"./dev/connect-server.js\");\n server = await connectMcpServer(args.server);\n toolName = await server.toolFor(options.uri);\n if (!toolName) {\n throw new Error(\n `mountly-mcp: server '${args.server}' has no tool linked to View '${options.uri}'`,\n );\n }\n }\n host = await startDevHost({\n htmlPath,\n fixtures,\n toolInput: {},\n toolName,\n callTool: server?.callTool,\n hostPort: args.port,\n });\n process.stdout.write(\n `\\n mountly-mcp dev\\n` +\n ` host ${host.hostUrl}\\n` +\n ` sandbox ${host.sandboxUrl}\\n` +\n ` view ${htmlPath}\\n` +\n ` fixtures ${Object.keys(fixtures).length || \"none — pass --fixtures\"}\\n` +\n ` server ${server ? `${args.server} → ${toolName ?? \"no tool bound to \" + options.uri}` : \"none — fixtures deliver directly\"}\\n\\n` +\n ` watching for changes; ctrl+c to stop\\n\\n`,\n );\n if (args.open) {\n const { spawn } = await import(\"node:child_process\");\n const opener =\n process.platform === \"darwin\"\n ? \"open\"\n : process.platform === \"win32\"\n ? \"start\"\n : \"xdg-open\";\n spawn(opener, [host.hostUrl], { stdio: \"ignore\", detached: true, shell: true }).unref();\n }\n })().catch((error: unknown) => {\n // Startup runs inside a watcher callback, where a rejection would\n // otherwise be silent and leave the process alive with no host.\n process.stderr.write(`${error instanceof Error ? error.message : String(error)}\\n`);\n void Promise.all([rollupWatcher.close(), server?.close()]).finally(() => process.exit(1));\n });\n });\n\n const shutdown = (): void => {\n void (async () => {\n await rollupWatcher.close();\n await host?.close();\n await server?.close();\n process.exit(0);\n })();\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n}\n\nasync function main(): Promise<void> {\n const [command, ...rest] = process.argv.slice(2);\n if (command === undefined || command === \"--help\" || command === \"-h\") {\n printUsage();\n process.exit(command === undefined ? 1 : 0);\n }\n if (command === \"create\") {\n const { createProject, parseCreateArgs } = await import(\"./create.js\");\n return createProject(parseCreateArgs(rest));\n }\n if (command === \"dev\") return dev(parseArgs(rest));\n if (command === \"build\") return buildApps(parseBuildArgs(rest));\n if (command === \"verify\") return verify(parseVerifyArgs(rest));\n process.stderr.write(`mountly-mcp: unknown command '${command}'. Try 'mountly-mcp --help'.\\n`);\n process.exit(1);\n}\n\nmain().catch((error: unknown) => {\n process.stderr.write(`${error instanceof Error ? error.message : String(error)}\\n`);\n process.exit(1);\n});\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `mountly-mcp` — create, add, build, develop, doctor, and verify MCP Apps.\n *\n * Config for build/dev comes from `mountlyMcpViews()` in vite.config.ts.\n */\nimport { readFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport { startDevHost } from \"./dev/index.js\";\nimport type { ConnectedMcpServer } from \"./dev/connect-server.js\";\nimport { CLI_ERROR_CODES, cliError } from \"./errors.js\";\nimport type { MountlyMcpViteApi, MountlyMcpViewOptions } from \"./vite/index.js\";\n\ninterface Args {\n config?: string;\n fixtures?: string;\n server?: string;\n app?: string;\n port?: number;\n open: boolean;\n}\n\ninterface VerifyArgs {\n manifest?: string;\n html: string[];\n strict: boolean;\n render: boolean;\n json: boolean;\n}\n\ninterface BuildArgs {\n config?: string;\n}\n\nfunction parseArgs(argv: ReadonlyArray<string>): Args {\n const args: Args = { open: true };\n for (let i = 0; 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 === \"--config\" || arg === \"-c\") args.config = next();\n else if (arg === \"--fixtures\" || arg === \"-f\") args.fixtures = next();\n else if (arg === \"--server\" || arg === \"-s\") args.server = next();\n else if (arg === \"--app\" || arg === \"-a\") args.app = next();\n else if (arg === \"--port\" || arg === \"-p\") args.port = Number(next());\n else if (arg === \"--no-open\") args.open = false;\n else if (arg === \"--help\" || arg === \"-h\") {\n printUsage();\n process.exit(0);\n } else {\n throw cliError(\n CLI_ERROR_CODES.UNKNOWN_OPTION,\n `unknown option '${arg}'`,\n \"See: mountly-mcp --help\",\n );\n }\n }\n return args;\n}\n\nfunction parseVerifyArgs(argv: ReadonlyArray<string>): VerifyArgs {\n const args: VerifyArgs = { html: [], strict: false, render: false, json: false };\n for (let i = 0; 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 === \"--manifest\" || arg === \"-m\") args.manifest = next();\n else if (arg === \"--html\") args.html.push(next());\n else if (arg === \"--strict\") args.strict = true;\n else if (arg === \"--render\") args.render = true;\n else if (arg === \"--json\") args.json = true;\n else {\n throw cliError(\n CLI_ERROR_CODES.UNKNOWN_OPTION,\n `unknown verify option '${arg}'`,\n \"Options: --manifest --html --strict --render --json\",\n );\n }\n }\n if (args.manifest && args.html.length > 0) {\n throw cliError(\n CLI_ERROR_CODES.UNKNOWN_OPTION,\n \"verify accepts either --manifest or --html, not both\",\n \"Use: mountly-mcp verify OR mountly-mcp verify --html dist/dashboard.html\",\n );\n }\n return args;\n}\n\nfunction parseBuildArgs(argv: ReadonlyArray<string>): BuildArgs {\n const args: BuildArgs = {};\n for (let i = 0; i < argv.length; i += 1) {\n const arg = argv[i];\n if (arg === \"--config\" || arg === \"-c\") {\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 \"Example: mountly-mcp build --config ./vite.config.ts\",\n );\n }\n args.config = value;\n i += 1;\n } else {\n throw cliError(\n CLI_ERROR_CODES.UNKNOWN_OPTION,\n `unknown build option '${arg}'`,\n \"Options: --config\",\n );\n }\n }\n return args;\n}\n\nfunction printUsage(): void {\n process.stdout.write(`mountly-mcp — create, add, build, develop, doctor, and verify MCP Apps\n\nUsage:\n mountly-mcp create <name> [options]\n mountly-mcp add <name> [options]\n mountly-mcp build [options]\n mountly-mcp dev [options]\n mountly-mcp verify [options]\n mountly-mcp doctor\n\nCreate options:\n -f, --framework <name> react | vue | svelte | vanilla (default: react)\n --dir <path> output directory (default: ./<name>)\n\nAdd options (second View in an existing project):\n -f, --framework <name> react | vue | svelte | vanilla (default: detect)\n --entry <path> View entry (default: src/<name>.tsx|ts)\n --uri <ui://...> resource URI (default: ui://app/<name>)\n -c, --config <path> vite config (default: auto)\n\nDev / build options:\n -c, --config <path> vite config to read the View from (default: auto)\n -f, --fixtures <path> JSON of named samples (default: mcp.fixtures.json)\n -s, --server <path> module default-exporting your MCP server, to call real tools\n -a, --app <name> View to develop (required when the manifest has several)\n -p, --port <number> host port (default: 5179; the sandbox takes the next one)\n --no-open don't open a browser\n -h, --help show this\n\nVerify options:\n -m, --manifest <path> manifest to verify (default: dist/mountly-mcp.manifest.json)\n --html <path> transitional single-View HTML; repeat for several\n --strict fail when warnings are present\n --render require each View to mount with content in Chromium (needs playwright)\n --json output the conformance report as JSON\n\nGreenfield: prefer \\`mountly-mcp create\\` — do not clone the Mountly monorepo.\n\nThe View's entry, uri and name come from the mountlyMcpViews() plugin in\nyour vite config, so there is nothing extra to configure.\n\nWithout --server each fixture value is delivered as structuredContent. With\n--server it is the tool's arguments instead, the view receives what the real\ntool returns, and tool calls the view makes itself are routed to your server.\n`);\n}\n\nasync function buildApps(args: BuildArgs): Promise<void> {\n const { createBuilder } = await import(\"vite\");\n const builder = await createBuilder({ configFile: args.config }, false);\n await builder.buildApp();\n}\n\nasync function verify(args: VerifyArgs): Promise<void> {\n const { formatConformanceReport, verifyMcpApps } = await import(\"./testing/index.js\");\n const report = await verifyMcpApps({\n manifestPath: args.manifest,\n htmlPaths: args.html.length > 0 ? args.html : undefined,\n render: args.render,\n });\n process.stdout.write(\n args.json ? JSON.stringify(report, null, 2) : formatConformanceReport(report),\n );\n if (!report.ok || (args.strict && report.diagnostics.length > 0)) process.exitCode = 1;\n}\n\n/**\n * Read the View's options back out of the vite config. The plugin publishes\n * them on `api.mountlyMcp`, so this stays correct if its option shape changes.\n */\nasync function loadViewOptions(\n configFile: string | undefined,\n appName: string | undefined,\n): Promise<{ options: MountlyMcpViewOptions; outDir: string; root: string }> {\n const { loadConfigFromFile } = await import(\"vite\");\n const loaded = await loadConfigFromFile({ command: \"build\", mode: \"development\" }, configFile);\n if (!loaded) {\n throw cliError(\n CLI_ERROR_CODES.NO_VITE_CONFIG,\n \"no vite config found\",\n \"Pass --config, or add mountlyMcpViews() to vite.config.ts. Or: npx mountly-mcp create my-app\",\n );\n }\n // Flattened as unknown[]: vite's PluginOption is recursively nested, and\n // asking TS to flatten it by its own type sends the checker infinite.\n const plugins = ((loaded.config.plugins ?? []) as unknown[]).flat(\n Number.POSITIVE_INFINITY,\n ) as Array<{ name?: string; api?: { mountlyMcp?: MountlyMcpViteApi } } | null>;\n const plugin = plugins.find((p) => p?.api?.mountlyMcp);\n if (!plugin?.api?.mountlyMcp) {\n throw cliError(\n CLI_ERROR_CODES.MISSING_VITE_PLUGIN,\n `no mountlyMcpViews() plugin in ${loaded.path}`,\n `Add mountlyMcpViews({ apps: [...] }) from \"mountly-mcp/vite\" to ${loaded.path}`,\n );\n }\n const apps = plugin.api.mountlyMcp.apps;\n if (apps.length > 1 && appName === undefined) {\n throw cliError(\n CLI_ERROR_CODES.MULTI_VIEW_NEEDS_APP,\n \"this build contains several Views\",\n `Pass --app <name>. Available: ${apps.map((app) => app.name).join(\", \")}`,\n );\n }\n const selected = appName === undefined ? apps[0] : apps.find((app) => app.name === appName);\n if (!selected) {\n throw cliError(\n CLI_ERROR_CODES.UNKNOWN_VIEW,\n `no View named '${String(appName)}'`,\n `Available: ${apps.map((app) => app.name).join(\", \")}`,\n );\n }\n return {\n options: selected,\n outDir: (loaded.config.build?.outDir as string | undefined) ?? \"dist\",\n root: (loaded.config.root as string | undefined) ?? process.cwd(),\n };\n}\n\n/**\n * Vite empties the out dir at the start of every build and the plugin writes\n * the resource in `closeBundle`, so the watcher's END event can arrive while\n * the files are still missing. Wait for the artifact rather than trusting the\n * event ordering — this also covers a build that failed and wrote nothing.\n */\nasync function waitForFile(path: string, timeoutMs = 15_000): Promise<void> {\n const deadline = Date.now() + timeoutMs;\n for (;;) {\n try {\n await readFile(path);\n return;\n } catch {\n if (Date.now() > deadline) throw new Error(`mountly-mcp: timed out waiting for ${path}`);\n await new Promise((done) => setTimeout(done, 50));\n }\n }\n}\n\nasync function loadFixtures(path: string | undefined): Promise<Record<string, unknown>> {\n const file = path ?? \"mcp.fixtures.json\";\n try {\n return JSON.parse(await readFile(resolve(file), \"utf8\")) as Record<string, unknown>;\n } catch (error) {\n // An explicit --fixtures that doesn't load is a mistake worth reporting;\n // the default one simply being absent is not.\n if (path) throw new Error(`mountly-mcp: could not read fixtures '${file}': ${String(error)}`);\n return {};\n }\n}\n\nasync function dev(args: Args): Promise<void> {\n const { options, outDir, root } = await loadViewOptions(args.config, args.app);\n const fixtures = await loadFixtures(args.fixtures);\n\n // Connected after the first build, not here: a server module typically reads\n // the View it serves, which does not exist yet on a cold checkout.\n let server: ConnectedMcpServer | undefined;\n let toolName: string | undefined;\n\n const { build } = await import(\"vite\");\n const htmlPath = options.output\n ? resolve(root, options.output)\n : resolve(root, outDir, `${options.name}.html`);\n\n // A watching build: vite rebuilds on change, and each rebuild bumps the\n // version the browser is polling, which reloads it.\n let host: Awaited<ReturnType<typeof startDevHost>> | undefined;\n let started = false;\n\n const previousSelectedApp = process.env.MOUNTLY_MCP_SELECTED_APP;\n process.env.MOUNTLY_MCP_SELECTED_APP = options.name;\n const watcher = await build({\n configFile: args.config ?? undefined,\n build: { watch: {} },\n logLevel: \"warn\",\n }).finally(() => {\n if (previousSelectedApp === undefined) delete process.env.MOUNTLY_MCP_SELECTED_APP;\n else process.env.MOUNTLY_MCP_SELECTED_APP = previousSelectedApp;\n });\n\n // `build({ watch })` returns a rollup watcher; the first BUNDLE_END means the\n // View exists on disk and the host has something to serve.\n const rollupWatcher = watcher as unknown as {\n on(event: \"event\", cb: (e: { code: string; error?: Error }) => void): void;\n close(): Promise<void>;\n };\n\n rollupWatcher.on(\"event\", (event) => {\n if (event.code === \"ERROR\") {\n process.stderr.write(`mountly-mcp: build failed — ${event.error?.message ?? \"unknown\"}\\n`);\n return;\n }\n if (event.code !== \"END\") return;\n if (started) {\n void waitForFile(`${htmlPath}.meta.json`).then(\n () => {\n host?.reload();\n process.stdout.write(\"mountly-mcp: rebuilt\\n\");\n },\n (error: unknown) => process.stderr.write(`${String(error)}\\n`),\n );\n return;\n }\n started = true;\n void (async () => {\n await waitForFile(`${htmlPath}.meta.json`);\n if (args.server) {\n const { connectMcpServer } = await import(\"./dev/connect-server.js\");\n server = await connectMcpServer(args.server);\n toolName = await server.toolFor(options.uri);\n if (!toolName) {\n throw cliError(\n CLI_ERROR_CODES.NO_TOOL_FOR_VIEW,\n `server '${args.server}' has no tool linked to View '${options.uri}'`,\n `In registerMcpApps tools[], set resourceUri: \"${options.uri}\" on a model-visible tool`,\n );\n }\n }\n host = await startDevHost({\n htmlPath,\n fixtures,\n toolInput: {},\n toolName,\n callTool: server?.callTool,\n hostPort: args.port,\n });\n process.stdout.write(\n `\\n mountly-mcp dev\\n` +\n ` host ${host.hostUrl}\\n` +\n ` sandbox ${host.sandboxUrl}\\n` +\n ` view ${htmlPath}\\n` +\n ` fixtures ${Object.keys(fixtures).length || \"none — pass --fixtures\"}\\n` +\n ` server ${server ? `${args.server} → ${toolName ?? \"no tool bound to \" + options.uri}` : \"none — fixtures deliver directly\"}\\n\\n` +\n ` watching for changes; ctrl+c to stop\\n\\n`,\n );\n if (args.open) {\n const { spawn } = await import(\"node:child_process\");\n const opener =\n process.platform === \"darwin\"\n ? \"open\"\n : process.platform === \"win32\"\n ? \"start\"\n : \"xdg-open\";\n spawn(opener, [host.hostUrl], { stdio: \"ignore\", detached: true, shell: true }).unref();\n }\n })().catch((error: unknown) => {\n // Startup runs inside a watcher callback, where a rejection would\n // otherwise be silent and leave the process alive with no host.\n process.stderr.write(`${error instanceof Error ? error.message : String(error)}\\n`);\n void Promise.all([rollupWatcher.close(), server?.close()]).finally(() => process.exit(1));\n });\n });\n\n const shutdown = (): void => {\n void (async () => {\n await rollupWatcher.close();\n await host?.close();\n await server?.close();\n process.exit(0);\n })();\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n}\n\nasync function main(): Promise<void> {\n const [command, ...rest] = process.argv.slice(2);\n if (command === undefined || command === \"--help\" || command === \"-h\") {\n printUsage();\n process.exit(command === undefined ? 1 : 0);\n }\n if (command === \"create\") {\n const { createProject, parseCreateArgs } = await import(\"./create.js\");\n return createProject(parseCreateArgs(rest));\n }\n if (command === \"add\") {\n const { addView, parseAddArgs } = await import(\"./add.js\");\n return addView(parseAddArgs(rest));\n }\n if (command === \"doctor\") {\n const { doctorCommand } = await import(\"./doctor.js\");\n return doctorCommand();\n }\n if (command === \"dev\") return dev(parseArgs(rest));\n if (command === \"build\") return buildApps(parseBuildArgs(rest));\n if (command === \"verify\") return verify(parseVerifyArgs(rest));\n throw cliError(\n CLI_ERROR_CODES.UNKNOWN_COMMAND,\n `unknown command '${command}'`,\n \"Try: mountly-mcp create | add | build | dev | verify | doctor\",\n );\n}\n\nmain().catch((error: unknown) => {\n process.stderr.write(`${error instanceof Error ? error.message : String(error)}\\n`);\n process.exit(1);\n});\n"],"mappings":"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","names":[]}
@@ -0,0 +1,7 @@
1
+ import {
2
+ connectMcpServer
3
+ } from "./chunk-OWAXBNZA.js";
4
+ export {
5
+ connectMcpServer
6
+ };
7
+ //# sourceMappingURL=connect-server-P2UZ62YC.js.map
package/dist/create.js CHANGED
@@ -1,3 +1,8 @@
1
+ import {
2
+ CLI_ERROR_CODES,
3
+ cliError
4
+ } from "./chunk-EOWAWZH2.js";
5
+
1
6
  // src/create.ts
2
7
  import {
3
8
  cpSync,
@@ -23,8 +28,10 @@ var SUPPORTED = ["react", "vue", "svelte", "vanilla"];
23
28
  function parseCreateArgs(argv) {
24
29
  const name = argv[0];
25
30
  if (!name || name.startsWith("-")) {
26
- throw new Error(
27
- "mountly-mcp create requires a project name. e.g. mountly-mcp create my-app --framework react"
31
+ throw cliError(
32
+ CLI_ERROR_CODES.UNKNOWN_OPTION,
33
+ "create requires a project name",
34
+ "Example: mountly-mcp create my-app --framework react"
28
35
  );
29
36
  }
30
37
  let framework = "react";
@@ -33,19 +40,37 @@ function parseCreateArgs(argv) {
33
40
  const arg = argv[i];
34
41
  if (arg === "--framework" || arg === "-f") {
35
42
  const value = argv[++i];
36
- if (value === void 0) throw new Error(`mountly-mcp: ${arg} needs a value`);
43
+ if (value === void 0) {
44
+ throw cliError(
45
+ CLI_ERROR_CODES.UNKNOWN_OPTION,
46
+ `${arg} needs a value`,
47
+ `Available: ${SUPPORTED.join(", ")}`
48
+ );
49
+ }
37
50
  if (!SUPPORTED.includes(value)) {
38
- throw new Error(
39
- `mountly-mcp: framework "${value}" not supported. Available: ${SUPPORTED.join(", ")}`
51
+ throw cliError(
52
+ CLI_ERROR_CODES.UNKNOWN_OPTION,
53
+ `framework "${value}" not supported`,
54
+ `Available: ${SUPPORTED.join(", ")}`
40
55
  );
41
56
  }
42
57
  framework = value;
43
58
  } else if (arg === "--dir") {
44
59
  const value = argv[++i];
45
- if (value === void 0) throw new Error("mountly-mcp: --dir needs a value");
60
+ if (value === void 0) {
61
+ throw cliError(
62
+ CLI_ERROR_CODES.UNKNOWN_OPTION,
63
+ "--dir needs a value",
64
+ "Example: --dir ./apps/my-app"
65
+ );
66
+ }
46
67
  dir = value;
47
68
  } else {
48
- throw new Error(`mountly-mcp: unknown create option '${arg}'`);
69
+ throw cliError(
70
+ CLI_ERROR_CODES.UNKNOWN_OPTION,
71
+ `unknown create option '${arg}'`,
72
+ "Options: --framework | --dir"
73
+ );
49
74
  }
50
75
  }
51
76
  return { name, framework, dir };
@@ -72,12 +97,18 @@ function renderTemplates(dir, ctx) {
72
97
  async function createProject(args) {
73
98
  const target = resolve(args.dir);
74
99
  if (existsSync(target)) {
75
- throw new Error(`mountly-mcp: target directory already exists: ${target}`);
100
+ throw cliError(
101
+ CLI_ERROR_CODES.TARGET_EXISTS,
102
+ `target directory already exists: ${target}`,
103
+ "Choose another --dir, or remove the existing folder"
104
+ );
76
105
  }
77
106
  const tplDir = join(TEMPLATES, args.framework);
78
107
  if (!existsSync(tplDir)) {
79
- throw new Error(
80
- `mountly-mcp: template for framework "${args.framework}" is missing at ${tplDir}`
108
+ throw cliError(
109
+ CLI_ERROR_CODES.MISSING_TEMPLATE,
110
+ `template for framework "${args.framework}" is missing at ${tplDir}`,
111
+ `Available: ${SUPPORTED.join(", ")}`
81
112
  );
82
113
  }
83
114
  mkdirSync(target, { recursive: true });
@@ -95,13 +126,10 @@ async function createProject(args) {
95
126
  "",
96
127
  `\u2713 Created MCP App ${args.name} (${args.framework}) at ${target}`,
97
128
  "",
98
- "Next steps:",
129
+ "Next:",
99
130
  ` cd ${args.dir}`,
100
131
  " pnpm install",
101
- " pnpm dev # sandboxed host + live reload",
102
- " pnpm verify # conformance check",
103
- "",
104
- "Do not clone the Mountly monorepo for greenfield apps \u2014 this scaffold is the canonical path.",
132
+ " pnpm dev",
105
133
  ""
106
134
  ].join("\n")
107
135
  );
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/create.ts"],"sourcesContent":["import {\n cpSync,\n existsSync,\n mkdirSync,\n readFileSync,\n readdirSync,\n statSync,\n unlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n/** Package root: dist/cli.js → .. ; src/cli.ts during tests → .. when run via dist. */\nconst PACKAGE_ROOT = resolve(__dirname, \"..\");\nconst TEMPLATES = resolve(PACKAGE_ROOT, \"templates\");\n\n/**\n * The version of mountly-mcp doing the scaffolding, so the generated\n * package.json pins the API this CLI actually ships.\n *\n * Hardcoding the pin in the templates meant a major release left them behind:\n * `^3.1.0` cannot resolve to 4.0.0, so every scaffolded app installed a\n * package without the exports its own vite.config imported.\n */\nfunction ownVersion(): string {\n const pkg = JSON.parse(readFileSync(join(PACKAGE_ROOT, \"package.json\"), \"utf8\")) as {\n version?: string;\n };\n if (!pkg.version) throw new Error(\"mountly-mcp: cannot read own version from package.json\");\n return pkg.version;\n}\n\nconst SUPPORTED = [\"react\", \"vue\", \"svelte\", \"vanilla\"] as const;\ntype Framework = (typeof SUPPORTED)[number];\n\nexport interface CreateArgs {\n name: string;\n framework: Framework;\n dir: string;\n}\n\nexport function parseCreateArgs(argv: ReadonlyArray<string>): CreateArgs {\n const name = argv[0];\n if (!name || name.startsWith(\"-\")) {\n throw new Error(\n \"mountly-mcp create requires a project name. e.g. mountly-mcp create my-app --framework react\",\n );\n }\n\n let framework: Framework = \"react\";\n let dir = `./${name}`;\n\n for (let i = 1; i < argv.length; i += 1) {\n const arg = argv[i];\n if (arg === \"--framework\" || arg === \"-f\") {\n const value = argv[++i];\n if (value === undefined) throw new Error(`mountly-mcp: ${arg} needs a value`);\n if (!SUPPORTED.includes(value as Framework)) {\n throw new Error(\n `mountly-mcp: framework \"${value}\" not supported. Available: ${SUPPORTED.join(\", \")}`,\n );\n }\n framework = value as Framework;\n } else if (arg === \"--dir\") {\n const value = argv[++i];\n if (value === undefined) throw new Error(\"mountly-mcp: --dir needs a value\");\n dir = value;\n } else {\n throw new Error(`mountly-mcp: unknown create option '${arg}'`);\n }\n }\n\n return { name, framework, dir };\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 renderTemplates(dir: string, ctx: Record<string, string>): void {\n for (const entry of readdirSync(dir)) {\n const full = join(dir, entry);\n if (statSync(full).isDirectory()) {\n renderTemplates(full, ctx);\n continue;\n }\n if (!entry.endsWith(\".tmpl\")) continue;\n let content = readFileSync(full, \"utf8\");\n for (const [key, value] of Object.entries(ctx)) {\n content = content.split(`{{${key}}}`).join(value);\n }\n writeFileSync(full.replace(/\\.tmpl$/, \"\"), content);\n unlinkSync(full);\n }\n}\n\nexport async function createProject(args: CreateArgs): Promise<void> {\n const target = resolve(args.dir);\n if (existsSync(target)) {\n throw new Error(`mountly-mcp: target directory already exists: ${target}`);\n }\n\n const tplDir = join(TEMPLATES, args.framework);\n if (!existsSync(tplDir)) {\n throw new Error(\n `mountly-mcp: template for framework \"${args.framework}\" is missing at ${tplDir}`,\n );\n }\n\n mkdirSync(target, { recursive: true });\n cpSync(tplDir, target, { recursive: true });\n\n const slug = slugify(args.name) || \"app\";\n renderTemplates(target, {\n name: args.name,\n slug,\n uri: `ui://${slug}/dashboard`,\n framework: args.framework,\n mcpVersion: ownVersion(),\n });\n\n process.stdout.write(\n [\n \"\",\n `✓ Created MCP App ${args.name} (${args.framework}) at ${target}`,\n \"\",\n \"Next steps:\",\n ` cd ${args.dir}`,\n \" pnpm install\",\n \" pnpm dev # sandboxed host + live reload\",\n \" pnpm verify # conformance check\",\n \"\",\n \"Do not clone the Mountly monorepo for greenfield apps — this scaffold is the canonical path.\",\n \"\",\n ].join(\"\\n\"),\n );\n}\n"],"mappings":";AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,SAAS,MAAM,eAAe;AACvC,SAAS,qBAAqB;AAE9B,IAAM,YAAY,QAAQ,cAAc,YAAY,GAAG,CAAC;AAExD,IAAM,eAAe,QAAQ,WAAW,IAAI;AAC5C,IAAM,YAAY,QAAQ,cAAc,WAAW;AAUnD,SAAS,aAAqB;AAC5B,QAAM,MAAM,KAAK,MAAM,aAAa,KAAK,cAAc,cAAc,GAAG,MAAM,CAAC;AAG/E,MAAI,CAAC,IAAI,QAAS,OAAM,IAAI,MAAM,wDAAwD;AAC1F,SAAO,IAAI;AACb;AAEA,IAAM,YAAY,CAAC,SAAS,OAAO,UAAU,SAAS;AAS/C,SAAS,gBAAgB,MAAyC;AACvE,QAAM,OAAO,KAAK,CAAC;AACnB,MAAI,CAAC,QAAQ,KAAK,WAAW,GAAG,GAAG;AACjC,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,MAAI,YAAuB;AAC3B,MAAI,MAAM,KAAK,IAAI;AAEnB,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,GAAG;AACvC,UAAM,MAAM,KAAK,CAAC;AAClB,QAAI,QAAQ,iBAAiB,QAAQ,MAAM;AACzC,YAAM,QAAQ,KAAK,EAAE,CAAC;AACtB,UAAI,UAAU,OAAW,OAAM,IAAI,MAAM,gBAAgB,GAAG,gBAAgB;AAC5E,UAAI,CAAC,UAAU,SAAS,KAAkB,GAAG;AAC3C,cAAM,IAAI;AAAA,UACR,2BAA2B,KAAK,+BAA+B,UAAU,KAAK,IAAI,CAAC;AAAA,QACrF;AAAA,MACF;AACA,kBAAY;AAAA,IACd,WAAW,QAAQ,SAAS;AAC1B,YAAM,QAAQ,KAAK,EAAE,CAAC;AACtB,UAAI,UAAU,OAAW,OAAM,IAAI,MAAM,kCAAkC;AAC3E,YAAM;AAAA,IACR,OAAO;AACL,YAAM,IAAI,MAAM,uCAAuC,GAAG,GAAG;AAAA,IAC/D;AAAA,EACF;AAEA,SAAO,EAAE,MAAM,WAAW,IAAI;AAChC;AAEA,SAAS,QAAQ,MAAsB;AACrC,SAAO,KACJ,KAAK,EACL,YAAY,EACZ,QAAQ,eAAe,GAAG,EAC1B,QAAQ,YAAY,EAAE;AAC3B;AAEA,SAAS,gBAAgB,KAAa,KAAmC;AACvE,aAAW,SAAS,YAAY,GAAG,GAAG;AACpC,UAAM,OAAO,KAAK,KAAK,KAAK;AAC5B,QAAI,SAAS,IAAI,EAAE,YAAY,GAAG;AAChC,sBAAgB,MAAM,GAAG;AACzB;AAAA,IACF;AACA,QAAI,CAAC,MAAM,SAAS,OAAO,EAAG;AAC9B,QAAI,UAAU,aAAa,MAAM,MAAM;AACvC,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,GAAG,GAAG;AAC9C,gBAAU,QAAQ,MAAM,KAAK,GAAG,IAAI,EAAE,KAAK,KAAK;AAAA,IAClD;AACA,kBAAc,KAAK,QAAQ,WAAW,EAAE,GAAG,OAAO;AAClD,eAAW,IAAI;AAAA,EACjB;AACF;AAEA,eAAsB,cAAc,MAAiC;AACnE,QAAM,SAAS,QAAQ,KAAK,GAAG;AAC/B,MAAI,WAAW,MAAM,GAAG;AACtB,UAAM,IAAI,MAAM,iDAAiD,MAAM,EAAE;AAAA,EAC3E;AAEA,QAAM,SAAS,KAAK,WAAW,KAAK,SAAS;AAC7C,MAAI,CAAC,WAAW,MAAM,GAAG;AACvB,UAAM,IAAI;AAAA,MACR,wCAAwC,KAAK,SAAS,mBAAmB,MAAM;AAAA,IACjF;AAAA,EACF;AAEA,YAAU,QAAQ,EAAE,WAAW,KAAK,CAAC;AACrC,SAAO,QAAQ,QAAQ,EAAE,WAAW,KAAK,CAAC;AAE1C,QAAM,OAAO,QAAQ,KAAK,IAAI,KAAK;AACnC,kBAAgB,QAAQ;AAAA,IACtB,MAAM,KAAK;AAAA,IACX;AAAA,IACA,KAAK,QAAQ,IAAI;AAAA,IACjB,WAAW,KAAK;AAAA,IAChB,YAAY,WAAW;AAAA,EACzB,CAAC;AAED,UAAQ,OAAO;AAAA,IACb;AAAA,MACE;AAAA,MACA,0BAAqB,KAAK,IAAI,KAAK,KAAK,SAAS,QAAQ,MAAM;AAAA,MAC/D;AAAA,MACA;AAAA,MACA,QAAQ,KAAK,GAAG;AAAA,MAChB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,EAAE,KAAK,IAAI;AAAA,EACb;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/create.ts"],"sourcesContent":["import {\n cpSync,\n existsSync,\n mkdirSync,\n readFileSync,\n readdirSync,\n statSync,\n unlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { CLI_ERROR_CODES, cliError } from \"./errors.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n/** Package root: dist/cli.js → .. ; src/cli.ts during tests → .. when run via dist. */\nconst PACKAGE_ROOT = resolve(__dirname, \"..\");\nconst TEMPLATES = resolve(PACKAGE_ROOT, \"templates\");\n\n/**\n * The version of mountly-mcp doing the scaffolding, so the generated\n * package.json pins the API this CLI actually ships.\n *\n * Hardcoding the pin in the templates meant a major release left them behind:\n * `^3.1.0` cannot resolve to 4.0.0, so every scaffolded app installed a\n * package without the exports its own vite.config imported.\n */\nfunction ownVersion(): string {\n const pkg = JSON.parse(readFileSync(join(PACKAGE_ROOT, \"package.json\"), \"utf8\")) as {\n version?: string;\n };\n if (!pkg.version) throw new Error(\"mountly-mcp: cannot read own version from package.json\");\n return pkg.version;\n}\n\nconst SUPPORTED = [\"react\", \"vue\", \"svelte\", \"vanilla\"] as const;\ntype Framework = (typeof SUPPORTED)[number];\n\nexport interface CreateArgs {\n name: string;\n framework: Framework;\n dir: string;\n}\n\nexport function parseCreateArgs(argv: ReadonlyArray<string>): CreateArgs {\n const name = argv[0];\n if (!name || name.startsWith(\"-\")) {\n throw cliError(\n CLI_ERROR_CODES.UNKNOWN_OPTION,\n \"create requires a project name\",\n \"Example: mountly-mcp create my-app --framework react\",\n );\n }\n\n let framework: Framework = \"react\";\n let dir = `./${name}`;\n\n for (let i = 1; i < argv.length; i += 1) {\n const arg = argv[i];\n if (arg === \"--framework\" || arg === \"-f\") {\n const value = argv[++i];\n if (value === undefined) {\n throw cliError(\n CLI_ERROR_CODES.UNKNOWN_OPTION,\n `${arg} needs a value`,\n `Available: ${SUPPORTED.join(\", \")}`,\n );\n }\n if (!SUPPORTED.includes(value as Framework)) {\n throw cliError(\n CLI_ERROR_CODES.UNKNOWN_OPTION,\n `framework \"${value}\" not supported`,\n `Available: ${SUPPORTED.join(\", \")}`,\n );\n }\n framework = value as Framework;\n } else if (arg === \"--dir\") {\n const value = argv[++i];\n if (value === undefined) {\n throw cliError(\n CLI_ERROR_CODES.UNKNOWN_OPTION,\n \"--dir needs a value\",\n \"Example: --dir ./apps/my-app\",\n );\n }\n dir = value;\n } else {\n throw cliError(\n CLI_ERROR_CODES.UNKNOWN_OPTION,\n `unknown create option '${arg}'`,\n \"Options: --framework | --dir\",\n );\n }\n }\n\n return { name, framework, dir };\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 renderTemplates(dir: string, ctx: Record<string, string>): void {\n for (const entry of readdirSync(dir)) {\n const full = join(dir, entry);\n if (statSync(full).isDirectory()) {\n renderTemplates(full, ctx);\n continue;\n }\n if (!entry.endsWith(\".tmpl\")) continue;\n let content = readFileSync(full, \"utf8\");\n for (const [key, value] of Object.entries(ctx)) {\n content = content.split(`{{${key}}}`).join(value);\n }\n writeFileSync(full.replace(/\\.tmpl$/, \"\"), content);\n unlinkSync(full);\n }\n}\n\nexport async function createProject(args: CreateArgs): Promise<void> {\n const target = resolve(args.dir);\n if (existsSync(target)) {\n throw cliError(\n CLI_ERROR_CODES.TARGET_EXISTS,\n `target directory already exists: ${target}`,\n \"Choose another --dir, or remove the existing folder\",\n );\n }\n\n const tplDir = join(TEMPLATES, args.framework);\n if (!existsSync(tplDir)) {\n throw cliError(\n CLI_ERROR_CODES.MISSING_TEMPLATE,\n `template for framework \"${args.framework}\" is missing at ${tplDir}`,\n `Available: ${SUPPORTED.join(\", \")}`,\n );\n }\n\n mkdirSync(target, { recursive: true });\n cpSync(tplDir, target, { recursive: true });\n\n const slug = slugify(args.name) || \"app\";\n renderTemplates(target, {\n name: args.name,\n slug,\n uri: `ui://${slug}/dashboard`,\n framework: args.framework,\n mcpVersion: ownVersion(),\n });\n\n process.stdout.write(\n [\n \"\",\n `✓ Created MCP App ${args.name} (${args.framework}) at ${target}`,\n \"\",\n \"Next:\",\n ` cd ${args.dir}`,\n \" pnpm install\",\n \" pnpm dev\",\n \"\",\n ].join(\"\\n\"),\n );\n}\n"],"mappings":";;;;;;AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,SAAS,MAAM,eAAe;AACvC,SAAS,qBAAqB;AAG9B,IAAM,YAAY,QAAQ,cAAc,YAAY,GAAG,CAAC;AAExD,IAAM,eAAe,QAAQ,WAAW,IAAI;AAC5C,IAAM,YAAY,QAAQ,cAAc,WAAW;AAUnD,SAAS,aAAqB;AAC5B,QAAM,MAAM,KAAK,MAAM,aAAa,KAAK,cAAc,cAAc,GAAG,MAAM,CAAC;AAG/E,MAAI,CAAC,IAAI,QAAS,OAAM,IAAI,MAAM,wDAAwD;AAC1F,SAAO,IAAI;AACb;AAEA,IAAM,YAAY,CAAC,SAAS,OAAO,UAAU,SAAS;AAS/C,SAAS,gBAAgB,MAAyC;AACvE,QAAM,OAAO,KAAK,CAAC;AACnB,MAAI,CAAC,QAAQ,KAAK,WAAW,GAAG,GAAG;AACjC,UAAM;AAAA,MACJ,gBAAgB;AAAA,MAChB;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,MAAI,YAAuB;AAC3B,MAAI,MAAM,KAAK,IAAI;AAEnB,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,GAAG;AACvC,UAAM,MAAM,KAAK,CAAC;AAClB,QAAI,QAAQ,iBAAiB,QAAQ,MAAM;AACzC,YAAM,QAAQ,KAAK,EAAE,CAAC;AACtB,UAAI,UAAU,QAAW;AACvB,cAAM;AAAA,UACJ,gBAAgB;AAAA,UAChB,GAAG,GAAG;AAAA,UACN,cAAc,UAAU,KAAK,IAAI,CAAC;AAAA,QACpC;AAAA,MACF;AACA,UAAI,CAAC,UAAU,SAAS,KAAkB,GAAG;AAC3C,cAAM;AAAA,UACJ,gBAAgB;AAAA,UAChB,cAAc,KAAK;AAAA,UACnB,cAAc,UAAU,KAAK,IAAI,CAAC;AAAA,QACpC;AAAA,MACF;AACA,kBAAY;AAAA,IACd,WAAW,QAAQ,SAAS;AAC1B,YAAM,QAAQ,KAAK,EAAE,CAAC;AACtB,UAAI,UAAU,QAAW;AACvB,cAAM;AAAA,UACJ,gBAAgB;AAAA,UAChB;AAAA,UACA;AAAA,QACF;AAAA,MACF;AACA,YAAM;AAAA,IACR,OAAO;AACL,YAAM;AAAA,QACJ,gBAAgB;AAAA,QAChB,0BAA0B,GAAG;AAAA,QAC7B;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,MAAM,WAAW,IAAI;AAChC;AAEA,SAAS,QAAQ,MAAsB;AACrC,SAAO,KACJ,KAAK,EACL,YAAY,EACZ,QAAQ,eAAe,GAAG,EAC1B,QAAQ,YAAY,EAAE;AAC3B;AAEA,SAAS,gBAAgB,KAAa,KAAmC;AACvE,aAAW,SAAS,YAAY,GAAG,GAAG;AACpC,UAAM,OAAO,KAAK,KAAK,KAAK;AAC5B,QAAI,SAAS,IAAI,EAAE,YAAY,GAAG;AAChC,sBAAgB,MAAM,GAAG;AACzB;AAAA,IACF;AACA,QAAI,CAAC,MAAM,SAAS,OAAO,EAAG;AAC9B,QAAI,UAAU,aAAa,MAAM,MAAM;AACvC,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,GAAG,GAAG;AAC9C,gBAAU,QAAQ,MAAM,KAAK,GAAG,IAAI,EAAE,KAAK,KAAK;AAAA,IAClD;AACA,kBAAc,KAAK,QAAQ,WAAW,EAAE,GAAG,OAAO;AAClD,eAAW,IAAI;AAAA,EACjB;AACF;AAEA,eAAsB,cAAc,MAAiC;AACnE,QAAM,SAAS,QAAQ,KAAK,GAAG;AAC/B,MAAI,WAAW,MAAM,GAAG;AACtB,UAAM;AAAA,MACJ,gBAAgB;AAAA,MAChB,oCAAoC,MAAM;AAAA,MAC1C;AAAA,IACF;AAAA,EACF;AAEA,QAAM,SAAS,KAAK,WAAW,KAAK,SAAS;AAC7C,MAAI,CAAC,WAAW,MAAM,GAAG;AACvB,UAAM;AAAA,MACJ,gBAAgB;AAAA,MAChB,2BAA2B,KAAK,SAAS,mBAAmB,MAAM;AAAA,MAClE,cAAc,UAAU,KAAK,IAAI,CAAC;AAAA,IACpC;AAAA,EACF;AAEA,YAAU,QAAQ,EAAE,WAAW,KAAK,CAAC;AACrC,SAAO,QAAQ,QAAQ,EAAE,WAAW,KAAK,CAAC;AAE1C,QAAM,OAAO,QAAQ,KAAK,IAAI,KAAK;AACnC,kBAAgB,QAAQ;AAAA,IACtB,MAAM,KAAK;AAAA,IACX;AAAA,IACA,KAAK,QAAQ,IAAI;AAAA,IACjB,WAAW,KAAK;AAAA,IAChB,YAAY,WAAW;AAAA,EACzB,CAAC;AAED,UAAQ,OAAO;AAAA,IACb;AAAA,MACE;AAAA,MACA,0BAAqB,KAAK,IAAI,KAAK,KAAK,SAAS,QAAQ,MAAM;AAAA,MAC/D;AAAA,MACA;AAAA,MACA,QAAQ,KAAK,GAAG;AAAA,MAChB;AAAA,MACA;AAAA,MACA;AAAA,IACF,EAAE,KAAK,IAAI;AAAA,EACb;AACF;","names":[]}
package/dist/dev/index.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  } from "../chunk-KWVGW5Q7.js";
5
5
  import {
6
6
  connectMcpServer
7
- } from "../chunk-UECZA34Q.js";
7
+ } from "../chunk-OWAXBNZA.js";
8
8
  import "../chunk-YJA4H6IB.js";
9
9
  export {
10
10
  connectMcpServer,