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.
- package/GLOSSARY.md +37 -0
- package/README.md +103 -190
- package/dist/add-DAFMA4F3.js +263 -0
- package/dist/add-DAFMA4F3.js.map +1 -0
- package/dist/add.d.ts +13 -0
- package/dist/chunk-EOWAWZH2.js +34 -0
- package/dist/chunk-EOWAWZH2.js.map +1 -0
- package/dist/{chunk-KWVGW5Q7.js → chunk-GN2IQYV3.js} +1 -1
- package/dist/chunk-GN2IQYV3.js.map +1 -0
- package/dist/{chunk-UECZA34Q.js → chunk-OWAXBNZA.js} +10 -5
- package/dist/chunk-OWAXBNZA.js.map +1 -0
- package/dist/cli.js +97 -25
- package/dist/cli.js.map +1 -1
- package/dist/compose/index.d.ts +48 -0
- package/dist/compose/index.js +141 -0
- package/dist/compose/index.js.map +1 -0
- package/dist/connect-server-P2UZ62YC.js +7 -0
- package/dist/create.js +43 -15
- package/dist/create.js.map +1 -1
- package/dist/dev/host-entry.js +7 -0
- package/dist/dev/index.js +2 -2
- package/dist/doctor-B54H6CRN.js +122 -0
- package/dist/doctor-B54H6CRN.js.map +1 -0
- package/dist/doctor.d.ts +16 -0
- package/dist/errors-4ERW7HZB.js +11 -0
- package/dist/errors-4ERW7HZB.js.map +1 -0
- package/dist/errors.d.ts +25 -0
- package/dist/json-render/index.js +4 -4
- package/package.json +17 -5
- package/templates/react/README.md.tmpl +83 -6
- package/templates/react/package.json.tmpl +2 -0
- package/templates/react/serve-stdio.mjs.tmpl +9 -0
- package/templates/react/server.mjs.tmpl +6 -1
- package/templates/react/src/view.tsx.tmpl +1 -1
- package/templates/svelte/README.md.tmpl +27 -4
- package/templates/svelte/package.json.tmpl +2 -0
- package/templates/svelte/serve-stdio.mjs.tmpl +5 -0
- package/templates/svelte/server.mjs.tmpl +6 -1
- package/templates/svelte/src/Dashboard.svelte.tmpl +1 -1
- package/templates/vanilla/README.md.tmpl +27 -11
- package/templates/vanilla/package.json.tmpl +2 -0
- package/templates/vanilla/serve-stdio.mjs.tmpl +5 -0
- package/templates/vanilla/server.mjs.tmpl +6 -1
- package/templates/vanilla/src/view.ts.tmpl +1 -1
- package/templates/vue/README.md.tmpl +27 -4
- package/templates/vue/package.json.tmpl +2 -0
- package/templates/vue/serve-stdio.mjs.tmpl +5 -0
- package/templates/vue/server.mjs.tmpl +6 -1
- package/templates/vue/src/Dashboard.vue.tmpl +1 -1
- package/dist/chunk-KWVGW5Q7.js.map +0 -1
- package/dist/chunk-UECZA34Q.js.map +0 -1
- package/dist/connect-server-7L4HGKKL.js +0 -7
- /package/dist/{connect-server-7L4HGKKL.js.map → connect-server-P2UZ62YC.js.map} +0 -0
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
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
8
|
+
} from "./chunk-GN2IQYV3.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)
|
|
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
|
|
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)
|
|
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
|
|
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
|
|
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)
|
|
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
|
|
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
|
|
130
|
-
|
|
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
|
|
139
|
-
|
|
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
|
|
145
|
-
|
|
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
|
|
151
|
-
|
|
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-
|
|
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
|
|
228
|
-
|
|
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
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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":";;;;;;;;AAQA,SAAS,gBAAgB;AACzB,SAAS,eAAe;AA0BxB,SAAS,UAAU,MAAmC;AACpD,QAAM,OAAa,EAAE,MAAM,KAAK;AAChC,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,GAAG;AACvC,UAAM,MAAM,KAAK,CAAC;AAClB,UAAM,OAAO,MAAc;AACzB,YAAM,QAAQ,KAAK,IAAI,CAAC;AACxB,UAAI,UAAU,OAAW,OAAM,IAAI,MAAM,gBAAgB,GAAG,gBAAgB;AAC5E,WAAK;AACL,aAAO;AAAA,IACT;AACA,QAAI,QAAQ,cAAc,QAAQ,KAAM,MAAK,SAAS,KAAK;AAAA,aAClD,QAAQ,gBAAgB,QAAQ,KAAM,MAAK,WAAW,KAAK;AAAA,aAC3D,QAAQ,cAAc,QAAQ,KAAM,MAAK,SAAS,KAAK;AAAA,aACvD,QAAQ,WAAW,QAAQ,KAAM,MAAK,MAAM,KAAK;AAAA,aACjD,QAAQ,YAAY,QAAQ,KAAM,MAAK,OAAO,OAAO,KAAK,CAAC;AAAA,aAC3D,QAAQ,YAAa,MAAK,OAAO;AAAA,aACjC,QAAQ,YAAY,QAAQ,MAAM;AACzC,iBAAW;AACX,cAAQ,KAAK,CAAC;AAAA,IAChB,MAAO,OAAM,IAAI,MAAM,gCAAgC,GAAG,GAAG;AAAA,EAC/D;AACA,SAAO;AACT;AAEA,SAAS,gBAAgB,MAAyC;AAChE,QAAM,OAAmB,EAAE,MAAM,CAAC,GAAG,QAAQ,OAAO,QAAQ,OAAO,MAAM,MAAM;AAC/E,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,GAAG;AACvC,UAAM,MAAM,KAAK,CAAC;AAClB,UAAM,OAAO,MAAc;AACzB,YAAM,QAAQ,KAAK,IAAI,CAAC;AACxB,UAAI,UAAU,OAAW,OAAM,IAAI,MAAM,gBAAgB,GAAG,gBAAgB;AAC5E,WAAK;AACL,aAAO;AAAA,IACT;AACA,QAAI,QAAQ,gBAAgB,QAAQ,KAAM,MAAK,WAAW,KAAK;AAAA,aACtD,QAAQ,SAAU,MAAK,KAAK,KAAK,KAAK,CAAC;AAAA,aACvC,QAAQ,WAAY,MAAK,SAAS;AAAA,aAClC,QAAQ,WAAY,MAAK,SAAS;AAAA,aAClC,QAAQ,SAAU,MAAK,OAAO;AAAA,QAClC,OAAM,IAAI,MAAM,uCAAuC,GAAG,GAAG;AAAA,EACpE;AACA,MAAI,KAAK,YAAY,KAAK,KAAK,SAAS,GAAG;AACzC,UAAM,IAAI,MAAM,mEAAmE;AAAA,EACrF;AACA,SAAO;AACT;AAEA,SAAS,eAAe,MAAwC;AAC9D,QAAM,OAAkB,CAAC;AACzB,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,GAAG;AACvC,UAAM,MAAM,KAAK,CAAC;AAClB,QAAI,QAAQ,cAAc,QAAQ,MAAM;AACtC,YAAM,QAAQ,KAAK,IAAI,CAAC;AACxB,UAAI,UAAU,OAAW,OAAM,IAAI,MAAM,gBAAgB,GAAG,gBAAgB;AAC5E,WAAK,SAAS;AACd,WAAK;AAAA,IACP,MAAO,OAAM,IAAI,MAAM,sCAAsC,GAAG,GAAG;AAAA,EACrE;AACA,SAAO;AACT;AAEA,SAAS,aAAmB;AAC1B,UAAQ,OAAO,MAAM;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,CAoCtB;AACD;AAEA,eAAe,UAAU,MAAgC;AACvD,QAAM,EAAE,cAAc,IAAI,MAAM,OAAO,MAAM;AAC7C,QAAM,UAAU,MAAM,cAAc,EAAE,YAAY,KAAK,OAAO,GAAG,KAAK;AACtE,QAAM,QAAQ,SAAS;AACzB;AAEA,eAAe,OAAO,MAAiC;AACrD,QAAM,EAAE,yBAAyB,cAAc,IAAI,MAAM,OAAO,oBAAoB;AACpF,QAAM,SAAS,MAAM,cAAc;AAAA,IACjC,cAAc,KAAK;AAAA,IACnB,WAAW,KAAK,KAAK,SAAS,IAAI,KAAK,OAAO;AAAA,IAC9C,QAAQ,KAAK;AAAA,EACf,CAAC;AACD,UAAQ,OAAO;AAAA,IACb,KAAK,OAAO,KAAK,UAAU,QAAQ,MAAM,CAAC,IAAI,wBAAwB,MAAM;AAAA,EAC9E;AACA,MAAI,CAAC,OAAO,MAAO,KAAK,UAAU,OAAO,YAAY,SAAS,EAAI,SAAQ,WAAW;AACvF;AAMA,eAAe,gBACb,YACA,SAC2E;AAC3E,QAAM,EAAE,mBAAmB,IAAI,MAAM,OAAO,MAAM;AAClD,QAAM,SAAS,MAAM,mBAAmB,EAAE,SAAS,SAAS,MAAM,cAAc,GAAG,UAAU;AAC7F,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAGA,QAAM,WAAY,OAAO,OAAO,WAAW,CAAC,GAAiB;AAAA,IAC3D,OAAO;AAAA,EACT;AACA,QAAM,SAAS,QAAQ,KAAK,CAAC,MAAM,GAAG,KAAK,UAAU;AACrD,MAAI,CAAC,QAAQ,KAAK,YAAY;AAC5B,UAAM,IAAI;AAAA,MACR,+CAA+C,OAAO,IAAI;AAAA,IAC5D;AAAA,EACF;AACA,QAAM,OAAO,OAAO,IAAI,WAAW;AACnC,MAAI,KAAK,SAAS,KAAK,YAAY,QAAW;AAC5C,UAAM,IAAI;AAAA,MACR,iFAAiF,KAAK,IAAI,CAAC,QAAQ,IAAI,IAAI,EAAE,KAAK,IAAI,CAAC;AAAA,IACzH;AAAA,EACF;AACA,QAAM,WAAW,YAAY,SAAY,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,QAAQ,IAAI,SAAS,OAAO;AAC1F,MAAI,CAAC,UAAU;AACb,UAAM,IAAI;AAAA,MACR,+BAA+B,OAAO,OAAO,CAAC,iBAAiB,KAAK,IAAI,CAAC,QAAQ,IAAI,IAAI,EAAE,KAAK,IAAI,CAAC;AAAA,IACvG;AAAA,EACF;AACA,SAAO;AAAA,IACL,SAAS;AAAA,IACT,QAAS,OAAO,OAAO,OAAO,UAAiC;AAAA,IAC/D,MAAO,OAAO,OAAO,QAA+B,QAAQ,IAAI;AAAA,EAClE;AACF;AAQA,eAAe,YAAY,MAAc,YAAY,MAAuB;AAC1E,QAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,aAAS;AACP,QAAI;AACF,YAAM,SAAS,IAAI;AACnB;AAAA,IACF,QAAQ;AACN,UAAI,KAAK,IAAI,IAAI,SAAU,OAAM,IAAI,MAAM,sCAAsC,IAAI,EAAE;AACvF,YAAM,IAAI,QAAQ,CAAC,SAAS,WAAW,MAAM,EAAE,CAAC;AAAA,IAClD;AAAA,EACF;AACF;AAEA,eAAe,aAAa,MAA4D;AACtF,QAAM,OAAO,QAAQ;AACrB,MAAI;AACF,WAAO,KAAK,MAAM,MAAM,SAAS,QAAQ,IAAI,GAAG,MAAM,CAAC;AAAA,EACzD,SAAS,OAAO;AAGd,QAAI,KAAM,OAAM,IAAI,MAAM,yCAAyC,IAAI,MAAM,OAAO,KAAK,CAAC,EAAE;AAC5F,WAAO,CAAC;AAAA,EACV;AACF;AAEA,eAAe,IAAI,MAA2B;AAC5C,QAAM,EAAE,SAAS,QAAQ,KAAK,IAAI,MAAM,gBAAgB,KAAK,QAAQ,KAAK,GAAG;AAC7E,QAAM,WAAW,MAAM,aAAa,KAAK,QAAQ;AAIjD,MAAI;AACJ,MAAI;AAEJ,QAAM,EAAE,MAAM,IAAI,MAAM,OAAO,MAAM;AACrC,QAAM,WAAW,QAAQ,SACrB,QAAQ,MAAM,QAAQ,MAAM,IAC5B,QAAQ,MAAM,QAAQ,GAAG,QAAQ,IAAI,OAAO;AAIhD,MAAI;AACJ,MAAI,UAAU;AAEd,QAAM,sBAAsB,QAAQ,IAAI;AACxC,UAAQ,IAAI,2BAA2B,QAAQ;AAC/C,QAAM,UAAU,MAAM,MAAM;AAAA,IAC1B,YAAY,KAAK,UAAU;AAAA,IAC3B,OAAO,EAAE,OAAO,CAAC,EAAE;AAAA,IACnB,UAAU;AAAA,EACZ,CAAC,EAAE,QAAQ,MAAM;AACf,QAAI,wBAAwB,OAAW,QAAO,QAAQ,IAAI;AAAA,QACrD,SAAQ,IAAI,2BAA2B;AAAA,EAC9C,CAAC;AAID,QAAM,gBAAgB;AAKtB,gBAAc,GAAG,SAAS,CAAC,UAAU;AACnC,QAAI,MAAM,SAAS,SAAS;AAC1B,cAAQ,OAAO,MAAM,oCAA+B,MAAM,OAAO,WAAW,SAAS;AAAA,CAAI;AACzF;AAAA,IACF;AACA,QAAI,MAAM,SAAS,MAAO;AAC1B,QAAI,SAAS;AACX,WAAK,YAAY,GAAG,QAAQ,YAAY,EAAE;AAAA,QACxC,MAAM;AACJ,gBAAM,OAAO;AACb,kBAAQ,OAAO,MAAM,wBAAwB;AAAA,QAC/C;AAAA,QACA,CAAC,UAAmB,QAAQ,OAAO,MAAM,GAAG,OAAO,KAAK,CAAC;AAAA,CAAI;AAAA,MAC/D;AACA;AAAA,IACF;AACA,cAAU;AACV,UAAM,YAAY;AAChB,YAAM,YAAY,GAAG,QAAQ,YAAY;AACzC,UAAI,KAAK,QAAQ;AACf,cAAM,EAAE,iBAAiB,IAAI,MAAM,OAAO,8BAAyB;AACnE,iBAAS,MAAM,iBAAiB,KAAK,MAAM;AAC3C,mBAAW,MAAM,OAAO,QAAQ,QAAQ,GAAG;AAC3C,YAAI,CAAC,UAAU;AACb,gBAAM,IAAI;AAAA,YACR,wBAAwB,KAAK,MAAM,iCAAiC,QAAQ,GAAG;AAAA,UACjF;AAAA,QACF;AAAA,MACF;AACA,aAAO,MAAM,aAAa;AAAA,QACxB;AAAA,QACA;AAAA,QACA,WAAW,CAAC;AAAA,QACZ;AAAA,QACA,UAAU,QAAQ;AAAA,QAClB,UAAU,KAAK;AAAA,MACjB,CAAC;AACD,cAAQ,OAAO;AAAA,QACb;AAAA;AAAA,aACgB,KAAK,OAAO;AAAA,aACZ,KAAK,UAAU;AAAA,aACf,QAAQ;AAAA,aACR,OAAO,KAAK,QAAQ,EAAE,UAAU,6BAAwB;AAAA,aACxD,SAAS,GAAG,KAAK,MAAM,WAAM,YAAY,sBAAsB,QAAQ,GAAG,KAAK,uCAAkC;AAAA;AAAA;AAAA;AAAA;AAAA,MAEnI;AACA,UAAI,KAAK,MAAM;AACb,cAAM,EAAE,MAAM,IAAI,MAAM,OAAO,eAAoB;AACnD,cAAM,SACJ,QAAQ,aAAa,WACjB,SACA,QAAQ,aAAa,UACnB,UACA;AACR,cAAM,QAAQ,CAAC,KAAK,OAAO,GAAG,EAAE,OAAO,UAAU,UAAU,MAAM,OAAO,KAAK,CAAC,EAAE,MAAM;AAAA,MACxF;AAAA,IACF,GAAG,EAAE,MAAM,CAAC,UAAmB;AAG7B,cAAQ,OAAO,MAAM,GAAG,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,CAAI;AAClF,WAAK,QAAQ,IAAI,CAAC,cAAc,MAAM,GAAG,QAAQ,MAAM,CAAC,CAAC,EAAE,QAAQ,MAAM,QAAQ,KAAK,CAAC,CAAC;AAAA,IAC1F,CAAC;AAAA,EACH,CAAC;AAED,QAAM,WAAW,MAAY;AAC3B,UAAM,YAAY;AAChB,YAAM,cAAc,MAAM;AAC1B,YAAM,MAAM,MAAM;AAClB,YAAM,QAAQ,MAAM;AACpB,cAAQ,KAAK,CAAC;AAAA,IAChB,GAAG;AAAA,EACL;AACA,UAAQ,GAAG,UAAU,QAAQ;AAC7B,UAAQ,GAAG,WAAW,QAAQ;AAChC;AAEA,eAAe,OAAsB;AACnC,QAAM,CAAC,SAAS,GAAG,IAAI,IAAI,QAAQ,KAAK,MAAM,CAAC;AAC/C,MAAI,YAAY,UAAa,YAAY,YAAY,YAAY,MAAM;AACrE,eAAW;AACX,YAAQ,KAAK,YAAY,SAAY,IAAI,CAAC;AAAA,EAC5C;AACA,MAAI,YAAY,UAAU;AACxB,UAAM,EAAE,eAAe,gBAAgB,IAAI,MAAM,OAAO,aAAa;AACrE,WAAO,cAAc,gBAAgB,IAAI,CAAC;AAAA,EAC5C;AACA,MAAI,YAAY,MAAO,QAAO,IAAI,UAAU,IAAI,CAAC;AACjD,MAAI,YAAY,QAAS,QAAO,UAAU,eAAe,IAAI,CAAC;AAC9D,MAAI,YAAY,SAAU,QAAO,OAAO,gBAAgB,IAAI,CAAC;AAC7D,UAAQ,OAAO,MAAM,iCAAiC,OAAO;AAAA,CAAgC;AAC7F,UAAQ,KAAK,CAAC;AAChB;AAEA,KAAK,EAAE,MAAM,CAAC,UAAmB;AAC/B,UAAQ,OAAO,MAAM,GAAG,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,CAAI;AAClF,UAAQ,KAAK,CAAC;AAChB,CAAC;","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,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Show a page composed from many teams' custom elements inside an MCP host.
|
|
3
|
+
*
|
|
4
|
+
* The agent is the composer: `show_page` carries the catalog's prompt as its
|
|
5
|
+
* description, the agent calls it with a tree, the server validates (or
|
|
6
|
+
* repairs) it, and the `ui://` view renders it with `mountly-compose` — each
|
|
7
|
+
* team's code loading on first use. When the user acts in a widget, the view
|
|
8
|
+
* sends the agent its next turn, with the current page, so it edits rather than
|
|
9
|
+
* starts over.
|
|
10
|
+
*/
|
|
11
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
12
|
+
import type { Catalog } from "mountly-compose";
|
|
13
|
+
export interface ComposeViewOptions {
|
|
14
|
+
/**
|
|
15
|
+
* The view's import map. Must map `mountly-compose` and
|
|
16
|
+
* `@modelcontextprotocol/ext-apps` (its `app-with-deps` build), plus
|
|
17
|
+
* whatever the teams' peer embeds import: `react`, `react/jsx-runtime`,
|
|
18
|
+
* `react-dom/client`, `mountly-react`, `mountly/embed`.
|
|
19
|
+
*/
|
|
20
|
+
imports: Record<string, string>;
|
|
21
|
+
/** The page's own CSS: host layout (`ui-stack`, `ui-grid`, `ui-note`) and design tokens. */
|
|
22
|
+
styles?: string;
|
|
23
|
+
/** Document title. */
|
|
24
|
+
title?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface RegisterComposeAppOptions extends ComposeViewOptions {
|
|
27
|
+
catalog: Catalog;
|
|
28
|
+
/** MCP tool name. Default `show_page`. */
|
|
29
|
+
name?: string;
|
|
30
|
+
/** `ui://` resource for the view. */
|
|
31
|
+
resourceUri?: string;
|
|
32
|
+
/** Appended to the tool description: what the agent may fill ids from, house rules. */
|
|
33
|
+
instructions?: string;
|
|
34
|
+
}
|
|
35
|
+
/** The view HTML: an import map, the host's CSS, and a module that renders tool results. */
|
|
36
|
+
export declare function composeViewHtml(options: ComposeViewOptions): string;
|
|
37
|
+
/**
|
|
38
|
+
* Register the `show_page` tool and its `ui://` view on an MCP server.
|
|
39
|
+
*
|
|
40
|
+
* The input is `{ page }`, typed loosely on purpose: a strict schema would make
|
|
41
|
+
* the SDK reject a near-miss before the handler could repair it. The handler
|
|
42
|
+
* validates against the catalog, strips small mistakes, and otherwise returns
|
|
43
|
+
* the reasons as a tool error the agent can act on.
|
|
44
|
+
*/
|
|
45
|
+
export declare function registerComposeApp(server: McpServer, options: RegisterComposeAppOptions): {
|
|
46
|
+
tool: string;
|
|
47
|
+
resourceUri: string;
|
|
48
|
+
};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// src/compose/index.ts
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import {
|
|
4
|
+
RESOURCE_MIME_TYPE,
|
|
5
|
+
registerAppResource,
|
|
6
|
+
registerAppTool
|
|
7
|
+
} from "@modelcontextprotocol/ext-apps/server";
|
|
8
|
+
var DEFAULT_RESOURCE_URI = "ui://mountly/compose.html";
|
|
9
|
+
var DEFAULT_TOOL_NAME = "show_page";
|
|
10
|
+
function composeViewHtml(options) {
|
|
11
|
+
const importMap = JSON.stringify({ imports: options.imports }).replace(/</g, "\\u003c");
|
|
12
|
+
const styles = (options.styles ?? "").replace(/<\/style/gi, "<\\/style");
|
|
13
|
+
const title = (options.title ?? "Page").replace(/[<&]/g, "");
|
|
14
|
+
return `<!doctype html>
|
|
15
|
+
<html lang="en">
|
|
16
|
+
<head>
|
|
17
|
+
<meta charset="utf-8" />
|
|
18
|
+
<title>${title}</title>
|
|
19
|
+
<script type="importmap">${importMap}</script>
|
|
20
|
+
<style>${styles}</style>
|
|
21
|
+
</head>
|
|
22
|
+
<body>
|
|
23
|
+
<main id="page"></main>
|
|
24
|
+
<script type="module">
|
|
25
|
+
import { App } from "@modelcontextprotocol/ext-apps";
|
|
26
|
+
import { connectView } from "mountly-compose";
|
|
27
|
+
|
|
28
|
+
const app = new App({ name: "mountly-compose", version: "1.0.0" });
|
|
29
|
+
connectView(app, document.querySelector("#page"), {
|
|
30
|
+
request: "Show the updated page with ${DEFAULT_TOOL_NAME}.",
|
|
31
|
+
});
|
|
32
|
+
await app.connect();
|
|
33
|
+
</script>
|
|
34
|
+
</body>
|
|
35
|
+
</html>
|
|
36
|
+
`;
|
|
37
|
+
}
|
|
38
|
+
function originsOf(options) {
|
|
39
|
+
const urls = [
|
|
40
|
+
...Object.values(options.imports),
|
|
41
|
+
...options.catalog.elements.flatMap((el) => el.embed ? [el.embed] : [])
|
|
42
|
+
];
|
|
43
|
+
const origins = /* @__PURE__ */ new Set();
|
|
44
|
+
for (const url of urls) {
|
|
45
|
+
try {
|
|
46
|
+
origins.add(new URL(url).origin);
|
|
47
|
+
} catch {
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return [...origins];
|
|
51
|
+
}
|
|
52
|
+
function tagsOf(node, into = /* @__PURE__ */ new Set()) {
|
|
53
|
+
into.add(node.tag);
|
|
54
|
+
for (const child of node.children ?? []) tagsOf(child, into);
|
|
55
|
+
return into;
|
|
56
|
+
}
|
|
57
|
+
function viewPayload(catalog, page) {
|
|
58
|
+
return { page, elements: catalog.elements, actions: catalog.actions };
|
|
59
|
+
}
|
|
60
|
+
function registerComposeApp(server, options) {
|
|
61
|
+
const { catalog } = options;
|
|
62
|
+
const name = options.name ?? DEFAULT_TOOL_NAME;
|
|
63
|
+
const resourceUri = options.resourceUri ?? DEFAULT_RESOURCE_URI;
|
|
64
|
+
if (!resourceUri.startsWith("ui://")) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`mountly-mcp/compose: resourceUri must use 'ui://' (received '${resourceUri}')`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
const origins = originsOf(options);
|
|
70
|
+
const csp = { connectDomains: origins, resourceDomains: origins, frameDomains: [] };
|
|
71
|
+
const html = composeViewHtml(options);
|
|
72
|
+
registerAppResource(
|
|
73
|
+
server,
|
|
74
|
+
"compose_view",
|
|
75
|
+
resourceUri,
|
|
76
|
+
{
|
|
77
|
+
mimeType: RESOURCE_MIME_TYPE,
|
|
78
|
+
description: "A page composed from teams' custom elements.",
|
|
79
|
+
_meta: { ui: { prefersBorder: false, csp } }
|
|
80
|
+
},
|
|
81
|
+
async () => ({
|
|
82
|
+
contents: [
|
|
83
|
+
{ uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html, _meta: { ui: { csp } } }
|
|
84
|
+
]
|
|
85
|
+
})
|
|
86
|
+
);
|
|
87
|
+
const description = [
|
|
88
|
+
`Show the user a page built from the components below. Call it with { "page": <tree> }.`,
|
|
89
|
+
`To change the page, call it again with the whole updated page.`,
|
|
90
|
+
options.instructions ?? "",
|
|
91
|
+
"",
|
|
92
|
+
catalog.prompt()
|
|
93
|
+
].filter((line, index) => line || index > 2).join("\n");
|
|
94
|
+
registerAppTool(
|
|
95
|
+
server,
|
|
96
|
+
name,
|
|
97
|
+
{
|
|
98
|
+
title: "Show page",
|
|
99
|
+
description,
|
|
100
|
+
inputSchema: { page: z.unknown().describe("The page as a tree: { tag, attrs, children }.") },
|
|
101
|
+
_meta: { ui: { resourceUri, visibility: ["model", "app"] } }
|
|
102
|
+
},
|
|
103
|
+
(async (args) => {
|
|
104
|
+
const errors = catalog.validate(args.page);
|
|
105
|
+
let page = args.page;
|
|
106
|
+
let note = "";
|
|
107
|
+
if (errors.length) {
|
|
108
|
+
const { tree, dropped } = catalog.repair(args.page);
|
|
109
|
+
if (!tree || catalog.validate(tree).length) {
|
|
110
|
+
return {
|
|
111
|
+
isError: true,
|
|
112
|
+
content: [
|
|
113
|
+
{
|
|
114
|
+
type: "text",
|
|
115
|
+
text: `The page cannot be shown:
|
|
116
|
+
${errors.join("\n")}
|
|
117
|
+
Fix these and call ${name} again.`
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
page = tree;
|
|
123
|
+
note = ` Removed what the catalog does not allow: ${dropped.join("; ")}.`;
|
|
124
|
+
}
|
|
125
|
+
const hostTags = new Set(
|
|
126
|
+
catalog.elements.filter((el) => el.team === "host").map((el) => el.tag)
|
|
127
|
+
);
|
|
128
|
+
const tags = [...tagsOf(page)].filter((tag) => !hostTags.has(tag));
|
|
129
|
+
return {
|
|
130
|
+
content: [{ type: "text", text: `Showing ${tags.join(", ") || "the page"}.${note}` }],
|
|
131
|
+
structuredContent: viewPayload(catalog, page)
|
|
132
|
+
};
|
|
133
|
+
})
|
|
134
|
+
);
|
|
135
|
+
return { tool: name, resourceUri };
|
|
136
|
+
}
|
|
137
|
+
export {
|
|
138
|
+
composeViewHtml,
|
|
139
|
+
registerComposeApp
|
|
140
|
+
};
|
|
141
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/compose/index.ts"],"sourcesContent":["/**\n * Show a page composed from many teams' custom elements inside an MCP host.\n *\n * The agent is the composer: `show_page` carries the catalog's prompt as its\n * description, the agent calls it with a tree, the server validates (or\n * repairs) it, and the `ui://` view renders it with `mountly-compose` — each\n * team's code loading on first use. When the user acts in a widget, the view\n * sends the agent its next turn, with the current page, so it edits rather than\n * starts over.\n */\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\nimport {\n RESOURCE_MIME_TYPE,\n registerAppResource,\n registerAppTool,\n} from \"@modelcontextprotocol/ext-apps/server\";\nimport type { Catalog, Tree } from \"mountly-compose\";\n\nconst DEFAULT_RESOURCE_URI = \"ui://mountly/compose.html\";\nconst DEFAULT_TOOL_NAME = \"show_page\";\n\nexport interface ComposeViewOptions {\n /**\n * The view's import map. Must map `mountly-compose` and\n * `@modelcontextprotocol/ext-apps` (its `app-with-deps` build), plus\n * whatever the teams' peer embeds import: `react`, `react/jsx-runtime`,\n * `react-dom/client`, `mountly-react`, `mountly/embed`.\n */\n imports: Record<string, string>;\n /** The page's own CSS: host layout (`ui-stack`, `ui-grid`, `ui-note`) and design tokens. */\n styles?: string;\n /** Document title. */\n title?: string;\n}\n\nexport interface RegisterComposeAppOptions extends ComposeViewOptions {\n catalog: Catalog;\n /** MCP tool name. Default `show_page`. */\n name?: string;\n /** `ui://` resource for the view. */\n resourceUri?: string;\n /** Appended to the tool description: what the agent may fill ids from, house rules. */\n instructions?: string;\n}\n\n/** The view HTML: an import map, the host's CSS, and a module that renders tool results. */\nexport function composeViewHtml(options: ComposeViewOptions): string {\n const importMap = JSON.stringify({ imports: options.imports }).replace(/</g, \"\\\\u003c\");\n const styles = (options.styles ?? \"\").replace(/<\\/style/gi, \"<\\\\/style\");\n const title = (options.title ?? \"Page\").replace(/[<&]/g, \"\");\n return `<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <title>${title}</title>\n <script type=\"importmap\">${importMap}</script>\n <style>${styles}</style>\n </head>\n <body>\n <main id=\"page\"></main>\n <script type=\"module\">\n import { App } from \"@modelcontextprotocol/ext-apps\";\n import { connectView } from \"mountly-compose\";\n\n const app = new App({ name: \"mountly-compose\", version: \"1.0.0\" });\n connectView(app, document.querySelector(\"#page\"), {\n request: \"Show the updated page with ${DEFAULT_TOOL_NAME}.\",\n });\n await app.connect();\n </script>\n </body>\n</html>\n`;\n}\n\n/** Every origin the view loads code or data from, for its content security policy. */\nfunction originsOf(options: RegisterComposeAppOptions): string[] {\n const urls = [\n ...Object.values(options.imports),\n ...options.catalog.elements.flatMap((el) => (el.embed ? [el.embed] : [])),\n ];\n const origins = new Set<string>();\n for (const url of urls) {\n try {\n origins.add(new URL(url).origin);\n } catch {\n // A relative URL is same-origin with the view and needs no entry.\n }\n }\n return [...origins];\n}\n\nfunction tagsOf(node: Tree, into = new Set<string>()): Set<string> {\n into.add(node.tag);\n for (const child of node.children ?? []) tagsOf(child, into);\n return into;\n}\n\n/**\n * What the view renders with: the page, and the catalog's elements and actions.\n * All the elements, not just the page's: an action names the widgets the next\n * page must show, and the view hands that to the agent on the next turn.\n */\nfunction viewPayload(catalog: Catalog, page: Tree) {\n return { page, elements: catalog.elements, actions: catalog.actions };\n}\n\n/**\n * Register the `show_page` tool and its `ui://` view on an MCP server.\n *\n * The input is `{ page }`, typed loosely on purpose: a strict schema would make\n * the SDK reject a near-miss before the handler could repair it. The handler\n * validates against the catalog, strips small mistakes, and otherwise returns\n * the reasons as a tool error the agent can act on.\n */\nexport function registerComposeApp(\n server: McpServer,\n options: RegisterComposeAppOptions,\n): { tool: string; resourceUri: string } {\n const { catalog } = options;\n const name = options.name ?? DEFAULT_TOOL_NAME;\n const resourceUri = options.resourceUri ?? DEFAULT_RESOURCE_URI;\n if (!resourceUri.startsWith(\"ui://\")) {\n throw new Error(\n `mountly-mcp/compose: resourceUri must use 'ui://' (received '${resourceUri}')`,\n );\n }\n const origins = originsOf(options);\n const csp = { connectDomains: origins, resourceDomains: origins, frameDomains: [] };\n const html = composeViewHtml(options);\n\n registerAppResource(\n server,\n \"compose_view\",\n resourceUri,\n {\n mimeType: RESOURCE_MIME_TYPE,\n description: \"A page composed from teams' custom elements.\",\n _meta: { ui: { prefersBorder: false, csp } },\n },\n async () => ({\n contents: [\n { uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html, _meta: { ui: { csp } } },\n ],\n }),\n );\n\n const description = [\n `Show the user a page built from the components below. Call it with { \"page\": <tree> }.`,\n `To change the page, call it again with the whole updated page.`,\n options.instructions ?? \"\",\n \"\",\n catalog.prompt(),\n ]\n .filter((line, index) => line || index > 2)\n .join(\"\\n\");\n\n registerAppTool(\n server,\n name,\n {\n title: \"Show page\",\n description,\n inputSchema: { page: z.unknown().describe(\"The page as a tree: { tag, attrs, children }.\") },\n _meta: { ui: { resourceUri, visibility: [\"model\", \"app\"] } },\n } as never,\n (async (args: { page?: unknown }) => {\n const errors = catalog.validate(args.page);\n let page = args.page as Tree;\n let note = \"\";\n if (errors.length) {\n const { tree, dropped } = catalog.repair(args.page);\n if (!tree || catalog.validate(tree).length) {\n return {\n isError: true,\n content: [\n {\n type: \"text\",\n text: `The page cannot be shown:\\n${errors.join(\"\\n\")}\\nFix these and call ${name} again.`,\n },\n ],\n };\n }\n page = tree;\n note = ` Removed what the catalog does not allow: ${dropped.join(\"; \")}.`;\n }\n const hostTags = new Set(\n catalog.elements.filter((el) => el.team === \"host\").map((el) => el.tag),\n );\n const tags = [...tagsOf(page)].filter((tag) => !hostTags.has(tag));\n return {\n content: [{ type: \"text\", text: `Showing ${tags.join(\", \") || \"the page\"}.${note}` }],\n structuredContent: viewPayload(catalog, page),\n };\n }) as never,\n );\n\n return { tool: name, resourceUri };\n}\n"],"mappings":";AAWA,SAAS,SAAS;AAClB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP,IAAM,uBAAuB;AAC7B,IAAM,oBAAoB;AA2BnB,SAAS,gBAAgB,SAAqC;AACnE,QAAM,YAAY,KAAK,UAAU,EAAE,SAAS,QAAQ,QAAQ,CAAC,EAAE,QAAQ,MAAM,SAAS;AACtF,QAAM,UAAU,QAAQ,UAAU,IAAI,QAAQ,cAAc,WAAW;AACvE,QAAM,SAAS,QAAQ,SAAS,QAAQ,QAAQ,SAAS,EAAE;AAC3D,SAAO;AAAA;AAAA;AAAA;AAAA,aAII,KAAK;AAAA,+BACa,SAAS;AAAA,aAC3B,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,+CAU4B,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOhE;AAGA,SAAS,UAAU,SAA8C;AAC/D,QAAM,OAAO;AAAA,IACX,GAAG,OAAO,OAAO,QAAQ,OAAO;AAAA,IAChC,GAAG,QAAQ,QAAQ,SAAS,QAAQ,CAAC,OAAQ,GAAG,QAAQ,CAAC,GAAG,KAAK,IAAI,CAAC,CAAE;AAAA,EAC1E;AACA,QAAM,UAAU,oBAAI,IAAY;AAChC,aAAW,OAAO,MAAM;AACtB,QAAI;AACF,cAAQ,IAAI,IAAI,IAAI,GAAG,EAAE,MAAM;AAAA,IACjC,QAAQ;AAAA,IAER;AAAA,EACF;AACA,SAAO,CAAC,GAAG,OAAO;AACpB;AAEA,SAAS,OAAO,MAAY,OAAO,oBAAI,IAAY,GAAgB;AACjE,OAAK,IAAI,KAAK,GAAG;AACjB,aAAW,SAAS,KAAK,YAAY,CAAC,EAAG,QAAO,OAAO,IAAI;AAC3D,SAAO;AACT;AAOA,SAAS,YAAY,SAAkB,MAAY;AACjD,SAAO,EAAE,MAAM,UAAU,QAAQ,UAAU,SAAS,QAAQ,QAAQ;AACtE;AAUO,SAAS,mBACd,QACA,SACuC;AACvC,QAAM,EAAE,QAAQ,IAAI;AACpB,QAAM,OAAO,QAAQ,QAAQ;AAC7B,QAAM,cAAc,QAAQ,eAAe;AAC3C,MAAI,CAAC,YAAY,WAAW,OAAO,GAAG;AACpC,UAAM,IAAI;AAAA,MACR,gEAAgE,WAAW;AAAA,IAC7E;AAAA,EACF;AACA,QAAM,UAAU,UAAU,OAAO;AACjC,QAAM,MAAM,EAAE,gBAAgB,SAAS,iBAAiB,SAAS,cAAc,CAAC,EAAE;AAClF,QAAM,OAAO,gBAAgB,OAAO;AAEpC;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,MACE,UAAU;AAAA,MACV,aAAa;AAAA,MACb,OAAO,EAAE,IAAI,EAAE,eAAe,OAAO,IAAI,EAAE;AAAA,IAC7C;AAAA,IACA,aAAa;AAAA,MACX,UAAU;AAAA,QACR,EAAE,KAAK,aAAa,UAAU,oBAAoB,MAAM,MAAM,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE;AAAA,MACvF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,cAAc;AAAA,IAClB;AAAA,IACA;AAAA,IACA,QAAQ,gBAAgB;AAAA,IACxB;AAAA,IACA,QAAQ,OAAO;AAAA,EACjB,EACG,OAAO,CAAC,MAAM,UAAU,QAAQ,QAAQ,CAAC,EACzC,KAAK,IAAI;AAEZ;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP;AAAA,MACA,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,+CAA+C,EAAE;AAAA,MAC3F,OAAO,EAAE,IAAI,EAAE,aAAa,YAAY,CAAC,SAAS,KAAK,EAAE,EAAE;AAAA,IAC7D;AAAA,KACC,OAAO,SAA6B;AACnC,YAAM,SAAS,QAAQ,SAAS,KAAK,IAAI;AACzC,UAAI,OAAO,KAAK;AAChB,UAAI,OAAO;AACX,UAAI,OAAO,QAAQ;AACjB,cAAM,EAAE,MAAM,QAAQ,IAAI,QAAQ,OAAO,KAAK,IAAI;AAClD,YAAI,CAAC,QAAQ,QAAQ,SAAS,IAAI,EAAE,QAAQ;AAC1C,iBAAO;AAAA,YACL,SAAS;AAAA,YACT,SAAS;AAAA,cACP;AAAA,gBACE,MAAM;AAAA,gBACN,MAAM;AAAA,EAA8B,OAAO,KAAK,IAAI,CAAC;AAAA,qBAAwB,IAAI;AAAA,cACnF;AAAA,YACF;AAAA,UACF;AAAA,QACF;AACA,eAAO;AACP,eAAO,6CAA6C,QAAQ,KAAK,IAAI,CAAC;AAAA,MACxE;AACA,YAAM,WAAW,IAAI;AAAA,QACnB,QAAQ,SAAS,OAAO,CAAC,OAAO,GAAG,SAAS,MAAM,EAAE,IAAI,CAAC,OAAO,GAAG,GAAG;AAAA,MACxE;AACA,YAAM,OAAO,CAAC,GAAG,OAAO,IAAI,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,SAAS,IAAI,GAAG,CAAC;AACjE,aAAO;AAAA,QACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,WAAW,KAAK,KAAK,IAAI,KAAK,UAAU,IAAI,IAAI,GAAG,CAAC;AAAA,QACpF,mBAAmB,YAAY,SAAS,IAAI;AAAA,MAC9C;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,MAAM,MAAM,YAAY;AACnC;","names":[]}
|
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
|
|
27
|
-
|
|
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)
|
|
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
|
|
39
|
-
|
|
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)
|
|
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
|
|
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
|
|
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
|
|
80
|
-
|
|
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
|
|
129
|
+
"Next:",
|
|
99
130
|
` cd ${args.dir}`,
|
|
100
131
|
" pnpm install",
|
|
101
|
-
" pnpm dev
|
|
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
|
);
|