mountly-mcp 0.0.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +283 -20
- package/dist/artifact/index.d.ts +49 -0
- package/dist/artifact/index.js +21 -0
- package/dist/artifact/index.js.map +1 -0
- package/dist/bridge/iframe-entry.d.ts +1 -0
- package/dist/bridge/iframe-entry.js +270 -220
- package/dist/bridge/index.d.ts +20 -15
- package/dist/bridge/index.js +2 -2
- package/dist/build/emit-html.d.ts +30 -0
- package/dist/build/emit-meta.d.ts +17 -0
- package/dist/build/index.d.ts +33 -48
- package/dist/build/index.js +9 -135
- package/dist/build/index.js.map +1 -1
- package/dist/chunk-33DMFKKD.js +111 -0
- package/dist/chunk-33DMFKKD.js.map +1 -0
- package/dist/chunk-4HC3OWAQ.js +185 -0
- package/dist/chunk-4HC3OWAQ.js.map +1 -0
- package/dist/chunk-5EHNJCMF.js +58 -0
- package/dist/chunk-5EHNJCMF.js.map +1 -0
- package/dist/chunk-6QHKKFO3.js +30 -0
- package/dist/chunk-6QHKKFO3.js.map +1 -0
- package/dist/chunk-AEVQKFXX.js +119 -0
- package/dist/chunk-AEVQKFXX.js.map +1 -0
- package/dist/chunk-DKK26TRS.js +199 -0
- package/dist/chunk-DKK26TRS.js.map +1 -0
- package/dist/chunk-JVQZGKBL.js +171 -0
- package/dist/chunk-JVQZGKBL.js.map +1 -0
- package/dist/chunk-KWVGW5Q7.js +582 -0
- package/dist/chunk-KWVGW5Q7.js.map +1 -0
- package/dist/{chunk-A64ZILAS.js → chunk-M4WFJR3C.js} +3 -2
- package/dist/{chunk-A64ZILAS.js.map → chunk-M4WFJR3C.js.map} +1 -1
- package/dist/chunk-MTJDRWGD.js +196 -0
- package/dist/chunk-MTJDRWGD.js.map +1 -0
- package/dist/chunk-SADNMGP6.js +16 -0
- package/dist/chunk-SADNMGP6.js.map +1 -0
- package/dist/chunk-UECZA34Q.js +52 -0
- package/dist/chunk-UECZA34Q.js.map +1 -0
- package/dist/chunk-YJA4H6IB.js +13 -0
- package/dist/chunk-YJA4H6IB.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +297 -0
- package/dist/cli.js.map +1 -0
- package/dist/connect-server-7L4HGKKL.js +7 -0
- package/dist/connect-server-7L4HGKKL.js.map +1 -0
- package/dist/create.d.ts +10 -0
- package/dist/create.js +107 -0
- package/dist/create.js.map +1 -0
- package/dist/dev/connect-server.d.ts +13 -0
- package/dist/dev/host-entry.d.ts +13 -0
- package/dist/dev/host-entry.js +18330 -0
- package/dist/dev/index.d.ts +45 -0
- package/dist/dev/index.js +14 -0
- package/dist/dev/index.js.map +1 -0
- package/dist/dev/sandbox-entry.d.ts +7 -0
- package/dist/dev/sandbox-entry.js +80 -0
- package/dist/html.d.ts +4 -0
- package/dist/index.d.ts +7 -26
- package/dist/index.js +16 -3
- package/dist/json-render/app.d.ts +22 -0
- package/dist/json-render/app.js +10 -0
- package/dist/json-render/app.js.map +1 -0
- package/dist/json-render/generative-view.d.ts +57 -0
- package/dist/json-render/index.d.ts +10 -0
- package/dist/json-render/index.js +120 -0
- package/dist/json-render/index.js.map +1 -0
- package/dist/json-render/mcp.d.ts +49 -0
- package/dist/json-render/mcp.js +11 -0
- package/dist/json-render/mcp.js.map +1 -0
- package/dist/json-render/server.d.ts +72 -0
- package/dist/json-render/server.js +98 -0
- package/dist/json-render/server.js.map +1 -0
- package/dist/json-render/spec-stream.d.ts +26 -0
- package/dist/json-render/use-spec-stream.d.ts +26 -0
- package/dist/publish.d.ts +10 -0
- package/dist/react/context.d.ts +14 -0
- package/dist/react/create-view.d.ts +8 -0
- package/dist/react/hooks.d.ts +46 -0
- package/dist/react/index.d.ts +3 -0
- package/dist/react/index.js +36 -0
- package/dist/react/index.js.map +1 -0
- package/dist/sandbox/index.d.ts +16 -0
- package/dist/sandbox/index.js +8 -0
- package/dist/sandbox/index.js.map +1 -0
- package/dist/schema.d.ts +20 -0
- package/dist/server/index.d.ts +73 -0
- package/dist/server/index.js +235 -0
- package/dist/server/index.js.map +1 -0
- package/dist/svelte/index.d.ts +29 -0
- package/dist/svelte/index.js +13 -0
- package/dist/svelte/index.js.map +1 -0
- package/dist/testing/index.d.ts +27 -0
- package/dist/testing/index.js +312 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/render-artifact.d.ts +35 -0
- package/dist/types.d.ts +65 -0
- package/dist/vite/index.d.ts +23 -0
- package/dist/vite/index.js +211 -0
- package/dist/vite/index.js.map +1 -0
- package/dist/vue/index.d.ts +51 -0
- package/dist/vue/index.js +93 -0
- package/dist/vue/index.js.map +1 -0
- package/package.json +162 -20
- package/templates/react/.gitignore.tmpl +4 -0
- package/templates/react/README.md.tmpl +15 -0
- package/templates/react/mcp.fixtures.json.tmpl +4 -0
- package/templates/react/package.json.tmpl +27 -0
- package/templates/react/server.mjs.tmpl +70 -0
- package/templates/react/src/view.tsx.tmpl +160 -0
- package/templates/react/tsconfig.json.tmpl +15 -0
- package/templates/react/vite.config.ts.tmpl +22 -0
- package/templates/svelte/.gitignore.tmpl +4 -0
- package/templates/svelte/README.md.tmpl +14 -0
- package/templates/svelte/mcp.fixtures.json.tmpl +4 -0
- package/templates/svelte/package.json.tmpl +26 -0
- package/templates/svelte/server.mjs.tmpl +70 -0
- package/templates/svelte/src/Dashboard.svelte.tmpl +158 -0
- package/templates/svelte/src/view.ts.tmpl +14 -0
- package/templates/svelte/tsconfig.json.tmpl +14 -0
- package/templates/svelte/vite.config.ts.tmpl +22 -0
- package/templates/vue/.gitignore.tmpl +4 -0
- package/templates/vue/README.md.tmpl +14 -0
- package/templates/vue/mcp.fixtures.json.tmpl +4 -0
- package/templates/vue/package.json.tmpl +26 -0
- package/templates/vue/server.mjs.tmpl +70 -0
- package/templates/vue/src/Dashboard.vue.tmpl +159 -0
- package/templates/vue/src/view.ts.tmpl +14 -0
- package/templates/vue/tsconfig.json.tmpl +15 -0
- package/templates/vue/vite.config.ts.tmpl +22 -0
- package/dist/chunk-BUGGLMNA.js +0 -132
- package/dist/chunk-BUGGLMNA.js.map +0 -1
- package/dist/types-B9JAWm4W.d.ts +0 -62
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LATEST_PROTOCOL_VERSION,
|
|
3
|
+
RESOURCE_MIME_TYPE
|
|
4
|
+
} from "./chunk-M4WFJR3C.js";
|
|
5
|
+
import {
|
|
6
|
+
escapeInlineScript,
|
|
7
|
+
serializeInlineScriptValue
|
|
8
|
+
} from "./chunk-YJA4H6IB.js";
|
|
9
|
+
|
|
10
|
+
// src/build/index.ts
|
|
11
|
+
import { readFile, writeFile } from "fs/promises";
|
|
12
|
+
import { fileURLToPath } from "url";
|
|
13
|
+
import { resolve } from "path";
|
|
14
|
+
|
|
15
|
+
// src/build/emit-html.ts
|
|
16
|
+
function escapeAttr(value) {
|
|
17
|
+
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
18
|
+
}
|
|
19
|
+
function escapeStyle(value) {
|
|
20
|
+
return value.replace(/<\/style/gi, "<\\/style");
|
|
21
|
+
}
|
|
22
|
+
function configScript(config) {
|
|
23
|
+
if (!config) return [];
|
|
24
|
+
const assignments = [];
|
|
25
|
+
if (config.awaitToolResult !== void 0) {
|
|
26
|
+
assignments.push(
|
|
27
|
+
`globalThis.__mountlyMcpAwaitToolResult__=${serializeInlineScriptValue(config.awaitToolResult)};`
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
if (config.displayModes !== void 0) {
|
|
31
|
+
assignments.push(
|
|
32
|
+
`globalThis.__mountlyMcpAvailableDisplayModes__=${serializeInlineScriptValue(config.displayModes)};`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
if (config.streamToolInput !== void 0) {
|
|
36
|
+
assignments.push(
|
|
37
|
+
`globalThis.__mountlyMcpStreamToolInput__=${serializeInlineScriptValue(config.streamToolInput)};`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
if (assignments.length === 0) return [];
|
|
41
|
+
return [`<script>${assignments.join("")}</script>`];
|
|
42
|
+
}
|
|
43
|
+
function emitHtml(input) {
|
|
44
|
+
const head = [
|
|
45
|
+
`<meta charset="utf-8">`,
|
|
46
|
+
`<meta name="mountly-mcp-protocol" content="${LATEST_PROTOCOL_VERSION}">`,
|
|
47
|
+
`<meta name="mountly-mcp-uri" content="${escapeAttr(input.uri)}">`
|
|
48
|
+
];
|
|
49
|
+
if (input.mode === "self-contained") {
|
|
50
|
+
head.push(`<style>${escapeStyle(input.css)}</style>`);
|
|
51
|
+
const body2 = [
|
|
52
|
+
`<div id="mountly-mcp-root"></div>`,
|
|
53
|
+
...configScript(input.config),
|
|
54
|
+
`<script type="module">${escapeInlineScript(input.js)}
|
|
55
|
+
${escapeInlineScript(input.bridgeRuntimeJs)}</script>`
|
|
56
|
+
];
|
|
57
|
+
return template(head, body2);
|
|
58
|
+
}
|
|
59
|
+
head.push(`<link rel="stylesheet" href="${escapeAttr(input.cssUrl)}">`);
|
|
60
|
+
const body = [
|
|
61
|
+
`<div id="mountly-mcp-root"></div>`,
|
|
62
|
+
...configScript(input.config),
|
|
63
|
+
`<script type="module" src="${escapeAttr(input.jsUrl)}"></script>`,
|
|
64
|
+
`<script type="module">${escapeInlineScript(input.bridgeRuntimeJs)}</script>`
|
|
65
|
+
];
|
|
66
|
+
return template(head, body);
|
|
67
|
+
}
|
|
68
|
+
function template(head, body) {
|
|
69
|
+
return `<!doctype html>
|
|
70
|
+
<html data-mountly-mcp>
|
|
71
|
+
<head>
|
|
72
|
+
${head.join("\n")}
|
|
73
|
+
</head>
|
|
74
|
+
<body>
|
|
75
|
+
${body.join("\n")}
|
|
76
|
+
</body>
|
|
77
|
+
</html>`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// src/build/emit-meta.ts
|
|
81
|
+
function assertUiUri(uri) {
|
|
82
|
+
if (!uri.startsWith("ui://")) {
|
|
83
|
+
throw new Error(`mountly-mcp: UI resource uri must use the 'ui://' scheme (received '${uri}')`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function emitMeta(input) {
|
|
87
|
+
assertUiUri(input.uri);
|
|
88
|
+
const ui = {};
|
|
89
|
+
if (input.csp) ui.csp = input.csp;
|
|
90
|
+
if (input.permissions) ui.permissions = input.permissions;
|
|
91
|
+
if (input.domain !== void 0) ui.domain = input.domain;
|
|
92
|
+
if (input.prefersBorder !== void 0) ui.prefersBorder = input.prefersBorder;
|
|
93
|
+
const decl = {
|
|
94
|
+
protocolVersion: LATEST_PROTOCOL_VERSION,
|
|
95
|
+
uri: input.uri,
|
|
96
|
+
name: input.name,
|
|
97
|
+
mimeType: RESOURCE_MIME_TYPE,
|
|
98
|
+
awaitToolResult: input.awaitToolResult ?? true,
|
|
99
|
+
displayModes: input.displayModes ?? ["inline"],
|
|
100
|
+
_meta: { ui }
|
|
101
|
+
};
|
|
102
|
+
if (input.description !== void 0) decl.description = input.description;
|
|
103
|
+
return decl;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// src/build/index.ts
|
|
107
|
+
function getBridgeRuntimePath() {
|
|
108
|
+
return fileURLToPath(import.meta.resolve("mountly-mcp/bridge/iframe-entry.js"));
|
|
109
|
+
}
|
|
110
|
+
function defaultBridgeRuntimePath() {
|
|
111
|
+
return getBridgeRuntimePath();
|
|
112
|
+
}
|
|
113
|
+
async function buildMcpResourceFromSource(options) {
|
|
114
|
+
const { js, css, ...rest } = options;
|
|
115
|
+
return writeResource({ ...rest, mode: "self-contained", js, css: css ?? "" });
|
|
116
|
+
}
|
|
117
|
+
async function buildMcpResource(options) {
|
|
118
|
+
if (!("cdn" in options)) {
|
|
119
|
+
const js = await readFile(options.entry, "utf8");
|
|
120
|
+
const css = options.cssEntry ? await readFile(options.cssEntry, "utf8") : "";
|
|
121
|
+
return writeResource({ ...options, mode: "self-contained", js, css });
|
|
122
|
+
}
|
|
123
|
+
return writeResource({ ...options, mode: "cdn" });
|
|
124
|
+
}
|
|
125
|
+
async function writeResource(options) {
|
|
126
|
+
const bridgeRuntimeJs = await readFile(
|
|
127
|
+
options.bridgeRuntimePath ?? defaultBridgeRuntimePath(),
|
|
128
|
+
"utf8"
|
|
129
|
+
);
|
|
130
|
+
const declaration = emitMeta({
|
|
131
|
+
uri: options.uri,
|
|
132
|
+
name: options.name,
|
|
133
|
+
description: options.description,
|
|
134
|
+
awaitToolResult: options.awaitToolResult,
|
|
135
|
+
displayModes: options.displayModes,
|
|
136
|
+
csp: options.mode === "cdn" ? mergeCdnIntoCsp(options.csp, options.cdn) : options.csp,
|
|
137
|
+
permissions: options.permissions,
|
|
138
|
+
domain: options.domain,
|
|
139
|
+
prefersBorder: options.prefersBorder
|
|
140
|
+
});
|
|
141
|
+
const config = {
|
|
142
|
+
awaitToolResult: declaration.awaitToolResult,
|
|
143
|
+
displayModes: declaration.displayModes,
|
|
144
|
+
streamToolInput: options.streamToolInput ?? false
|
|
145
|
+
};
|
|
146
|
+
const html = options.mode === "cdn" ? emitHtml({
|
|
147
|
+
mode: "cdn",
|
|
148
|
+
uri: options.uri,
|
|
149
|
+
jsUrl: options.cdn.jsUrl,
|
|
150
|
+
cssUrl: options.cdn.cssUrl,
|
|
151
|
+
bridgeRuntimeJs,
|
|
152
|
+
config
|
|
153
|
+
}) : emitHtml({
|
|
154
|
+
mode: "self-contained",
|
|
155
|
+
uri: options.uri,
|
|
156
|
+
js: options.js,
|
|
157
|
+
css: options.css,
|
|
158
|
+
bridgeRuntimeJs,
|
|
159
|
+
config
|
|
160
|
+
});
|
|
161
|
+
const metaPath = `${options.output}.meta.json`;
|
|
162
|
+
await writeFile(options.output, html, "utf8");
|
|
163
|
+
await writeFile(metaPath, JSON.stringify(declaration, null, 2), "utf8");
|
|
164
|
+
const htmlPath = resolve(options.output);
|
|
165
|
+
return {
|
|
166
|
+
htmlPath,
|
|
167
|
+
metaPath: resolve(metaPath),
|
|
168
|
+
declaration,
|
|
169
|
+
artifact: {
|
|
170
|
+
name: declaration.name,
|
|
171
|
+
uri: declaration.uri,
|
|
172
|
+
htmlPath,
|
|
173
|
+
metaPath: resolve(metaPath),
|
|
174
|
+
declaration
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function mergeCdnIntoCsp(csp, cdn) {
|
|
179
|
+
const jsOrigin = new URL(cdn.jsUrl).origin;
|
|
180
|
+
const cssOrigin = new URL(cdn.cssUrl).origin;
|
|
181
|
+
const merged = { ...csp ?? {} };
|
|
182
|
+
const existing = new Set(merged.resourceDomains ?? []);
|
|
183
|
+
existing.add(jsOrigin);
|
|
184
|
+
existing.add(cssOrigin);
|
|
185
|
+
merged.resourceDomains = Array.from(existing);
|
|
186
|
+
return merged;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export {
|
|
190
|
+
emitHtml,
|
|
191
|
+
emitMeta,
|
|
192
|
+
getBridgeRuntimePath,
|
|
193
|
+
buildMcpResourceFromSource,
|
|
194
|
+
buildMcpResource
|
|
195
|
+
};
|
|
196
|
+
//# sourceMappingURL=chunk-MTJDRWGD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/build/index.ts","../src/build/emit-html.ts","../src/build/emit-meta.ts"],"sourcesContent":["import { readFile, writeFile } from \"node:fs/promises\";\nimport { fileURLToPath } from \"node:url\";\nimport { resolve } from \"node:path\";\nimport type {\n McpUiDisplayMode,\n McpUiResourceCsp,\n McpUiResourcePermissions,\n} from \"@modelcontextprotocol/ext-apps\";\nimport type { McpResourceDeclaration } from \"../types.js\";\nimport type { McpAppArtifact } from \"../artifact/index.js\";\nimport { emitHtml } from \"./emit-html.js\";\nimport { emitMeta } from \"./emit-meta.js\";\n\n/**\n * Absolute path to the iframe bridge runtime shipped with this package.\n *\n * `buildMcpResource` inlines this file's contents into the emitted HTML so\n * the View can speak the MCP Apps postMessage protocol. Use this when you\n * need to point another build tool at the runtime, or when you want a\n * stable, version-pinned alternative to passing `bridgeRuntimePath` yourself.\n *\n * Resolves relative to the installed package, so it works identically in\n * monorepos and in projects that installed mountly-mcp from npm.\n */\nexport function getBridgeRuntimePath(): string {\n // Ask the module resolver rather than walking up from __dirname: the subpath\n // is a declared export, so this holds wherever a bundler puts this file.\n return fileURLToPath(import.meta.resolve(\"mountly-mcp/bridge/iframe-entry.js\"));\n}\n\nfunction defaultBridgeRuntimePath(): string {\n return getBridgeRuntimePath();\n}\n\nexport interface BuildSelfContainedOptions {\n entry: string;\n uri: string;\n name: string;\n output: string;\n description?: string;\n awaitToolResult?: boolean;\n displayModes?: ReadonlyArray<McpUiDisplayMode>;\n /**\n * Render progressive states from `ui/notifications/tool-input-partial`.\n * Off by default — see `RunBridgeOptions.streamToolInput`.\n */\n streamToolInput?: boolean;\n csp?: McpUiResourceCsp;\n /** Sandbox permissions requested by the view (camera/microphone/geolocation/clipboardWrite). */\n permissions?: McpUiResourcePermissions;\n /** Dedicated sandbox origin (host-dependent format). */\n domain?: string;\n /** Whether the view requests the host to show a visible border + background. */\n prefersBorder?: boolean;\n cssEntry?: string;\n bridgeRuntimePath?: string;\n}\n\nexport interface BuildCdnOptions extends BuildSelfContainedOptions {\n cdn: { jsUrl: string; cssUrl: string };\n}\n\nexport type BuildOptions = BuildSelfContainedOptions | BuildCdnOptions;\n\nexport interface BuildResult {\n htmlPath: string;\n metaPath: string;\n declaration: McpResourceDeclaration;\n artifact: McpAppArtifact;\n}\n\nexport interface BuildFromSourceOptions extends Omit<\n BuildSelfContainedOptions,\n \"entry\" | \"cssEntry\"\n> {\n /** The bundled View JS. */\n js: string;\n /** The View's CSS, if it has any. */\n css?: string;\n}\n\n/**\n * Same as {@link buildMcpResource}, but taking the bundle in memory instead of\n * from disk — for bundler plugins that already hold the emitted sources and\n * shouldn't have to guess when they land on the filesystem.\n */\nexport async function buildMcpResourceFromSource(\n options: BuildFromSourceOptions,\n): Promise<BuildResult> {\n const { js, css, ...rest } = options;\n return writeResource({ ...rest, mode: \"self-contained\", js, css: css ?? \"\" });\n}\n\nexport async function buildMcpResource(options: BuildOptions): Promise<BuildResult> {\n if (!(\"cdn\" in options)) {\n const js = await readFile(options.entry, \"utf8\");\n const css = options.cssEntry ? await readFile(options.cssEntry, \"utf8\") : \"\";\n return writeResource({ ...options, mode: \"self-contained\", js, css });\n }\n return writeResource({ ...options, mode: \"cdn\" });\n}\n\ntype ResourceMeta = Omit<BuildSelfContainedOptions, \"entry\" | \"cssEntry\">;\n\ntype WriteResourceOptions =\n | (ResourceMeta & { mode: \"self-contained\"; js: string; css: string })\n | (ResourceMeta & { mode: \"cdn\"; cdn: BuildCdnOptions[\"cdn\"] });\n\nasync function writeResource(options: WriteResourceOptions): Promise<BuildResult> {\n const bridgeRuntimeJs = await readFile(\n options.bridgeRuntimePath ?? defaultBridgeRuntimePath(),\n \"utf8\",\n );\n\n const declaration = emitMeta({\n uri: options.uri,\n name: options.name,\n description: options.description,\n awaitToolResult: options.awaitToolResult,\n displayModes: options.displayModes,\n csp: options.mode === \"cdn\" ? mergeCdnIntoCsp(options.csp, options.cdn) : options.csp,\n permissions: options.permissions,\n domain: options.domain,\n prefersBorder: options.prefersBorder,\n });\n\n // Same values the sidecar declares, so the view's `ui/initialize` matches it.\n const config = {\n awaitToolResult: declaration.awaitToolResult,\n displayModes: declaration.displayModes,\n streamToolInput: options.streamToolInput ?? false,\n };\n\n const html =\n options.mode === \"cdn\"\n ? emitHtml({\n mode: \"cdn\",\n uri: options.uri,\n jsUrl: options.cdn.jsUrl,\n cssUrl: options.cdn.cssUrl,\n bridgeRuntimeJs,\n config,\n })\n : emitHtml({\n mode: \"self-contained\",\n uri: options.uri,\n js: options.js,\n css: options.css,\n bridgeRuntimeJs,\n config,\n });\n\n const metaPath = `${options.output}.meta.json`;\n await writeFile(options.output, html, \"utf8\");\n await writeFile(metaPath, JSON.stringify(declaration, null, 2), \"utf8\");\n\n const htmlPath = resolve(options.output);\n return {\n htmlPath,\n metaPath: resolve(metaPath),\n declaration,\n artifact: {\n name: declaration.name,\n uri: declaration.uri,\n htmlPath,\n metaPath: resolve(metaPath),\n declaration,\n },\n };\n}\n\nfunction mergeCdnIntoCsp(\n csp: McpUiResourceCsp | undefined,\n cdn: { jsUrl: string; cssUrl: string },\n): McpUiResourceCsp {\n const jsOrigin = new URL(cdn.jsUrl).origin;\n const cssOrigin = new URL(cdn.cssUrl).origin;\n const merged: McpUiResourceCsp = { ...(csp ?? {}) };\n const existing = new Set(merged.resourceDomains ?? []);\n existing.add(jsOrigin);\n existing.add(cssOrigin);\n merged.resourceDomains = Array.from(existing);\n return merged;\n}\n\nexport { emitHtml } from \"./emit-html.js\";\nexport { emitMeta } from \"./emit-meta.js\";\n","import { MCP_APPS_PROTOCOL_VERSION } from \"../schema.js\";\nimport type { McpUiDisplayMode } from \"@modelcontextprotocol/ext-apps\";\nimport { escapeInlineScript, serializeInlineScriptValue } from \"../html.js\";\n\n/**\n * Runtime config the emitted HTML hands to the inlined bridge\n * (`bridge/iframe-entry.ts` reads these off `globalThis`). Without it the view\n * falls back to `awaitToolResult: true` / `availableDisplayModes: [\"inline\"]`,\n * regardless of what the build declared in the sidecar.\n */\nexport interface EmitHtmlConfig {\n awaitToolResult?: boolean;\n displayModes?: ReadonlyArray<McpUiDisplayMode>;\n streamToolInput?: boolean;\n}\n\nexport interface EmitHtmlSelfContainedInput {\n mode: \"self-contained\";\n uri: string;\n js: string;\n css: string;\n bridgeRuntimeJs: string;\n config?: EmitHtmlConfig;\n}\n\nexport interface EmitHtmlCdnInput {\n mode: \"cdn\";\n uri: string;\n jsUrl: string;\n cssUrl: string;\n bridgeRuntimeJs: string;\n config?: EmitHtmlConfig;\n}\n\nexport type EmitHtmlInput = EmitHtmlSelfContainedInput | EmitHtmlCdnInput;\n\nfunction escapeAttr(value: string): string {\n return value\n .replace(/&/g, \"&\")\n .replace(/\"/g, \""\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\");\n}\n\nfunction escapeStyle(value: string): string {\n return value.replace(/<\\/style/gi, \"<\\\\/style\");\n}\n\n/**\n * Classic (non-module) script so it runs at parse time — before the deferred\n * module scripts that boot the bridge.\n */\nfunction configScript(config: EmitHtmlConfig | undefined): string[] {\n if (!config) return [];\n const assignments: string[] = [];\n if (config.awaitToolResult !== undefined) {\n assignments.push(\n `globalThis.__mountlyMcpAwaitToolResult__=${serializeInlineScriptValue(config.awaitToolResult)};`,\n );\n }\n if (config.displayModes !== undefined) {\n assignments.push(\n `globalThis.__mountlyMcpAvailableDisplayModes__=${serializeInlineScriptValue(config.displayModes)};`,\n );\n }\n if (config.streamToolInput !== undefined) {\n assignments.push(\n `globalThis.__mountlyMcpStreamToolInput__=${serializeInlineScriptValue(config.streamToolInput)};`,\n );\n }\n if (assignments.length === 0) return [];\n return [`<script>${assignments.join(\"\")}</script>`];\n}\n\nexport function emitHtml(input: EmitHtmlInput): string {\n const head = [\n `<meta charset=\"utf-8\">`,\n `<meta name=\"mountly-mcp-protocol\" content=\"${MCP_APPS_PROTOCOL_VERSION}\">`,\n `<meta name=\"mountly-mcp-uri\" content=\"${escapeAttr(input.uri)}\">`,\n ];\n\n if (input.mode === \"self-contained\") {\n head.push(`<style>${escapeStyle(input.css)}</style>`);\n const body = [\n `<div id=\"mountly-mcp-root\"></div>`,\n ...configScript(input.config),\n `<script type=\"module\">${escapeInlineScript(input.js)}\\n${escapeInlineScript(input.bridgeRuntimeJs)}</script>`,\n ];\n return template(head, body);\n }\n\n head.push(`<link rel=\"stylesheet\" href=\"${escapeAttr(input.cssUrl)}\">`);\n // The View bundle MUST load before the bridge: module scripts execute in\n // document order, and the bridge reads the published View synchronously.\n const body = [\n `<div id=\"mountly-mcp-root\"></div>`,\n ...configScript(input.config),\n `<script type=\"module\" src=\"${escapeAttr(input.jsUrl)}\"></script>`,\n `<script type=\"module\">${escapeInlineScript(input.bridgeRuntimeJs)}</script>`,\n ];\n return template(head, body);\n}\n\nfunction template(head: string[], body: string[]): string {\n return `<!doctype html>\n<html data-mountly-mcp>\n<head>\n${head.join(\"\\n\")}\n</head>\n<body>\n${body.join(\"\\n\")}\n</body>\n</html>`;\n}\n","import { MCP_APPS_MIME, MCP_APPS_PROTOCOL_VERSION } from \"../schema.js\";\nimport type {\n McpUiDisplayMode,\n McpUiResourceCsp,\n McpUiResourcePermissions,\n} from \"@modelcontextprotocol/ext-apps\";\nimport type { McpResourceDeclaration } from \"../types.js\";\n\nexport interface EmitMetaInput {\n uri: string;\n name: string;\n description?: string;\n awaitToolResult?: boolean;\n displayModes?: ReadonlyArray<McpUiDisplayMode>;\n csp?: McpUiResourceCsp;\n /** Sandbox permissions requested by the view (camera/microphone/geolocation/clipboardWrite). */\n permissions?: McpUiResourcePermissions;\n /** Dedicated sandbox origin requested by the view (host-dependent format). */\n domain?: string;\n /** Whether the view requests the host to show a visible border + background. */\n prefersBorder?: boolean;\n}\n\nfunction assertUiUri(uri: string): void {\n if (!uri.startsWith(\"ui://\")) {\n throw new Error(`mountly-mcp: UI resource uri must use the 'ui://' scheme (received '${uri}')`);\n }\n}\n\nexport function emitMeta(input: EmitMetaInput): McpResourceDeclaration {\n assertUiUri(input.uri);\n\n const ui: McpResourceDeclaration[\"_meta\"][\"ui\"] = {};\n if (input.csp) ui.csp = input.csp;\n if (input.permissions) ui.permissions = input.permissions;\n if (input.domain !== undefined) ui.domain = input.domain;\n if (input.prefersBorder !== undefined) ui.prefersBorder = input.prefersBorder;\n\n const decl: McpResourceDeclaration = {\n protocolVersion: MCP_APPS_PROTOCOL_VERSION,\n uri: input.uri,\n name: input.name,\n mimeType: MCP_APPS_MIME,\n awaitToolResult: input.awaitToolResult ?? true,\n displayModes: input.displayModes ?? [\"inline\"],\n _meta: { ui },\n };\n if (input.description !== undefined) decl.description = input.description;\n return decl;\n}\n"],"mappings":"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","names":["body"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// src/publish.ts
|
|
2
|
+
var MCP_VIEW_GLOBAL_KEY = "__mountlyMcpView__";
|
|
3
|
+
function publishMcpView(view) {
|
|
4
|
+
globalThis[MCP_VIEW_GLOBAL_KEY] = view;
|
|
5
|
+
return view;
|
|
6
|
+
}
|
|
7
|
+
function getPublishedMcpView() {
|
|
8
|
+
return globalThis[MCP_VIEW_GLOBAL_KEY];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export {
|
|
12
|
+
MCP_VIEW_GLOBAL_KEY,
|
|
13
|
+
publishMcpView,
|
|
14
|
+
getPublishedMcpView
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=chunk-SADNMGP6.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/publish.ts"],"sourcesContent":["import type { McpView } from \"./types.js\";\n\n/** Internal bridge key — prefer {@link publishMcpView} / `createMcpView`. */\nexport const MCP_VIEW_GLOBAL_KEY = \"__mountlyMcpView__\" as const;\n\n/**\n * Publish a View module for the iframe bridge. Call once from your View entry\n * (or rely on `createMcpView`, which does this for you).\n */\nexport function publishMcpView(view: McpView): McpView {\n (globalThis as Record<string, unknown>)[MCP_VIEW_GLOBAL_KEY] = view;\n return view;\n}\n\n/** Read the published View module, if any. */\nexport function getPublishedMcpView(): McpView | undefined {\n return (globalThis as Record<string, unknown>)[MCP_VIEW_GLOBAL_KEY] as\n | McpView\n | undefined;\n}\n"],"mappings":";AAGO,IAAM,sBAAsB;AAM5B,SAAS,eAAe,MAAwB;AACrD,EAAC,WAAuC,mBAAmB,IAAI;AAC/D,SAAO;AACT;AAGO,SAAS,sBAA2C;AACzD,SAAQ,WAAuC,mBAAmB;AAGpE;","names":[]}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// src/dev/connect-server.ts
|
|
2
|
+
async function connectMcpServer(path) {
|
|
3
|
+
const { pathToFileURL } = await import("url");
|
|
4
|
+
const { resolve } = await import("path");
|
|
5
|
+
const mod = await import(pathToFileURL(resolve(path)).href);
|
|
6
|
+
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.`
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
const value = typeof mod.default === "function" ? await mod.default() : mod.default;
|
|
12
|
+
let client;
|
|
13
|
+
if (value !== null && typeof value === "object" && "connectInProcess" in value && typeof value.connectInProcess === "function") {
|
|
14
|
+
const running = value;
|
|
15
|
+
({ client } = await running.connectInProcess());
|
|
16
|
+
} else if (value !== null && typeof value === "object" && "connect" in value && typeof value.connect === "function") {
|
|
17
|
+
const [{ Client }, { InMemoryTransport }, { EXTENSION_ID, RESOURCE_MIME_TYPE }] = await Promise.all([
|
|
18
|
+
import("@modelcontextprotocol/sdk/client/index.js"),
|
|
19
|
+
import("@modelcontextprotocol/sdk/inMemory.js"),
|
|
20
|
+
import("@modelcontextprotocol/ext-apps/server")
|
|
21
|
+
]);
|
|
22
|
+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
23
|
+
await value.connect(serverTransport);
|
|
24
|
+
client = new Client({ name: "mountly-mcp-dev", version: "0.0.1" }, {
|
|
25
|
+
capabilities: { extensions: { [EXTENSION_ID]: { mimeTypes: [RESOURCE_MIME_TYPE] } } }
|
|
26
|
+
});
|
|
27
|
+
await client.connect(clientTransport);
|
|
28
|
+
} else {
|
|
29
|
+
throw new TypeError(
|
|
30
|
+
`mountly-mcp: ${path} must default-export an MCP server, RunningMcpAppServer, or a function returning one`
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
callTool: (name, args) => client.callTool({ name, arguments: args ?? {} }),
|
|
35
|
+
async toolFor(uri) {
|
|
36
|
+
const { tools } = await client.listTools();
|
|
37
|
+
const uiMeta = (tool) => tool._meta?.ui;
|
|
38
|
+
const bound = tools.filter((tool) => uiMeta(tool)?.resourceUri === uri);
|
|
39
|
+
const visible = bound.find((tool) => {
|
|
40
|
+
const visibility = uiMeta(tool)?.visibility;
|
|
41
|
+
return visibility === void 0 || visibility.includes("model");
|
|
42
|
+
});
|
|
43
|
+
return (visible ?? bound[0])?.name;
|
|
44
|
+
},
|
|
45
|
+
close: () => client.close()
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export {
|
|
50
|
+
connectMcpServer
|
|
51
|
+
};
|
|
52
|
+
//# sourceMappingURL=chunk-UECZA34Q.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 mod = (await import(pathToFileURL(resolve(path)).href)) as { default?: unknown };\n if (mod.default === undefined) {\n throw new Error(\n `mountly-mcp: ${path} has no default export. Export your server (or a function returning it) as default.`,\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 new TypeError(\n `mountly-mcp: ${path} must default-export an MCP server, RunningMcpAppServer, or a function returning one`,\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,MAAO,MAAM,OAAO,cAAc,QAAQ,IAAI,CAAC,EAAE;AACvD,MAAI,IAAI,YAAY,QAAW;AAC7B,UAAM,IAAI;AAAA,MACR,gBAAgB,IAAI;AAAA,IACtB;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,IAAI;AAAA,MACR,gBAAgB,IAAI;AAAA,IACtB;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":[]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// src/html.ts
|
|
2
|
+
function escapeInlineScript(value) {
|
|
3
|
+
return value.replace(/<\/script/gi, "<\\/script");
|
|
4
|
+
}
|
|
5
|
+
function serializeInlineScriptValue(value) {
|
|
6
|
+
return JSON.stringify(value).replace(/</g, "\\u003c");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
escapeInlineScript,
|
|
11
|
+
serializeInlineScriptValue
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=chunk-YJA4H6IB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/html.ts"],"sourcesContent":["/** Keep generated JavaScript from terminating the inline script that owns it. */\nexport function escapeInlineScript(value: string): string {\n return value.replace(/<\\/script/gi, \"<\\\\/script\");\n}\n\n/** Serialize a value so no HTML tag can begin inside an inline script. */\nexport function serializeInlineScriptValue(value: unknown): string {\n return JSON.stringify(value).replace(/</g, \"\\\\u003c\");\n}\n"],"mappings":";AACO,SAAS,mBAAmB,OAAuB;AACxD,SAAO,MAAM,QAAQ,eAAe,YAAY;AAClD;AAGO,SAAS,2BAA2B,OAAwB;AACjE,SAAO,KAAK,UAAU,KAAK,EAAE,QAAQ,MAAM,SAAS;AACtD;","names":[]}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
startDevHost
|
|
4
|
+
} from "./chunk-KWVGW5Q7.js";
|
|
5
|
+
import "./chunk-UECZA34Q.js";
|
|
6
|
+
import "./chunk-YJA4H6IB.js";
|
|
7
|
+
|
|
8
|
+
// src/cli.ts
|
|
9
|
+
import { readFile } from "fs/promises";
|
|
10
|
+
import { resolve } from "path";
|
|
11
|
+
function parseArgs(argv) {
|
|
12
|
+
const args = { open: true };
|
|
13
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
14
|
+
const arg = argv[i];
|
|
15
|
+
const next = () => {
|
|
16
|
+
const value = argv[i + 1];
|
|
17
|
+
if (value === void 0) throw new Error(`mountly-mcp: ${arg} needs a value`);
|
|
18
|
+
i += 1;
|
|
19
|
+
return value;
|
|
20
|
+
};
|
|
21
|
+
if (arg === "--config" || arg === "-c") args.config = next();
|
|
22
|
+
else if (arg === "--fixtures" || arg === "-f") args.fixtures = next();
|
|
23
|
+
else if (arg === "--server" || arg === "-s") args.server = next();
|
|
24
|
+
else if (arg === "--app" || arg === "-a") args.app = next();
|
|
25
|
+
else if (arg === "--port" || arg === "-p") args.port = Number(next());
|
|
26
|
+
else if (arg === "--no-open") args.open = false;
|
|
27
|
+
else if (arg === "--help" || arg === "-h") {
|
|
28
|
+
printUsage();
|
|
29
|
+
process.exit(0);
|
|
30
|
+
} else throw new Error(`mountly-mcp: unknown option '${arg}'`);
|
|
31
|
+
}
|
|
32
|
+
return args;
|
|
33
|
+
}
|
|
34
|
+
function parseVerifyArgs(argv) {
|
|
35
|
+
const args = { html: [], strict: false, render: false, json: false };
|
|
36
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
37
|
+
const arg = argv[i];
|
|
38
|
+
const next = () => {
|
|
39
|
+
const value = argv[i + 1];
|
|
40
|
+
if (value === void 0) throw new Error(`mountly-mcp: ${arg} needs a value`);
|
|
41
|
+
i += 1;
|
|
42
|
+
return value;
|
|
43
|
+
};
|
|
44
|
+
if (arg === "--manifest" || arg === "-m") args.manifest = next();
|
|
45
|
+
else if (arg === "--html") args.html.push(next());
|
|
46
|
+
else if (arg === "--strict") args.strict = true;
|
|
47
|
+
else if (arg === "--render") args.render = true;
|
|
48
|
+
else if (arg === "--json") args.json = true;
|
|
49
|
+
else throw new Error(`mountly-mcp: unknown verify option '${arg}'`);
|
|
50
|
+
}
|
|
51
|
+
if (args.manifest && args.html.length > 0) {
|
|
52
|
+
throw new Error("mountly-mcp: verify accepts either --manifest or --html, not both");
|
|
53
|
+
}
|
|
54
|
+
return args;
|
|
55
|
+
}
|
|
56
|
+
function parseBuildArgs(argv) {
|
|
57
|
+
const args = {};
|
|
58
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
59
|
+
const arg = argv[i];
|
|
60
|
+
if (arg === "--config" || arg === "-c") {
|
|
61
|
+
const value = argv[i + 1];
|
|
62
|
+
if (value === void 0) throw new Error(`mountly-mcp: ${arg} needs a value`);
|
|
63
|
+
args.config = value;
|
|
64
|
+
i += 1;
|
|
65
|
+
} else throw new Error(`mountly-mcp: unknown build option '${arg}'`);
|
|
66
|
+
}
|
|
67
|
+
return args;
|
|
68
|
+
}
|
|
69
|
+
function printUsage() {
|
|
70
|
+
process.stdout.write(`mountly-mcp \u2014 create, build, develop, and verify MCP Apps
|
|
71
|
+
|
|
72
|
+
Usage:
|
|
73
|
+
mountly-mcp create <name> [options]
|
|
74
|
+
mountly-mcp build [options]
|
|
75
|
+
mountly-mcp dev [options]
|
|
76
|
+
mountly-mcp verify [options]
|
|
77
|
+
|
|
78
|
+
Create options:
|
|
79
|
+
-f, --framework <name> react | vue | svelte (default: react)
|
|
80
|
+
--dir <path> output directory (default: ./<name>)
|
|
81
|
+
|
|
82
|
+
Dev / build options:
|
|
83
|
+
-c, --config <path> vite config to read the View from (default: auto)
|
|
84
|
+
-f, --fixtures <path> JSON of named samples (default: mcp.fixtures.json)
|
|
85
|
+
-s, --server <path> module default-exporting your MCP server, to call real tools
|
|
86
|
+
-a, --app <name> View to develop (required when the manifest has several)
|
|
87
|
+
-p, --port <number> host port (default: 5179; the sandbox takes the next one)
|
|
88
|
+
--no-open don't open a browser
|
|
89
|
+
-h, --help show this
|
|
90
|
+
|
|
91
|
+
Verify options:
|
|
92
|
+
-m, --manifest <path> manifest to verify (default: dist/mountly-mcp.manifest.json)
|
|
93
|
+
--html <path> transitional single-View HTML; repeat for several
|
|
94
|
+
--strict fail when warnings are present
|
|
95
|
+
--render require each View to mount with content in Chromium (needs playwright)
|
|
96
|
+
--json output the conformance report as JSON
|
|
97
|
+
|
|
98
|
+
Greenfield: prefer \`mountly-mcp create\` \u2014 do not clone the Mountly monorepo.
|
|
99
|
+
|
|
100
|
+
The View's entry, uri and name come from the mountlyMcpViews() plugin in
|
|
101
|
+
your vite config, so there is nothing extra to configure.
|
|
102
|
+
|
|
103
|
+
Without --server each fixture value is delivered as structuredContent. With
|
|
104
|
+
--server it is the tool's arguments instead, the view receives what the real
|
|
105
|
+
tool returns, and tool calls the view makes itself are routed to your server.
|
|
106
|
+
`);
|
|
107
|
+
}
|
|
108
|
+
async function buildApps(args) {
|
|
109
|
+
const { createBuilder } = await import("vite");
|
|
110
|
+
const builder = await createBuilder({ configFile: args.config }, false);
|
|
111
|
+
await builder.buildApp();
|
|
112
|
+
}
|
|
113
|
+
async function verify(args) {
|
|
114
|
+
const { formatConformanceReport, verifyMcpApps } = await import("./testing/index.js");
|
|
115
|
+
const report = await verifyMcpApps({
|
|
116
|
+
manifestPath: args.manifest,
|
|
117
|
+
htmlPaths: args.html.length > 0 ? args.html : void 0,
|
|
118
|
+
render: args.render
|
|
119
|
+
});
|
|
120
|
+
process.stdout.write(
|
|
121
|
+
args.json ? JSON.stringify(report, null, 2) : formatConformanceReport(report)
|
|
122
|
+
);
|
|
123
|
+
if (!report.ok || args.strict && report.diagnostics.length > 0) process.exitCode = 1;
|
|
124
|
+
}
|
|
125
|
+
async function loadViewOptions(configFile, appName) {
|
|
126
|
+
const { loadConfigFromFile } = await import("vite");
|
|
127
|
+
const loaded = await loadConfigFromFile({ command: "build", mode: "development" }, configFile);
|
|
128
|
+
if (!loaded) {
|
|
129
|
+
throw new Error(
|
|
130
|
+
"mountly-mcp: no vite config found. Pass --config, or add mountlyMcpViews() to vite.config.ts."
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
const plugins = (loaded.config.plugins ?? []).flat(
|
|
134
|
+
Number.POSITIVE_INFINITY
|
|
135
|
+
);
|
|
136
|
+
const plugin = plugins.find((p) => p?.api?.mountlyMcp);
|
|
137
|
+
if (!plugin?.api?.mountlyMcp) {
|
|
138
|
+
throw new Error(
|
|
139
|
+
`mountly-mcp: no mountlyMcpViews() plugin in ${loaded.path}. Add it to the plugins array.`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
const apps = plugin.api.mountlyMcp.apps;
|
|
143
|
+
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(", ")}`
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
const selected = appName === void 0 ? apps[0] : apps.find((app) => app.name === appName);
|
|
149
|
+
if (!selected) {
|
|
150
|
+
throw new Error(
|
|
151
|
+
`mountly-mcp: no View named '${String(appName)}'. Available: ${apps.map((app) => app.name).join(", ")}`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
options: selected,
|
|
156
|
+
outDir: loaded.config.build?.outDir ?? "dist",
|
|
157
|
+
root: loaded.config.root ?? process.cwd()
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
async function waitForFile(path, timeoutMs = 15e3) {
|
|
161
|
+
const deadline = Date.now() + timeoutMs;
|
|
162
|
+
for (; ; ) {
|
|
163
|
+
try {
|
|
164
|
+
await readFile(path);
|
|
165
|
+
return;
|
|
166
|
+
} catch {
|
|
167
|
+
if (Date.now() > deadline) throw new Error(`mountly-mcp: timed out waiting for ${path}`);
|
|
168
|
+
await new Promise((done) => setTimeout(done, 50));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
async function loadFixtures(path) {
|
|
173
|
+
const file = path ?? "mcp.fixtures.json";
|
|
174
|
+
try {
|
|
175
|
+
return JSON.parse(await readFile(resolve(file), "utf8"));
|
|
176
|
+
} catch (error) {
|
|
177
|
+
if (path) throw new Error(`mountly-mcp: could not read fixtures '${file}': ${String(error)}`);
|
|
178
|
+
return {};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
async function dev(args) {
|
|
182
|
+
const { options, outDir, root } = await loadViewOptions(args.config, args.app);
|
|
183
|
+
const fixtures = await loadFixtures(args.fixtures);
|
|
184
|
+
let server;
|
|
185
|
+
let toolName;
|
|
186
|
+
const { build } = await import("vite");
|
|
187
|
+
const htmlPath = options.output ? resolve(root, options.output) : resolve(root, outDir, `${options.name}.html`);
|
|
188
|
+
let host;
|
|
189
|
+
let started = false;
|
|
190
|
+
const previousSelectedApp = process.env.MOUNTLY_MCP_SELECTED_APP;
|
|
191
|
+
process.env.MOUNTLY_MCP_SELECTED_APP = options.name;
|
|
192
|
+
const watcher = await build({
|
|
193
|
+
configFile: args.config ?? void 0,
|
|
194
|
+
build: { watch: {} },
|
|
195
|
+
logLevel: "warn"
|
|
196
|
+
}).finally(() => {
|
|
197
|
+
if (previousSelectedApp === void 0) delete process.env.MOUNTLY_MCP_SELECTED_APP;
|
|
198
|
+
else process.env.MOUNTLY_MCP_SELECTED_APP = previousSelectedApp;
|
|
199
|
+
});
|
|
200
|
+
const rollupWatcher = watcher;
|
|
201
|
+
rollupWatcher.on("event", (event) => {
|
|
202
|
+
if (event.code === "ERROR") {
|
|
203
|
+
process.stderr.write(`mountly-mcp: build failed \u2014 ${event.error?.message ?? "unknown"}
|
|
204
|
+
`);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (event.code !== "END") return;
|
|
208
|
+
if (started) {
|
|
209
|
+
void waitForFile(`${htmlPath}.meta.json`).then(
|
|
210
|
+
() => {
|
|
211
|
+
host?.reload();
|
|
212
|
+
process.stdout.write("mountly-mcp: rebuilt\n");
|
|
213
|
+
},
|
|
214
|
+
(error) => process.stderr.write(`${String(error)}
|
|
215
|
+
`)
|
|
216
|
+
);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
started = true;
|
|
220
|
+
void (async () => {
|
|
221
|
+
await waitForFile(`${htmlPath}.meta.json`);
|
|
222
|
+
if (args.server) {
|
|
223
|
+
const { connectMcpServer } = await import("./connect-server-7L4HGKKL.js");
|
|
224
|
+
server = await connectMcpServer(args.server);
|
|
225
|
+
toolName = await server.toolFor(options.uri);
|
|
226
|
+
if (!toolName) {
|
|
227
|
+
throw new Error(
|
|
228
|
+
`mountly-mcp: server '${args.server}' has no tool linked to View '${options.uri}'`
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
host = await startDevHost({
|
|
233
|
+
htmlPath,
|
|
234
|
+
fixtures,
|
|
235
|
+
toolInput: {},
|
|
236
|
+
toolName,
|
|
237
|
+
callTool: server?.callTool,
|
|
238
|
+
hostPort: args.port
|
|
239
|
+
});
|
|
240
|
+
process.stdout.write(
|
|
241
|
+
`
|
|
242
|
+
mountly-mcp dev
|
|
243
|
+
host ${host.hostUrl}
|
|
244
|
+
sandbox ${host.sandboxUrl}
|
|
245
|
+
view ${htmlPath}
|
|
246
|
+
fixtures ${Object.keys(fixtures).length || "none \u2014 pass --fixtures"}
|
|
247
|
+
server ${server ? `${args.server} \u2192 ${toolName ?? "no tool bound to " + options.uri}` : "none \u2014 fixtures deliver directly"}
|
|
248
|
+
|
|
249
|
+
watching for changes; ctrl+c to stop
|
|
250
|
+
|
|
251
|
+
`
|
|
252
|
+
);
|
|
253
|
+
if (args.open) {
|
|
254
|
+
const { spawn } = await import("child_process");
|
|
255
|
+
const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
256
|
+
spawn(opener, [host.hostUrl], { stdio: "ignore", detached: true, shell: true }).unref();
|
|
257
|
+
}
|
|
258
|
+
})().catch((error) => {
|
|
259
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
260
|
+
`);
|
|
261
|
+
void Promise.all([rollupWatcher.close(), server?.close()]).finally(() => process.exit(1));
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
const shutdown = () => {
|
|
265
|
+
void (async () => {
|
|
266
|
+
await rollupWatcher.close();
|
|
267
|
+
await host?.close();
|
|
268
|
+
await server?.close();
|
|
269
|
+
process.exit(0);
|
|
270
|
+
})();
|
|
271
|
+
};
|
|
272
|
+
process.on("SIGINT", shutdown);
|
|
273
|
+
process.on("SIGTERM", shutdown);
|
|
274
|
+
}
|
|
275
|
+
async function main() {
|
|
276
|
+
const [command, ...rest] = process.argv.slice(2);
|
|
277
|
+
if (command === void 0 || command === "--help" || command === "-h") {
|
|
278
|
+
printUsage();
|
|
279
|
+
process.exit(command === void 0 ? 1 : 0);
|
|
280
|
+
}
|
|
281
|
+
if (command === "create") {
|
|
282
|
+
const { createProject, parseCreateArgs } = await import("./create.js");
|
|
283
|
+
return createProject(parseCreateArgs(rest));
|
|
284
|
+
}
|
|
285
|
+
if (command === "dev") return dev(parseArgs(rest));
|
|
286
|
+
if (command === "build") return buildApps(parseBuildArgs(rest));
|
|
287
|
+
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);
|
|
291
|
+
}
|
|
292
|
+
main().catch((error) => {
|
|
293
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
294
|
+
`);
|
|
295
|
+
process.exit(1);
|
|
296
|
+
});
|
|
297
|
+
//# sourceMappingURL=cli.js.map
|