mountly-mcp 0.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/README.md +36 -0
- package/dist/bridge/iframe-entry.js +18704 -0
- package/dist/bridge/index.d.ts +49 -0
- package/dist/bridge/index.js +12 -0
- package/dist/bridge/index.js.map +1 -0
- package/dist/build/index.d.ts +81 -0
- package/dist/build/index.js +143 -0
- package/dist/build/index.js.map +1 -0
- package/dist/chunk-A64ZILAS.js +24 -0
- package/dist/chunk-A64ZILAS.js.map +1 -0
- package/dist/chunk-BUGGLMNA.js +132 -0
- package/dist/chunk-BUGGLMNA.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/types-B9JAWm4W.d.ts +62 -0
- package/package.json +38 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { App } from '@modelcontextprotocol/ext-apps';
|
|
2
|
+
export { App, McpUiAppCapabilities, McpUiHostCapabilities, McpUiHostContext, McpUiHostContextChangedNotification, McpUiToolCancelledNotification, McpUiToolInputNotification, McpUiToolResultNotification, PostMessageTransport } from '@modelcontextprotocol/ext-apps';
|
|
3
|
+
import { WidgetModule } from 'mountly/adapter';
|
|
4
|
+
import { D as DisplayMode } from '../types-B9JAWm4W.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* View-side bridge that turns an MCP Apps `App` into a mountly `WidgetModule`
|
|
8
|
+
* lifecycle. The full spec handshake — `ui/initialize`,
|
|
9
|
+
* `ui/notifications/initialized`, `ui/notifications/tool-input(-partial)`,
|
|
10
|
+
* `ui/notifications/tool-result`, `ui/notifications/tool-cancelled`,
|
|
11
|
+
* `ui/notifications/host-context-changed`, `ui/resource-teardown`,
|
|
12
|
+
* `ui/notifications/size-changed` — is delegated to ext-apps's `App`.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
interface RunBridgeOptions {
|
|
16
|
+
/** Pre-constructed App (used by jsdom tests); otherwise one is created here. */
|
|
17
|
+
app?: App;
|
|
18
|
+
widget: WidgetModule;
|
|
19
|
+
container: Element;
|
|
20
|
+
/**
|
|
21
|
+
* If true (default), mount is deferred until the first tool-result arrives;
|
|
22
|
+
* the widget renders against `{ toolInput, toolResult, hostContext, mcp }`
|
|
23
|
+
* props on each notification.
|
|
24
|
+
*
|
|
25
|
+
* If false, mount fires immediately after `ui/initialize` with `{ mcp }`
|
|
26
|
+
* only — for "stateless" views that drive themselves via `mcp.callServerTool`.
|
|
27
|
+
*/
|
|
28
|
+
awaitToolResult?: boolean;
|
|
29
|
+
/** Display modes this view supports. Declared to the host during `ui/initialize`. */
|
|
30
|
+
availableDisplayModes?: ReadonlyArray<DisplayMode>;
|
|
31
|
+
/** App identity sent during `ui/initialize`. */
|
|
32
|
+
appInfo?: {
|
|
33
|
+
name: string;
|
|
34
|
+
version: string;
|
|
35
|
+
};
|
|
36
|
+
/** Override for tests; defaults to `window.addEventListener('beforeunload', ...)`. */
|
|
37
|
+
onTeardown?: (handler: () => void) => () => void;
|
|
38
|
+
}
|
|
39
|
+
interface RunningBridge {
|
|
40
|
+
/** Resolves once `ui/initialize` handshake completes and the first mount (or deferred mount) is wired. */
|
|
41
|
+
ready: Promise<void>;
|
|
42
|
+
/** The underlying App; exposed for hosts that want to drive the view directly. */
|
|
43
|
+
app: App;
|
|
44
|
+
/** Synchronously triggers teardown. */
|
|
45
|
+
stop(): void;
|
|
46
|
+
}
|
|
47
|
+
declare function runBridge(options: RunBridgeOptions): RunningBridge;
|
|
48
|
+
|
|
49
|
+
export { type RunBridgeOptions, type RunningBridge, runBridge };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { D as DisplayMode, M as McpCsp, f as McpResourcePermissions, d as McpResourceDeclaration } from '../types-B9JAWm4W.js';
|
|
2
|
+
import '@modelcontextprotocol/ext-apps';
|
|
3
|
+
|
|
4
|
+
interface EmitHtmlSelfContainedInput {
|
|
5
|
+
mode: "self-contained";
|
|
6
|
+
uri: string;
|
|
7
|
+
js: string;
|
|
8
|
+
css: string;
|
|
9
|
+
bridgeRuntimeJs: string;
|
|
10
|
+
}
|
|
11
|
+
interface EmitHtmlCdnInput {
|
|
12
|
+
mode: "cdn";
|
|
13
|
+
uri: string;
|
|
14
|
+
jsUrl: string;
|
|
15
|
+
cssUrl: string;
|
|
16
|
+
bridgeRuntimeJs: string;
|
|
17
|
+
}
|
|
18
|
+
type EmitHtmlInput = EmitHtmlSelfContainedInput | EmitHtmlCdnInput;
|
|
19
|
+
declare function emitHtml(input: EmitHtmlInput): string;
|
|
20
|
+
|
|
21
|
+
interface EmitMetaInput {
|
|
22
|
+
uri: string;
|
|
23
|
+
name: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
awaitToolResult?: boolean;
|
|
26
|
+
displayModes?: ReadonlyArray<DisplayMode>;
|
|
27
|
+
csp?: McpCsp;
|
|
28
|
+
/** Sandbox permissions requested by the view (camera/microphone/geolocation/clipboardWrite). */
|
|
29
|
+
permissions?: McpResourcePermissions;
|
|
30
|
+
/** Dedicated sandbox origin requested by the view (host-dependent format). */
|
|
31
|
+
domain?: string;
|
|
32
|
+
/** Whether the view requests the host to show a visible border + background. */
|
|
33
|
+
prefersBorder?: boolean;
|
|
34
|
+
}
|
|
35
|
+
declare function emitMeta(input: EmitMetaInput): McpResourceDeclaration;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Absolute path to the iframe bridge runtime shipped with this package.
|
|
39
|
+
*
|
|
40
|
+
* `buildMcpResource` inlines this file's contents into the emitted HTML so
|
|
41
|
+
* the widget can speak the MCP Apps postMessage protocol. Use this when you
|
|
42
|
+
* need to point another build tool at the runtime, or when you want a
|
|
43
|
+
* stable, version-pinned alternative to passing `bridgeRuntimePath` yourself.
|
|
44
|
+
*
|
|
45
|
+
* Resolves relative to the installed package, so it works identically in
|
|
46
|
+
* monorepos and in projects that installed mountly-mcp from npm.
|
|
47
|
+
*/
|
|
48
|
+
declare function getBridgeRuntimePath(): string;
|
|
49
|
+
interface BuildSelfContainedOptions {
|
|
50
|
+
entry: string;
|
|
51
|
+
uri: string;
|
|
52
|
+
name: string;
|
|
53
|
+
output: string;
|
|
54
|
+
description?: string;
|
|
55
|
+
awaitToolResult?: boolean;
|
|
56
|
+
displayModes?: ReadonlyArray<DisplayMode>;
|
|
57
|
+
csp?: McpCsp;
|
|
58
|
+
/** Sandbox permissions requested by the view (camera/microphone/geolocation/clipboardWrite). */
|
|
59
|
+
permissions?: McpResourcePermissions;
|
|
60
|
+
/** Dedicated sandbox origin (host-dependent format). */
|
|
61
|
+
domain?: string;
|
|
62
|
+
/** Whether the view requests the host to show a visible border + background. */
|
|
63
|
+
prefersBorder?: boolean;
|
|
64
|
+
cssEntry?: string;
|
|
65
|
+
bridgeRuntimePath?: string;
|
|
66
|
+
}
|
|
67
|
+
interface BuildCdnOptions extends BuildSelfContainedOptions {
|
|
68
|
+
cdn: {
|
|
69
|
+
jsUrl: string;
|
|
70
|
+
cssUrl: string;
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
type BuildOptions = BuildSelfContainedOptions | BuildCdnOptions;
|
|
74
|
+
interface BuildResult {
|
|
75
|
+
htmlPath: string;
|
|
76
|
+
metaPath: string;
|
|
77
|
+
declaration: McpResourceDeclaration;
|
|
78
|
+
}
|
|
79
|
+
declare function buildMcpResource(options: BuildOptions): Promise<BuildResult>;
|
|
80
|
+
|
|
81
|
+
export { type BuildCdnOptions, type BuildOptions, type BuildResult, type BuildSelfContainedOptions, buildMcpResource, emitHtml, emitMeta, getBridgeRuntimePath };
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LATEST_PROTOCOL_VERSION,
|
|
3
|
+
RESOURCE_MIME_TYPE
|
|
4
|
+
} from "../chunk-A64ZILAS.js";
|
|
5
|
+
|
|
6
|
+
// src/build/index.ts
|
|
7
|
+
import { readFile, writeFile } from "fs/promises";
|
|
8
|
+
import { dirname, resolve } from "path";
|
|
9
|
+
import { fileURLToPath } from "url";
|
|
10
|
+
|
|
11
|
+
// src/build/emit-html.ts
|
|
12
|
+
function escapeAttr(value) {
|
|
13
|
+
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
14
|
+
}
|
|
15
|
+
function escapeScript(value) {
|
|
16
|
+
return value.replace(/<\/script/gi, "<\\/script");
|
|
17
|
+
}
|
|
18
|
+
function escapeStyle(value) {
|
|
19
|
+
return value.replace(/<\/style/gi, "<\\/style");
|
|
20
|
+
}
|
|
21
|
+
function emitHtml(input) {
|
|
22
|
+
const head = [
|
|
23
|
+
`<meta charset="utf-8">`,
|
|
24
|
+
`<meta name="mountly-mcp-protocol" content="${LATEST_PROTOCOL_VERSION}">`,
|
|
25
|
+
`<meta name="mountly-mcp-uri" content="${escapeAttr(input.uri)}">`
|
|
26
|
+
];
|
|
27
|
+
if (input.mode === "self-contained") {
|
|
28
|
+
head.push(`<style>${escapeStyle(input.css)}</style>`);
|
|
29
|
+
const body2 = [
|
|
30
|
+
`<div id="mountly-mcp-root"></div>`,
|
|
31
|
+
`<script type="module">${escapeScript(input.js)}
|
|
32
|
+
${escapeScript(input.bridgeRuntimeJs)}</script>`
|
|
33
|
+
];
|
|
34
|
+
return template(head, body2);
|
|
35
|
+
}
|
|
36
|
+
head.push(`<link rel="stylesheet" href="${escapeAttr(input.cssUrl)}">`);
|
|
37
|
+
const body = [
|
|
38
|
+
`<div id="mountly-mcp-root"></div>`,
|
|
39
|
+
`<script type="module">${escapeScript(input.bridgeRuntimeJs)}</script>`,
|
|
40
|
+
`<script type="module" src="${escapeAttr(input.jsUrl)}"></script>`
|
|
41
|
+
];
|
|
42
|
+
return template(head, body);
|
|
43
|
+
}
|
|
44
|
+
function template(head, body) {
|
|
45
|
+
return `<!doctype html>
|
|
46
|
+
<html data-mountly-mcp>
|
|
47
|
+
<head>
|
|
48
|
+
${head.join("\n")}
|
|
49
|
+
</head>
|
|
50
|
+
<body>
|
|
51
|
+
${body.join("\n")}
|
|
52
|
+
</body>
|
|
53
|
+
</html>`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/build/emit-meta.ts
|
|
57
|
+
function assertUiUri(uri) {
|
|
58
|
+
if (!uri.startsWith("ui://")) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
`mountly-mcp: UI resource uri must use the 'ui://' scheme (received '${uri}')`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function emitMeta(input) {
|
|
65
|
+
assertUiUri(input.uri);
|
|
66
|
+
const ui = {};
|
|
67
|
+
if (input.csp) ui.csp = input.csp;
|
|
68
|
+
if (input.permissions) ui.permissions = input.permissions;
|
|
69
|
+
if (input.domain !== void 0) ui.domain = input.domain;
|
|
70
|
+
if (input.prefersBorder !== void 0) ui.prefersBorder = input.prefersBorder;
|
|
71
|
+
const decl = {
|
|
72
|
+
protocolVersion: LATEST_PROTOCOL_VERSION,
|
|
73
|
+
uri: input.uri,
|
|
74
|
+
name: input.name,
|
|
75
|
+
mimeType: RESOURCE_MIME_TYPE,
|
|
76
|
+
awaitToolResult: input.awaitToolResult ?? true,
|
|
77
|
+
displayModes: input.displayModes ?? ["inline"],
|
|
78
|
+
_meta: { ui }
|
|
79
|
+
};
|
|
80
|
+
if (input.description !== void 0) decl.description = input.description;
|
|
81
|
+
return decl;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/build/index.ts
|
|
85
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
86
|
+
function getBridgeRuntimePath() {
|
|
87
|
+
return resolve(__dirname, "../bridge/iframe-entry.js");
|
|
88
|
+
}
|
|
89
|
+
function defaultBridgeRuntimePath() {
|
|
90
|
+
return getBridgeRuntimePath();
|
|
91
|
+
}
|
|
92
|
+
async function buildMcpResource(options) {
|
|
93
|
+
const bridgeRuntimeJs = await readFile(
|
|
94
|
+
options.bridgeRuntimePath ?? defaultBridgeRuntimePath(),
|
|
95
|
+
"utf8"
|
|
96
|
+
);
|
|
97
|
+
const declaration = emitMeta({
|
|
98
|
+
uri: options.uri,
|
|
99
|
+
name: options.name,
|
|
100
|
+
description: options.description,
|
|
101
|
+
awaitToolResult: options.awaitToolResult,
|
|
102
|
+
displayModes: options.displayModes,
|
|
103
|
+
csp: "cdn" in options ? mergeCdnIntoCsp(options.csp, options.cdn) : options.csp,
|
|
104
|
+
permissions: options.permissions,
|
|
105
|
+
domain: options.domain,
|
|
106
|
+
prefersBorder: options.prefersBorder
|
|
107
|
+
});
|
|
108
|
+
let html;
|
|
109
|
+
if ("cdn" in options) {
|
|
110
|
+
html = emitHtml({
|
|
111
|
+
mode: "cdn",
|
|
112
|
+
uri: options.uri,
|
|
113
|
+
jsUrl: options.cdn.jsUrl,
|
|
114
|
+
cssUrl: options.cdn.cssUrl,
|
|
115
|
+
bridgeRuntimeJs
|
|
116
|
+
});
|
|
117
|
+
} else {
|
|
118
|
+
const js = await readFile(options.entry, "utf8");
|
|
119
|
+
const css = options.cssEntry ? await readFile(options.cssEntry, "utf8") : "";
|
|
120
|
+
html = emitHtml({ mode: "self-contained", uri: options.uri, js, css, bridgeRuntimeJs });
|
|
121
|
+
}
|
|
122
|
+
const metaPath = `${options.output}.meta.json`;
|
|
123
|
+
await writeFile(options.output, html, "utf8");
|
|
124
|
+
await writeFile(metaPath, JSON.stringify(declaration, null, 2), "utf8");
|
|
125
|
+
return { htmlPath: options.output, metaPath, declaration };
|
|
126
|
+
}
|
|
127
|
+
function mergeCdnIntoCsp(csp, cdn) {
|
|
128
|
+
const jsOrigin = new URL(cdn.jsUrl).origin;
|
|
129
|
+
const cssOrigin = new URL(cdn.cssUrl).origin;
|
|
130
|
+
const merged = { ...csp ?? {} };
|
|
131
|
+
const existing = new Set(merged.resourceDomains ?? []);
|
|
132
|
+
existing.add(jsOrigin);
|
|
133
|
+
existing.add(cssOrigin);
|
|
134
|
+
merged.resourceDomains = Array.from(existing);
|
|
135
|
+
return merged;
|
|
136
|
+
}
|
|
137
|
+
export {
|
|
138
|
+
buildMcpResource,
|
|
139
|
+
emitHtml,
|
|
140
|
+
emitMeta,
|
|
141
|
+
getBridgeRuntimePath
|
|
142
|
+
};
|
|
143
|
+
//# sourceMappingURL=index.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 { dirname, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type {\n DisplayMode,\n McpCsp,\n McpResourceDeclaration,\n McpResourcePermissions,\n} from \"../types.js\";\nimport { emitHtml } from \"./emit-html.js\";\nimport { emitMeta } from \"./emit-meta.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\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 widget 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 return resolve(__dirname, \"../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<DisplayMode>;\n csp?: McpCsp;\n /** Sandbox permissions requested by the view (camera/microphone/geolocation/clipboardWrite). */\n permissions?: McpResourcePermissions;\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}\n\nexport async function buildMcpResource(options: BuildOptions): 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: \"cdn\" in options ? mergeCdnIntoCsp(options.csp, options.cdn) : options.csp,\n permissions: options.permissions,\n domain: options.domain,\n prefersBorder: options.prefersBorder,\n });\n\n let html: string;\n if (\"cdn\" in options) {\n html = emitHtml({\n mode: \"cdn\",\n uri: options.uri,\n jsUrl: options.cdn.jsUrl,\n cssUrl: options.cdn.cssUrl,\n bridgeRuntimeJs,\n });\n } else {\n const js = await readFile(options.entry, \"utf8\");\n const css = options.cssEntry ? await readFile(options.cssEntry, \"utf8\") : \"\";\n html = emitHtml({ mode: \"self-contained\", uri: options.uri, js, css, bridgeRuntimeJs });\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 return { htmlPath: options.output, metaPath, declaration };\n}\n\nfunction mergeCdnIntoCsp(\n csp: McpCsp | undefined,\n cdn: { jsUrl: string; cssUrl: string },\n): McpCsp {\n const jsOrigin = new URL(cdn.jsUrl).origin;\n const cssOrigin = new URL(cdn.cssUrl).origin;\n const merged: McpCsp = { ...(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\";\n\nexport interface EmitHtmlSelfContainedInput {\n mode: \"self-contained\";\n uri: string;\n js: string;\n css: string;\n bridgeRuntimeJs: string;\n}\n\nexport interface EmitHtmlCdnInput {\n mode: \"cdn\";\n uri: string;\n jsUrl: string;\n cssUrl: string;\n bridgeRuntimeJs: string;\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 escapeScript(value: string): string {\n return value.replace(/<\\/script/gi, \"<\\\\/script\");\n}\n\nfunction escapeStyle(value: string): string {\n return value.replace(/<\\/style/gi, \"<\\\\/style\");\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 `<script type=\"module\">${escapeScript(input.js)}\\n${escapeScript(input.bridgeRuntimeJs)}</script>`,\n ];\n return template(head, body);\n }\n\n head.push(`<link rel=\"stylesheet\" href=\"${escapeAttr(input.cssUrl)}\">`);\n const body = [\n `<div id=\"mountly-mcp-root\"></div>`,\n `<script type=\"module\">${escapeScript(input.bridgeRuntimeJs)}</script>`,\n `<script type=\"module\" src=\"${escapeAttr(input.jsUrl)}\"></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 DisplayMode,\n McpCsp,\n McpResourceDeclaration,\n McpResourcePermissions,\n} from \"../types.js\";\n\nexport interface EmitMetaInput {\n uri: string;\n name: string;\n description?: string;\n awaitToolResult?: boolean;\n displayModes?: ReadonlyArray<DisplayMode>;\n csp?: McpCsp;\n /** Sandbox permissions requested by the view (camera/microphone/geolocation/clipboardWrite). */\n permissions?: McpResourcePermissions;\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(\n `mountly-mcp: UI resource uri must use the 'ui://' scheme (received '${uri}')`,\n );\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,24 @@
|
|
|
1
|
+
// src/schema.ts
|
|
2
|
+
import {
|
|
3
|
+
LATEST_PROTOCOL_VERSION,
|
|
4
|
+
RESOURCE_MIME_TYPE,
|
|
5
|
+
RESOURCE_URI_META_KEY
|
|
6
|
+
} from "@modelcontextprotocol/ext-apps";
|
|
7
|
+
import { EXTENSION_ID } from "@modelcontextprotocol/ext-apps/server";
|
|
8
|
+
var MCP_APPS_URI_SCHEME = "ui://";
|
|
9
|
+
var MCP_ERROR_CODES = {
|
|
10
|
+
INITIALIZE_TIMEOUT: "mountly-mcp/initialize-timeout",
|
|
11
|
+
TOOL_CALL_FAILED: "mountly-mcp/tool-call-failed",
|
|
12
|
+
WIDGET_MOUNT_THREW: "mountly-mcp/widget-mount-threw",
|
|
13
|
+
INVALID_ORIGIN: "mountly-mcp/invalid-origin"
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
MCP_APPS_URI_SCHEME,
|
|
18
|
+
MCP_ERROR_CODES,
|
|
19
|
+
LATEST_PROTOCOL_VERSION,
|
|
20
|
+
RESOURCE_MIME_TYPE,
|
|
21
|
+
RESOURCE_URI_META_KEY,
|
|
22
|
+
EXTENSION_ID
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=chunk-A64ZILAS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/schema.ts"],"sourcesContent":["/**\n * Spec constants — re-exported from the canonical SDK so they cannot drift.\n * See `@modelcontextprotocol/ext-apps` for the source of truth.\n */\nexport {\n LATEST_PROTOCOL_VERSION as MCP_APPS_PROTOCOL_VERSION,\n RESOURCE_MIME_TYPE as MCP_APPS_MIME,\n RESOURCE_URI_META_KEY,\n} from \"@modelcontextprotocol/ext-apps\";\n\nexport { EXTENSION_ID } from \"@modelcontextprotocol/ext-apps/server\";\n\nexport const MCP_APPS_URI_SCHEME = \"ui://\" as const;\n\n/**\n * Implementation-specific error codes for mountly-mcp's host/bridge layer.\n * The spec doesn't standardize these; they appear in `notifications/message`\n * params so hosts can categorize widget failures.\n */\nexport const MCP_ERROR_CODES = {\n INITIALIZE_TIMEOUT: \"mountly-mcp/initialize-timeout\",\n TOOL_CALL_FAILED: \"mountly-mcp/tool-call-failed\",\n WIDGET_MOUNT_THREW: \"mountly-mcp/widget-mount-threw\",\n INVALID_ORIGIN: \"mountly-mcp/invalid-origin\",\n} as const;\n\nexport type McpErrorCode = (typeof MCP_ERROR_CODES)[keyof typeof MCP_ERROR_CODES];\n"],"mappings":";AAIA;AAAA,EAC6B;AAAA,EACL;AAAA,EACtB;AAAA,OACK;AAEP,SAAS,oBAAoB;AAEtB,IAAM,sBAAsB;AAO5B,IAAM,kBAAkB;AAAA,EAC7B,oBAAoB;AAAA,EACpB,kBAAkB;AAAA,EAClB,oBAAoB;AAAA,EACpB,gBAAgB;AAClB;","names":[]}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MCP_ERROR_CODES
|
|
3
|
+
} from "./chunk-A64ZILAS.js";
|
|
4
|
+
|
|
5
|
+
// src/bridge/index.ts
|
|
6
|
+
import {
|
|
7
|
+
App,
|
|
8
|
+
PostMessageTransport
|
|
9
|
+
} from "@modelcontextprotocol/ext-apps";
|
|
10
|
+
function renderErrorBoundary(container, code, message) {
|
|
11
|
+
const doc = container.ownerDocument;
|
|
12
|
+
if (!doc) return;
|
|
13
|
+
const errorDiv = doc.createElement("div");
|
|
14
|
+
errorDiv.setAttribute("data-mountly-mcp-error", code);
|
|
15
|
+
if (message !== void 0) errorDiv.textContent = message;
|
|
16
|
+
container.innerHTML = "";
|
|
17
|
+
container.appendChild(errorDiv);
|
|
18
|
+
}
|
|
19
|
+
function runBridge(options) {
|
|
20
|
+
const {
|
|
21
|
+
widget,
|
|
22
|
+
container,
|
|
23
|
+
awaitToolResult = true,
|
|
24
|
+
availableDisplayModes = ["inline"],
|
|
25
|
+
appInfo = { name: "mountly-mcp", version: "0.0.0" }
|
|
26
|
+
} = options;
|
|
27
|
+
const appCapabilities = {
|
|
28
|
+
availableDisplayModes: [...availableDisplayModes]
|
|
29
|
+
};
|
|
30
|
+
const app = options.app ?? new App(appInfo, appCapabilities);
|
|
31
|
+
let toolInput;
|
|
32
|
+
let toolResult;
|
|
33
|
+
let mounted = false;
|
|
34
|
+
let pending = Promise.resolve();
|
|
35
|
+
function currentProps() {
|
|
36
|
+
return {
|
|
37
|
+
mcp: app,
|
|
38
|
+
toolInput,
|
|
39
|
+
toolResult,
|
|
40
|
+
hostContext: app.getHostContext()
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function notifyError(code, error) {
|
|
44
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
45
|
+
try {
|
|
46
|
+
app.sendLog({ level: "error", data: { code, message } });
|
|
47
|
+
} catch {
|
|
48
|
+
}
|
|
49
|
+
renderErrorBoundary(container, code, message);
|
|
50
|
+
}
|
|
51
|
+
function renderWith(props) {
|
|
52
|
+
pending = pending.then(async () => {
|
|
53
|
+
try {
|
|
54
|
+
if (!mounted) {
|
|
55
|
+
const r = widget.mount(container, props);
|
|
56
|
+
if (r instanceof Promise) await r;
|
|
57
|
+
mounted = true;
|
|
58
|
+
} else if (widget.update) {
|
|
59
|
+
const r = widget.update(container, props);
|
|
60
|
+
if (r instanceof Promise) await r;
|
|
61
|
+
} else {
|
|
62
|
+
widget.unmount(container);
|
|
63
|
+
const r = widget.mount(container, props);
|
|
64
|
+
if (r instanceof Promise) await r;
|
|
65
|
+
mounted = true;
|
|
66
|
+
}
|
|
67
|
+
} catch (error) {
|
|
68
|
+
notifyError(MCP_ERROR_CODES.WIDGET_MOUNT_THREW, error);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
app.ontoolinput = (params) => {
|
|
73
|
+
toolInput = params;
|
|
74
|
+
if (!awaitToolResult) return;
|
|
75
|
+
if (mounted || toolResult !== void 0) renderWith(currentProps());
|
|
76
|
+
};
|
|
77
|
+
app.ontoolinputpartial = () => {
|
|
78
|
+
};
|
|
79
|
+
app.ontoolresult = (params) => {
|
|
80
|
+
toolResult = params;
|
|
81
|
+
renderWith(currentProps());
|
|
82
|
+
};
|
|
83
|
+
app.ontoolcancelled = () => {
|
|
84
|
+
renderWith(currentProps());
|
|
85
|
+
};
|
|
86
|
+
app.onhostcontextchanged = () => {
|
|
87
|
+
if (mounted) renderWith(currentProps());
|
|
88
|
+
};
|
|
89
|
+
app.onteardown = async () => {
|
|
90
|
+
if (mounted) {
|
|
91
|
+
widget.unmount(container);
|
|
92
|
+
mounted = false;
|
|
93
|
+
}
|
|
94
|
+
return {};
|
|
95
|
+
};
|
|
96
|
+
const ready = (async () => {
|
|
97
|
+
if (!options.app) {
|
|
98
|
+
await app.connect();
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
app.setupSizeChangedNotifications();
|
|
102
|
+
} catch {
|
|
103
|
+
}
|
|
104
|
+
if (!awaitToolResult) {
|
|
105
|
+
renderWith({ mcp: app });
|
|
106
|
+
}
|
|
107
|
+
})();
|
|
108
|
+
const detachTeardown = options.onTeardown?.(() => {
|
|
109
|
+
if (mounted) widget.unmount(container);
|
|
110
|
+
}) ?? (typeof window !== "undefined" ? (() => {
|
|
111
|
+
const h = () => {
|
|
112
|
+
if (mounted) widget.unmount(container);
|
|
113
|
+
};
|
|
114
|
+
window.addEventListener("beforeunload", h);
|
|
115
|
+
return () => window.removeEventListener("beforeunload", h);
|
|
116
|
+
})() : () => void 0);
|
|
117
|
+
return {
|
|
118
|
+
ready,
|
|
119
|
+
app,
|
|
120
|
+
stop() {
|
|
121
|
+
detachTeardown();
|
|
122
|
+
if (mounted) widget.unmount(container);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export {
|
|
128
|
+
App,
|
|
129
|
+
PostMessageTransport,
|
|
130
|
+
runBridge
|
|
131
|
+
};
|
|
132
|
+
//# sourceMappingURL=chunk-BUGGLMNA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/bridge/index.ts"],"sourcesContent":["/**\n * View-side bridge that turns an MCP Apps `App` into a mountly `WidgetModule`\n * lifecycle. The full spec handshake — `ui/initialize`,\n * `ui/notifications/initialized`, `ui/notifications/tool-input(-partial)`,\n * `ui/notifications/tool-result`, `ui/notifications/tool-cancelled`,\n * `ui/notifications/host-context-changed`, `ui/resource-teardown`,\n * `ui/notifications/size-changed` — is delegated to ext-apps's `App`.\n */\nimport {\n App,\n PostMessageTransport,\n type McpUiAppCapabilities,\n} from \"@modelcontextprotocol/ext-apps\";\nimport type { WidgetModule } from \"mountly/adapter\";\nimport { MCP_ERROR_CODES } from \"../schema.js\";\nimport type {\n DisplayMode,\n McpWidgetProps,\n ToolInput,\n ToolResult,\n} from \"../types.js\";\n\nexport interface RunBridgeOptions {\n /** Pre-constructed App (used by jsdom tests); otherwise one is created here. */\n app?: App;\n widget: WidgetModule;\n container: Element;\n /**\n * If true (default), mount is deferred until the first tool-result arrives;\n * the widget renders against `{ toolInput, toolResult, hostContext, mcp }`\n * props on each notification.\n *\n * If false, mount fires immediately after `ui/initialize` with `{ mcp }`\n * only — for \"stateless\" views that drive themselves via `mcp.callServerTool`.\n */\n awaitToolResult?: boolean;\n /** Display modes this view supports. Declared to the host during `ui/initialize`. */\n availableDisplayModes?: ReadonlyArray<DisplayMode>;\n /** App identity sent during `ui/initialize`. */\n appInfo?: { name: string; version: string };\n /** Override for tests; defaults to `window.addEventListener('beforeunload', ...)`. */\n onTeardown?: (handler: () => void) => () => void;\n}\n\nexport interface RunningBridge {\n /** Resolves once `ui/initialize` handshake completes and the first mount (or deferred mount) is wired. */\n ready: Promise<void>;\n /** The underlying App; exposed for hosts that want to drive the view directly. */\n app: App;\n /** Synchronously triggers teardown. */\n stop(): void;\n}\n\nfunction renderErrorBoundary(container: Element, code: string, message?: string): void {\n const doc = container.ownerDocument;\n if (!doc) return;\n const errorDiv = doc.createElement(\"div\");\n errorDiv.setAttribute(\"data-mountly-mcp-error\", code);\n if (message !== undefined) errorDiv.textContent = message;\n container.innerHTML = \"\";\n container.appendChild(errorDiv);\n}\n\nexport function runBridge(options: RunBridgeOptions): RunningBridge {\n const {\n widget,\n container,\n awaitToolResult = true,\n availableDisplayModes = [\"inline\"],\n appInfo = { name: \"mountly-mcp\", version: \"0.0.0\" },\n } = options;\n\n const appCapabilities: McpUiAppCapabilities = {\n availableDisplayModes: [...availableDisplayModes],\n };\n\n const app = options.app ?? new App(appInfo, appCapabilities);\n\n let toolInput: ToolInput | undefined;\n let toolResult: ToolResult | undefined;\n let mounted = false;\n let pending: Promise<void> = Promise.resolve();\n\n function currentProps(): McpWidgetProps {\n return {\n mcp: app,\n toolInput,\n toolResult,\n hostContext: app.getHostContext(),\n };\n }\n\n function notifyError(code: string, error: unknown): void {\n const message = error instanceof Error ? error.message : String(error);\n try {\n app.sendLog({ level: \"error\", data: { code, message } });\n } catch {\n // ignore — log channel may not be ready\n }\n renderErrorBoundary(container, code, message);\n }\n\n function renderWith(props: McpWidgetProps): void {\n pending = pending.then(async () => {\n try {\n if (!mounted) {\n const r = widget.mount(container, props);\n if (r instanceof Promise) await r;\n mounted = true;\n } else if (widget.update) {\n const r = widget.update(container, props);\n if (r instanceof Promise) await r;\n } else {\n widget.unmount(container);\n const r = widget.mount(container, props);\n if (r instanceof Promise) await r;\n mounted = true;\n }\n } catch (error) {\n notifyError(MCP_ERROR_CODES.WIDGET_MOUNT_THREW, error);\n }\n });\n }\n\n // Event handlers MUST be registered before connect() — ext-apps will refuse\n // to attach handlers retroactively after the initialize handshake completes.\n app.ontoolinput = (params) => {\n toolInput = params;\n if (!awaitToolResult) return;\n if (mounted || toolResult !== undefined) renderWith(currentProps());\n };\n\n app.ontoolinputpartial = () => {\n // Views MAY ignore partial input per spec; default policy is to skip.\n // Consumers wanting streaming UX can subscribe via app.addEventListener.\n };\n\n app.ontoolresult = (params) => {\n toolResult = params;\n renderWith(currentProps());\n };\n\n app.ontoolcancelled = () => {\n renderWith(currentProps());\n };\n\n app.onhostcontextchanged = () => {\n if (mounted) renderWith(currentProps());\n };\n\n app.onteardown = async () => {\n if (mounted) {\n widget.unmount(container);\n mounted = false;\n }\n return {};\n };\n\n const ready = (async () => {\n if (!options.app) {\n // App.connect() defaults to PostMessageTransport(window.parent, window.parent).\n await app.connect();\n }\n\n try {\n app.setupSizeChangedNotifications();\n } catch {\n // ResizeObserver may be unavailable (jsdom/non-browser).\n }\n\n if (!awaitToolResult) {\n renderWith({ mcp: app });\n }\n })();\n\n const detachTeardown =\n options.onTeardown?.(() => {\n if (mounted) widget.unmount(container);\n }) ??\n (typeof window !== \"undefined\"\n ? (() => {\n const h = () => {\n if (mounted) widget.unmount(container);\n };\n window.addEventListener(\"beforeunload\", h);\n return () => window.removeEventListener(\"beforeunload\", h);\n })()\n : () => undefined);\n\n return {\n ready,\n app,\n stop() {\n detachTeardown();\n if (mounted) widget.unmount(container);\n },\n };\n}\n\nexport { App, PostMessageTransport };\nexport type {\n McpUiAppCapabilities,\n McpUiHostCapabilities,\n McpUiHostContext,\n McpUiToolInputNotification,\n McpUiToolResultNotification,\n McpUiToolCancelledNotification,\n McpUiHostContextChangedNotification,\n} from \"@modelcontextprotocol/ext-apps\";\n"],"mappings":"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","names":[]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export { A as AppCapabilities, D as DisplayMode, H as HostCapabilities, a as HostContext, b as HostContextChanged, M as McpCsp, c as McpHost, d as McpResourceDeclaration, e as McpResourceMeta, f as McpResourcePermissions, g as McpToolMeta, h as McpToolVisibility, i as McpWidgetProps, T as ToolCancelled, j as ToolInput, k as ToolResult } from './types-B9JAWm4W.js';
|
|
2
|
+
export { RunBridgeOptions, RunningBridge, runBridge } from './bridge/index.js';
|
|
3
|
+
export { App, RESOURCE_MIME_TYPE as MCP_APPS_MIME, LATEST_PROTOCOL_VERSION as MCP_APPS_PROTOCOL_VERSION, PostMessageTransport, RESOURCE_URI_META_KEY } from '@modelcontextprotocol/ext-apps';
|
|
4
|
+
export { EXTENSION_ID } from '@modelcontextprotocol/ext-apps/server';
|
|
5
|
+
import 'mountly/adapter';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Spec constants — re-exported from the canonical SDK so they cannot drift.
|
|
9
|
+
* See `@modelcontextprotocol/ext-apps` for the source of truth.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
declare const MCP_APPS_URI_SCHEME: "ui://";
|
|
13
|
+
/**
|
|
14
|
+
* Implementation-specific error codes for mountly-mcp's host/bridge layer.
|
|
15
|
+
* The spec doesn't standardize these; they appear in `notifications/message`
|
|
16
|
+
* params so hosts can categorize widget failures.
|
|
17
|
+
*/
|
|
18
|
+
declare const MCP_ERROR_CODES: {
|
|
19
|
+
readonly INITIALIZE_TIMEOUT: "mountly-mcp/initialize-timeout";
|
|
20
|
+
readonly TOOL_CALL_FAILED: "mountly-mcp/tool-call-failed";
|
|
21
|
+
readonly WIDGET_MOUNT_THREW: "mountly-mcp/widget-mount-threw";
|
|
22
|
+
readonly INVALID_ORIGIN: "mountly-mcp/invalid-origin";
|
|
23
|
+
};
|
|
24
|
+
type McpErrorCode = (typeof MCP_ERROR_CODES)[keyof typeof MCP_ERROR_CODES];
|
|
25
|
+
|
|
26
|
+
export { MCP_APPS_URI_SCHEME, MCP_ERROR_CODES, type McpErrorCode };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import {
|
|
2
|
+
App,
|
|
3
|
+
PostMessageTransport,
|
|
4
|
+
runBridge
|
|
5
|
+
} from "./chunk-BUGGLMNA.js";
|
|
6
|
+
import {
|
|
7
|
+
EXTENSION_ID,
|
|
8
|
+
LATEST_PROTOCOL_VERSION,
|
|
9
|
+
MCP_APPS_URI_SCHEME,
|
|
10
|
+
MCP_ERROR_CODES,
|
|
11
|
+
RESOURCE_MIME_TYPE,
|
|
12
|
+
RESOURCE_URI_META_KEY
|
|
13
|
+
} from "./chunk-A64ZILAS.js";
|
|
14
|
+
export {
|
|
15
|
+
App,
|
|
16
|
+
EXTENSION_ID,
|
|
17
|
+
RESOURCE_MIME_TYPE as MCP_APPS_MIME,
|
|
18
|
+
LATEST_PROTOCOL_VERSION as MCP_APPS_PROTOCOL_VERSION,
|
|
19
|
+
MCP_APPS_URI_SCHEME,
|
|
20
|
+
MCP_ERROR_CODES,
|
|
21
|
+
PostMessageTransport,
|
|
22
|
+
RESOURCE_URI_META_KEY,
|
|
23
|
+
runBridge
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { McpUiAppCapabilities, McpUiDisplayMode, McpUiHostCapabilities, McpUiHostContext, McpUiHostContextChangedNotification, McpUiResourceCsp, App, RESOURCE_MIME_TYPE, McpUiResourceMeta, McpUiResourcePermissions, McpUiToolMeta, McpUiToolVisibility, McpUiToolInputNotification, McpUiToolResultNotification, McpUiToolCancelledNotification } from '@modelcontextprotocol/ext-apps';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Types — re-exported from `@modelcontextprotocol/ext-apps` so they stay in
|
|
5
|
+
* lock-step with the official MCP Apps spec (SEP-1865, 2026-01-26).
|
|
6
|
+
*
|
|
7
|
+
* Only the mountly-specific glue types (props passed to a widget's mount(),
|
|
8
|
+
* resource declaration written to the sidecar JSON) live here.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** The MCP Apps view-side SDK instance. mountly-mcp surfaces this through React context. */
|
|
12
|
+
type McpHost = App;
|
|
13
|
+
type DisplayMode = McpUiDisplayMode;
|
|
14
|
+
type AppCapabilities = McpUiAppCapabilities;
|
|
15
|
+
type HostCapabilities = McpUiHostCapabilities;
|
|
16
|
+
type HostContext = McpUiHostContext;
|
|
17
|
+
type McpCsp = McpUiResourceCsp;
|
|
18
|
+
type McpResourcePermissions = McpUiResourcePermissions;
|
|
19
|
+
type McpResourceMeta = McpUiResourceMeta;
|
|
20
|
+
type McpToolMeta = McpUiToolMeta;
|
|
21
|
+
type McpToolVisibility = McpUiToolVisibility;
|
|
22
|
+
type ToolInput = McpUiToolInputNotification["params"];
|
|
23
|
+
type ToolResult = McpUiToolResultNotification["params"];
|
|
24
|
+
type ToolCancelled = McpUiToolCancelledNotification["params"];
|
|
25
|
+
type HostContextChanged = McpUiHostContextChangedNotification["params"];
|
|
26
|
+
/**
|
|
27
|
+
* Props passed to a `WidgetModule.mount(container, props)` call.
|
|
28
|
+
*
|
|
29
|
+
* The bridge surfaces the App handle plus the latest tool input/result and a
|
|
30
|
+
* snapshot of host context. Widgets receive new props through `update()` as
|
|
31
|
+
* notifications arrive — see `runBridge` in `bridge/index.ts`.
|
|
32
|
+
*/
|
|
33
|
+
interface McpWidgetProps {
|
|
34
|
+
/** The view-side ext-apps App; exposes the full spec-compliant API surface. */
|
|
35
|
+
mcp: McpHost;
|
|
36
|
+
/** Tool arguments (after `ui/notifications/tool-input`). */
|
|
37
|
+
toolInput?: ToolInput;
|
|
38
|
+
/** Tool result (after `ui/notifications/tool-result`). */
|
|
39
|
+
toolResult?: ToolResult;
|
|
40
|
+
/** Host context (theme, styles, displayMode, container dimensions, etc.). */
|
|
41
|
+
hostContext?: HostContext;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Resource metadata written to the `.meta.json` sidecar next to the emitted
|
|
45
|
+
* HTML, consumed by `mountly-mcp-server` when registering the `ui://`
|
|
46
|
+
* resource with `_meta.ui = {...}`.
|
|
47
|
+
*/
|
|
48
|
+
interface McpResourceDeclaration {
|
|
49
|
+
protocolVersion: string;
|
|
50
|
+
uri: string;
|
|
51
|
+
name: string;
|
|
52
|
+
description?: string;
|
|
53
|
+
mimeType: typeof RESOURCE_MIME_TYPE;
|
|
54
|
+
/** Whether `runBridge` defers mount until the first tool-result arrives. */
|
|
55
|
+
awaitToolResult: boolean;
|
|
56
|
+
displayModes: ReadonlyArray<DisplayMode>;
|
|
57
|
+
_meta: {
|
|
58
|
+
ui: McpResourceMeta;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export type { AppCapabilities as A, DisplayMode as D, HostCapabilities as H, McpCsp as M, ToolCancelled as T, HostContext as a, HostContextChanged as b, McpHost as c, McpResourceDeclaration as d, McpResourceMeta as e, McpResourcePermissions as f, McpToolMeta as g, McpToolVisibility as h, McpWidgetProps as i, ToolInput as j, ToolResult as k };
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mountly-mcp",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "Publish a Mountly widget as an MCP Apps ui:// resource (SEP-1865).",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/jagreehal/mountly",
|
|
9
|
+
"directory": "packages/adapters/mountly-mcp"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"main": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": { "import": "./dist/index.js", "types": "./dist/index.d.ts" },
|
|
16
|
+
"./build": { "import": "./dist/build/index.js", "types": "./dist/build/index.d.ts" },
|
|
17
|
+
"./bridge": { "import": "./dist/bridge/index.js", "types": "./dist/bridge/index.d.ts" },
|
|
18
|
+
"./bridge/iframe-entry.js": "./dist/bridge/iframe-entry.js"
|
|
19
|
+
},
|
|
20
|
+
"files": ["dist"],
|
|
21
|
+
"publishConfig": { "access": "public" },
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsup",
|
|
24
|
+
"typecheck": "tsc --noEmit",
|
|
25
|
+
"dev": "tsup --watch"
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"mountly": "^0.2.2"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@modelcontextprotocol/ext-apps": "^1.7.1"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"mountly": "^0.2.2",
|
|
35
|
+
"tsup": "^8.5.1",
|
|
36
|
+
"typescript": "^6.0.3"
|
|
37
|
+
}
|
|
38
|
+
}
|