mountly-mcp 4.0.0 → 4.0.2
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 +22 -17
- package/dist/bridge/iframe-entry.js +3 -1
- package/dist/bridge/index.js +1 -1
- package/dist/{chunk-JVQZGKBL.js → chunk-2HNXL5A4.js} +4 -2
- package/dist/chunk-2HNXL5A4.js.map +1 -0
- package/dist/{chunk-SADNMGP6.js → chunk-5L3QHYWK.js} +1 -1
- package/dist/{chunk-SADNMGP6.js.map → chunk-5L3QHYWK.js.map} +1 -1
- package/dist/{chunk-AEVQKFXX.js → chunk-SXF23BZD.js} +2 -2
- package/dist/create.d.ts +1 -1
- package/dist/create.js +8 -2
- package/dist/create.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/json-render/index.js +2 -2
- package/dist/react/index.js +2 -2
- package/dist/svelte/index.js +1 -1
- package/dist/vue/index.js +1 -1
- package/package.json +9 -9
- package/templates/react/package.json.tmpl +1 -1
- package/templates/svelte/package.json.tmpl +1 -1
- package/templates/vanilla/.gitignore.tmpl +4 -0
- package/templates/vanilla/README.md.tmpl +20 -0
- package/templates/vanilla/mcp.fixtures.json.tmpl +4 -0
- package/templates/vanilla/package.json.tmpl +22 -0
- package/templates/vanilla/server.mjs.tmpl +70 -0
- package/templates/vanilla/src/view.ts.tmpl +177 -0
- package/templates/vanilla/tsconfig.json.tmpl +14 -0
- package/templates/vanilla/vite.config.ts.tmpl +20 -0
- package/templates/vue/package.json.tmpl +1 -1
- package/dist/chunk-JVQZGKBL.js.map +0 -1
- /package/dist/{chunk-AEVQKFXX.js.map → chunk-SXF23BZD.js.map} +0 -0
package/README.md
CHANGED
|
@@ -23,14 +23,19 @@ npx mountly-mcp create my-app --framework react
|
|
|
23
23
|
cd my-app && pnpm install && pnpm dev
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
+
`--framework` takes `react`, `vue`, `svelte`, or `vanilla`. Vanilla pulls in no
|
|
27
|
+
framework and no adapter: a View is just `mount` / `update` / `unmount`, so
|
|
28
|
+
`publishMcpView` takes the object directly and the bridge drives it the same
|
|
29
|
+
way it drives the others.
|
|
30
|
+
|
|
26
31
|
## Build with Agent Skills
|
|
27
32
|
|
|
28
|
-
| Skill
|
|
29
|
-
|
|
|
30
|
-
| [`create-mcp-app`](../../plugins/mountly-mcp/skills/create-mcp-app/SKILL.md)
|
|
31
|
-
| [`add-app-to-server`](../../plugins/mountly-mcp/skills/add-app-to-server/SKILL.md) | _"Add UI to my MCP server"_
|
|
32
|
-
| [`convert-web-app`](../../plugins/mountly-mcp/skills/convert-web-app/SKILL.md)
|
|
33
|
-
| [`migrate-ext-apps`](../../plugins/mountly-mcp/skills/migrate-ext-apps/SKILL.md)
|
|
33
|
+
| Skill | Try it |
|
|
34
|
+
| ---------------------------------------------------------------------------------- | ------------------------------------- |
|
|
35
|
+
| [`create-mcp-app`](../../plugins/mountly-mcp/skills/create-mcp-app/SKILL.md) | _"Create an MCP App"_ |
|
|
36
|
+
| [`add-app-to-server`](../../plugins/mountly-mcp/skills/add-app-to-server/SKILL.md) | _"Add UI to my MCP server"_ |
|
|
37
|
+
| [`convert-web-app`](../../plugins/mountly-mcp/skills/convert-web-app/SKILL.md) | _"Turn my component into an MCP App"_ |
|
|
38
|
+
| [`migrate-ext-apps`](../../plugins/mountly-mcp/skills/migrate-ext-apps/SKILL.md) | _"Migrate from ext-apps"_ |
|
|
34
39
|
|
|
35
40
|
```
|
|
36
41
|
/plugin marketplace add jagreehal/mountly
|
|
@@ -266,18 +271,18 @@ renders what it can. Drop the loose branch once core relaxes those fields.
|
|
|
266
271
|
|
|
267
272
|
## Exports
|
|
268
273
|
|
|
269
|
-
| Entry | What's in it
|
|
270
|
-
| ---------------------- |
|
|
271
|
-
| `mountly-mcp` | `runBridge`, spec types re-exported from ext-apps
|
|
272
|
-
| `mountly-mcp/react` | `createMcpView` + hooks
|
|
273
|
-
| `mountly-mcp/vue` | `createMcpView` + composables
|
|
274
|
-
| `mountly-mcp/svelte` | `createMcpView` (props-based)
|
|
274
|
+
| Entry | What's in it |
|
|
275
|
+
| ---------------------- | ------------------------------------------------------- |
|
|
276
|
+
| `mountly-mcp` | `runBridge`, spec types re-exported from ext-apps |
|
|
277
|
+
| `mountly-mcp/react` | `createMcpView` + hooks |
|
|
278
|
+
| `mountly-mcp/vue` | `createMcpView` + composables |
|
|
279
|
+
| `mountly-mcp/svelte` | `createMcpView` (props-based) |
|
|
275
280
|
| `mountly-mcp/vite` | single- and multi-View `mountlyMcpViews()` build plugin |
|
|
276
|
-
| `mountly-mcp/artifact` | versioned manifest and App artifact APIs
|
|
277
|
-
| `mountly-mcp/dev` | `startDevHost()` and in-process server connectivity
|
|
278
|
-
| `mountly-mcp/build` | `buildMcpResource` for other bundlers
|
|
279
|
-
| `mountly-mcp/server` | `registerMcpApps` plus convenience server adapters
|
|
280
|
-
| `mountly-mcp/testing` | static/browser conformance and report formatting
|
|
281
|
+
| `mountly-mcp/artifact` | versioned manifest and App artifact APIs |
|
|
282
|
+
| `mountly-mcp/dev` | `startDevHost()` and in-process server connectivity |
|
|
283
|
+
| `mountly-mcp/build` | `buildMcpResource` for other bundlers |
|
|
284
|
+
| `mountly-mcp/server` | `registerMcpApps` plus convenience server adapters |
|
|
285
|
+
| `mountly-mcp/testing` | static/browser conformance and report formatting |
|
|
281
286
|
|
|
282
287
|
Types use the spec's own names (`McpUiHostContext`, `McpUiDisplayMode`, …) —
|
|
283
288
|
one vocabulary, no translation layer.
|
|
@@ -18589,6 +18589,7 @@ container holding the app. Specify either width or maxWidth, and either height o
|
|
|
18589
18589
|
let toolResult;
|
|
18590
18590
|
let mounted = false;
|
|
18591
18591
|
let stopped = false;
|
|
18592
|
+
let failed = false;
|
|
18592
18593
|
let pending = Promise.resolve();
|
|
18593
18594
|
function currentProps() {
|
|
18594
18595
|
return {
|
|
@@ -18600,6 +18601,7 @@ container holding the app. Specify either width or maxWidth, and either height o
|
|
|
18600
18601
|
};
|
|
18601
18602
|
}
|
|
18602
18603
|
function notifyError(code, error51) {
|
|
18604
|
+
failed = true;
|
|
18603
18605
|
const message = error51 instanceof Error ? error51.message : String(error51);
|
|
18604
18606
|
try {
|
|
18605
18607
|
void app.sendLog({ level: "error", data: { code, message } }).catch(() => void 0);
|
|
@@ -18615,7 +18617,7 @@ container holding the app. Specify either width or maxWidth, and either height o
|
|
|
18615
18617
|
const r2 = view2.mount(container2, props);
|
|
18616
18618
|
if (r2 instanceof Promise) await r2;
|
|
18617
18619
|
mounted = true;
|
|
18618
|
-
container2.setAttribute("data-mountly-mcp-state", "mounted");
|
|
18620
|
+
if (!failed) container2.setAttribute("data-mountly-mcp-state", "mounted");
|
|
18619
18621
|
} else if (view2.update) {
|
|
18620
18622
|
const r2 = view2.update(container2, props);
|
|
18621
18623
|
if (r2 instanceof Promise) await r2;
|
package/dist/bridge/index.js
CHANGED
|
@@ -35,6 +35,7 @@ function runBridge(options) {
|
|
|
35
35
|
let toolResult;
|
|
36
36
|
let mounted = false;
|
|
37
37
|
let stopped = false;
|
|
38
|
+
let failed = false;
|
|
38
39
|
let pending = Promise.resolve();
|
|
39
40
|
function currentProps() {
|
|
40
41
|
return {
|
|
@@ -46,6 +47,7 @@ function runBridge(options) {
|
|
|
46
47
|
};
|
|
47
48
|
}
|
|
48
49
|
function notifyError(code, error) {
|
|
50
|
+
failed = true;
|
|
49
51
|
const message = error instanceof Error ? error.message : String(error);
|
|
50
52
|
try {
|
|
51
53
|
void app.sendLog({ level: "error", data: { code, message } }).catch(() => void 0);
|
|
@@ -61,7 +63,7 @@ function runBridge(options) {
|
|
|
61
63
|
const r = view.mount(container, props);
|
|
62
64
|
if (r instanceof Promise) await r;
|
|
63
65
|
mounted = true;
|
|
64
|
-
container.setAttribute("data-mountly-mcp-state", "mounted");
|
|
66
|
+
if (!failed) container.setAttribute("data-mountly-mcp-state", "mounted");
|
|
65
67
|
} else if (view.update) {
|
|
66
68
|
const r = view.update(container, props);
|
|
67
69
|
if (r instanceof Promise) await r;
|
|
@@ -168,4 +170,4 @@ export {
|
|
|
168
170
|
PostMessageTransport,
|
|
169
171
|
runBridge
|
|
170
172
|
};
|
|
171
|
-
//# sourceMappingURL=chunk-
|
|
173
|
+
//# sourceMappingURL=chunk-2HNXL5A4.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 view module\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 type McpUiDisplayMode,\n type McpUiToolInputNotification,\n type McpUiToolInputPartialNotification,\n type McpUiToolResultNotification,\n} from \"@modelcontextprotocol/ext-apps\";\n\nimport { MCP_ERROR_CODES } from \"../schema.js\";\nimport type { McpView, McpViewProps } from \"../types.js\";\n\nexport interface RunBridgeOptions {\n /** Pre-constructed App (used by jsdom tests); otherwise one is created here. */\n app?: App;\n view: McpView;\n container: Element;\n /**\n * If true (default), mount is deferred until the first tool-result arrives;\n * the View 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 /**\n * Render progressive states from `ui/notifications/tool-input-partial`.\n *\n * Off by default — the spec lets views ignore partials, and they carry\n * best-effort recovered JSON that MUST NOT drive critical operations. When\n * on, partials arrive as `toolInputPartial` (mounting the View early if\n * needed) and are cleared once the complete `tool-input` lands.\n */\n streamToolInput?: boolean;\n /** Display modes this view supports. Declared to the host during `ui/initialize`. */\n availableDisplayModes?: ReadonlyArray<McpUiDisplayMode>;\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\n/**\n * `data-mountly-mcp-state` is the View's machine-readable outcome, read by\n * `verify --render`. Once the boundary writes `error` the state latches:\n * a View that threw is a defect worth reporting even if a later notification\n * happens to render it successfully. The UI still recovers — only the recorded\n * outcome is sticky. See the `failed` flag in `runBridge`.\n */\nfunction renderErrorBoundary(container: Element, code: string, message?: string): void {\n container.setAttribute(\"data-mountly-mcp-state\", \"error\");\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 view,\n container,\n awaitToolResult = true,\n streamToolInput = false,\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: McpUiToolInputNotification[\"params\"] | undefined;\n let toolInputPartial: McpUiToolInputPartialNotification[\"params\"] | undefined;\n let toolResult: McpUiToolResultNotification[\"params\"] | undefined;\n let mounted = false;\n let stopped = false;\n /** Latches once the error boundary has fired; see `renderErrorBoundary`. */\n let failed = false;\n let pending: Promise<void> = Promise.resolve();\n\n function currentProps(): McpViewProps {\n return {\n mcp: app,\n toolInput,\n toolInputPartial,\n toolResult,\n hostContext: app.getHostContext(),\n };\n }\n\n function notifyError(code: string, error: unknown): void {\n failed = true;\n const message = error instanceof Error ? error.message : String(error);\n try {\n void app.sendLog({ level: \"error\", data: { code, message } }).catch(() => undefined);\n } catch {\n // ignore — log channel may not be ready\n }\n renderErrorBoundary(container, code, message);\n }\n\n function renderWith(props: McpViewProps): Promise<void> {\n pending = pending.then(async () => {\n if (stopped) return;\n try {\n if (!mounted) {\n const r = view.mount(container, props);\n if (r instanceof Promise) await r;\n mounted = true;\n // Recovering the UI is good; pretending the earlier throw did not\n // happen is not. The attribute stays `error` once it has been set.\n if (!failed) container.setAttribute(\"data-mountly-mcp-state\", \"mounted\");\n } else if (view.update) {\n const r = view.update(container, props);\n if (r instanceof Promise) await r;\n } else {\n await view.unmount(container);\n mounted = false;\n const r = view.mount(container, props);\n if (r instanceof Promise) await r;\n mounted = true;\n }\n } catch (error) {\n notifyError(MCP_ERROR_CODES.VIEW_MOUNT_THREW, error);\n }\n });\n return pending;\n }\n\n async function unmountView(): Promise<void> {\n await pending;\n if (!mounted) return;\n mounted = false;\n await view.unmount(container);\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 const handleToolInput = (params: McpUiToolInputNotification[\"params\"]) => {\n toolInput = params;\n toolInputPartial = undefined;\n if (!awaitToolResult) return;\n if (mounted || toolResult !== undefined) void renderWith(currentProps());\n };\n\n const handleToolInputPartial = (params: McpUiToolInputPartialNotification[\"params\"]) => {\n // Views MAY ignore partial input per spec; opt in with `streamToolInput`.\n // Consumers wanting finer control can subscribe via app.addEventListener.\n if (!streamToolInput) return;\n toolInputPartial = params;\n void renderWith(currentProps());\n };\n\n const handleToolResult = (params: McpUiToolResultNotification[\"params\"]) => {\n toolResult = params;\n void renderWith(currentProps());\n };\n\n const handleToolCancelled = () => {\n void renderWith(currentProps());\n };\n\n const handleHostContextChanged = () => {\n if (mounted) void renderWith(currentProps());\n };\n\n app.addEventListener(\"toolinput\", handleToolInput);\n app.addEventListener(\"toolinputpartial\", handleToolInputPartial);\n app.addEventListener(\"toolresult\", handleToolResult);\n app.addEventListener(\"toolcancelled\", handleToolCancelled);\n app.addEventListener(\"hostcontextchanged\", handleHostContextChanged);\n\n const previousTeardown = app.onteardown;\n const bridgeTeardown: NonNullable<typeof app.onteardown> = async (params, extra) => {\n stopped = true;\n detachTeardown();\n detachAppHandlers();\n await unmountView();\n return (await previousTeardown?.(params, extra)) ?? {};\n };\n app.onteardown = bridgeTeardown;\n\n function detachAppHandlers(): void {\n app.removeEventListener(\"toolinput\", handleToolInput);\n app.removeEventListener(\"toolinputpartial\", handleToolInputPartial);\n app.removeEventListener(\"toolresult\", handleToolResult);\n app.removeEventListener(\"toolcancelled\", handleToolCancelled);\n app.removeEventListener(\"hostcontextchanged\", handleHostContextChanged);\n if (app.onteardown === bridgeTeardown) app.onteardown = previousTeardown;\n }\n\n const ready = (async () => {\n try {\n if (!options.app) {\n // App.connect() defaults to PostMessageTransport(window.parent, window.parent)\n // and owns automatic resize setup.\n await app.connect();\n }\n\n if (!awaitToolResult) {\n await renderWith(currentProps());\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const code = /timeout/i.test(message)\n ? MCP_ERROR_CODES.INITIALIZE_TIMEOUT\n : MCP_ERROR_CODES.INITIALIZE_FAILED;\n notifyError(code, error);\n throw error;\n }\n })();\n\n let detachTeardown: () => void = () => undefined;\n\n function stopBridge(): void {\n if (stopped) return;\n stopped = true;\n detachTeardown();\n detachAppHandlers();\n void unmountView();\n if (!options.app) void app.close().catch(() => undefined);\n }\n\n detachTeardown =\n options.onTeardown?.(stopBridge) ??\n (typeof window !== \"undefined\"\n ? (() => {\n const h = () => stopBridge();\n window.addEventListener(\"beforeunload\", h);\n return () => window.removeEventListener(\"beforeunload\", h);\n })()\n : () => undefined);\n\n return {\n ready,\n app,\n stop: stopBridge,\n };\n}\n\nexport { App, PostMessageTransport };\nexport type {\n McpUiAppCapabilities,\n McpUiHostCapabilities,\n McpUiHostContext,\n McpUiToolInputNotification,\n McpUiToolInputPartialNotification,\n McpUiToolResultNotification,\n McpUiToolCancelledNotification,\n McpUiHostContextChangedNotification,\n} from \"@modelcontextprotocol/ext-apps\";\n"],"mappings":";;;;;AAQA;AAAA,EACE;AAAA,EACA;AAAA,OAMK;AAoDP,SAAS,oBAAoB,WAAoB,MAAc,SAAwB;AACrF,YAAU,aAAa,0BAA0B,OAAO;AACxD,QAAM,MAAM,UAAU;AACtB,MAAI,CAAC,IAAK;AACV,QAAM,WAAW,IAAI,cAAc,KAAK;AACxC,WAAS,aAAa,0BAA0B,IAAI;AACpD,MAAI,YAAY,OAAW,UAAS,cAAc;AAClD,YAAU,YAAY;AACtB,YAAU,YAAY,QAAQ;AAChC;AAEO,SAAS,UAAU,SAA0C;AAClE,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA,kBAAkB;AAAA,IAClB,kBAAkB;AAAA,IAClB,wBAAwB,CAAC,QAAQ;AAAA,IACjC,UAAU,EAAE,MAAM,eAAe,SAAS,QAAQ;AAAA,EACpD,IAAI;AAEJ,QAAM,kBAAwC;AAAA,IAC5C,uBAAuB,CAAC,GAAG,qBAAqB;AAAA,EAClD;AAEA,QAAM,MAAM,QAAQ,OAAO,IAAI,IAAI,SAAS,eAAe;AAE3D,MAAI;AACJ,MAAI;AACJ,MAAI;AACJ,MAAI,UAAU;AACd,MAAI,UAAU;AAEd,MAAI,SAAS;AACb,MAAI,UAAyB,QAAQ,QAAQ;AAE7C,WAAS,eAA6B;AACpC,WAAO;AAAA,MACL,KAAK;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA,aAAa,IAAI,eAAe;AAAA,IAClC;AAAA,EACF;AAEA,WAAS,YAAY,MAAc,OAAsB;AACvD,aAAS;AACT,UAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,QAAI;AACF,WAAK,IAAI,QAAQ,EAAE,OAAO,SAAS,MAAM,EAAE,MAAM,QAAQ,EAAE,CAAC,EAAE,MAAM,MAAM,MAAS;AAAA,IACrF,QAAQ;AAAA,IAER;AACA,wBAAoB,WAAW,MAAM,OAAO;AAAA,EAC9C;AAEA,WAAS,WAAW,OAAoC;AACtD,cAAU,QAAQ,KAAK,YAAY;AACjC,UAAI,QAAS;AACb,UAAI;AACF,YAAI,CAAC,SAAS;AACZ,gBAAM,IAAI,KAAK,MAAM,WAAW,KAAK;AACrC,cAAI,aAAa,QAAS,OAAM;AAChC,oBAAU;AAGV,cAAI,CAAC,OAAQ,WAAU,aAAa,0BAA0B,SAAS;AAAA,QACzE,WAAW,KAAK,QAAQ;AACtB,gBAAM,IAAI,KAAK,OAAO,WAAW,KAAK;AACtC,cAAI,aAAa,QAAS,OAAM;AAAA,QAClC,OAAO;AACL,gBAAM,KAAK,QAAQ,SAAS;AAC5B,oBAAU;AACV,gBAAM,IAAI,KAAK,MAAM,WAAW,KAAK;AACrC,cAAI,aAAa,QAAS,OAAM;AAChC,oBAAU;AAAA,QACZ;AAAA,MACF,SAAS,OAAO;AACd,oBAAY,gBAAgB,kBAAkB,KAAK;AAAA,MACrD;AAAA,IACF,CAAC;AACD,WAAO;AAAA,EACT;AAEA,iBAAe,cAA6B;AAC1C,UAAM;AACN,QAAI,CAAC,QAAS;AACd,cAAU;AACV,UAAM,KAAK,QAAQ,SAAS;AAAA,EAC9B;AAIA,QAAM,kBAAkB,CAAC,WAAiD;AACxE,gBAAY;AACZ,uBAAmB;AACnB,QAAI,CAAC,gBAAiB;AACtB,QAAI,WAAW,eAAe,OAAW,MAAK,WAAW,aAAa,CAAC;AAAA,EACzE;AAEA,QAAM,yBAAyB,CAAC,WAAwD;AAGtF,QAAI,CAAC,gBAAiB;AACtB,uBAAmB;AACnB,SAAK,WAAW,aAAa,CAAC;AAAA,EAChC;AAEA,QAAM,mBAAmB,CAAC,WAAkD;AAC1E,iBAAa;AACb,SAAK,WAAW,aAAa,CAAC;AAAA,EAChC;AAEA,QAAM,sBAAsB,MAAM;AAChC,SAAK,WAAW,aAAa,CAAC;AAAA,EAChC;AAEA,QAAM,2BAA2B,MAAM;AACrC,QAAI,QAAS,MAAK,WAAW,aAAa,CAAC;AAAA,EAC7C;AAEA,MAAI,iBAAiB,aAAa,eAAe;AACjD,MAAI,iBAAiB,oBAAoB,sBAAsB;AAC/D,MAAI,iBAAiB,cAAc,gBAAgB;AACnD,MAAI,iBAAiB,iBAAiB,mBAAmB;AACzD,MAAI,iBAAiB,sBAAsB,wBAAwB;AAEnE,QAAM,mBAAmB,IAAI;AAC7B,QAAM,iBAAqD,OAAO,QAAQ,UAAU;AAClF,cAAU;AACV,mBAAe;AACf,sBAAkB;AAClB,UAAM,YAAY;AAClB,WAAQ,MAAM,mBAAmB,QAAQ,KAAK,KAAM,CAAC;AAAA,EACvD;AACA,MAAI,aAAa;AAEjB,WAAS,oBAA0B;AACjC,QAAI,oBAAoB,aAAa,eAAe;AACpD,QAAI,oBAAoB,oBAAoB,sBAAsB;AAClE,QAAI,oBAAoB,cAAc,gBAAgB;AACtD,QAAI,oBAAoB,iBAAiB,mBAAmB;AAC5D,QAAI,oBAAoB,sBAAsB,wBAAwB;AACtE,QAAI,IAAI,eAAe,eAAgB,KAAI,aAAa;AAAA,EAC1D;AAEA,QAAM,SAAS,YAAY;AACzB,QAAI;AACF,UAAI,CAAC,QAAQ,KAAK;AAGhB,cAAM,IAAI,QAAQ;AAAA,MACpB;AAEA,UAAI,CAAC,iBAAiB;AACpB,cAAM,WAAW,aAAa,CAAC;AAAA,MACjC;AAAA,IACF,SAAS,OAAO;AACd,YAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,YAAM,OAAO,WAAW,KAAK,OAAO,IAChC,gBAAgB,qBAChB,gBAAgB;AACpB,kBAAY,MAAM,KAAK;AACvB,YAAM;AAAA,IACR;AAAA,EACF,GAAG;AAEH,MAAI,iBAA6B,MAAM;AAEvC,WAAS,aAAmB;AAC1B,QAAI,QAAS;AACb,cAAU;AACV,mBAAe;AACf,sBAAkB;AAClB,SAAK,YAAY;AACjB,QAAI,CAAC,QAAQ,IAAK,MAAK,IAAI,MAAM,EAAE,MAAM,MAAM,MAAS;AAAA,EAC1D;AAEA,mBACE,QAAQ,aAAa,UAAU,MAC9B,OAAO,WAAW,eACd,MAAM;AACL,UAAM,IAAI,MAAM,WAAW;AAC3B,WAAO,iBAAiB,gBAAgB,CAAC;AACzC,WAAO,MAAM,OAAO,oBAAoB,gBAAgB,CAAC;AAAA,EAC3D,GAAG,IACH,MAAM;AAEZ,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,MAAM;AAAA,EACR;AACF;","names":[]}
|
|
@@ -1 +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
|
|
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 McpView | 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;AACpE;","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
publishMcpView
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-5L3QHYWK.js";
|
|
4
4
|
|
|
5
5
|
// src/react/context.ts
|
|
6
6
|
import { createContext } from "react";
|
|
@@ -116,4 +116,4 @@ export {
|
|
|
116
116
|
useAutoResize,
|
|
117
117
|
createMcpView
|
|
118
118
|
};
|
|
119
|
-
//# sourceMappingURL=chunk-
|
|
119
|
+
//# sourceMappingURL=chunk-SXF23BZD.js.map
|
package/dist/create.d.ts
CHANGED
package/dist/create.js
CHANGED
|
@@ -14,7 +14,12 @@ import { fileURLToPath } from "url";
|
|
|
14
14
|
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
15
|
var PACKAGE_ROOT = resolve(__dirname, "..");
|
|
16
16
|
var TEMPLATES = resolve(PACKAGE_ROOT, "templates");
|
|
17
|
-
|
|
17
|
+
function ownVersion() {
|
|
18
|
+
const pkg = JSON.parse(readFileSync(join(PACKAGE_ROOT, "package.json"), "utf8"));
|
|
19
|
+
if (!pkg.version) throw new Error("mountly-mcp: cannot read own version from package.json");
|
|
20
|
+
return pkg.version;
|
|
21
|
+
}
|
|
22
|
+
var SUPPORTED = ["react", "vue", "svelte", "vanilla"];
|
|
18
23
|
function parseCreateArgs(argv) {
|
|
19
24
|
const name = argv[0];
|
|
20
25
|
if (!name || name.startsWith("-")) {
|
|
@@ -82,7 +87,8 @@ async function createProject(args) {
|
|
|
82
87
|
name: args.name,
|
|
83
88
|
slug,
|
|
84
89
|
uri: `ui://${slug}/dashboard`,
|
|
85
|
-
framework: args.framework
|
|
90
|
+
framework: args.framework,
|
|
91
|
+
mcpVersion: ownVersion()
|
|
86
92
|
});
|
|
87
93
|
process.stdout.write(
|
|
88
94
|
[
|
package/dist/create.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/create.ts"],"sourcesContent":["import {\n cpSync,\n existsSync,\n mkdirSync,\n readFileSync,\n readdirSync,\n statSync,\n unlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n/** Package root: dist/cli.js → .. ; src/cli.ts during tests → .. when run via dist. */\nconst PACKAGE_ROOT = resolve(__dirname, \"..\");\nconst TEMPLATES = resolve(PACKAGE_ROOT, \"templates\");\n\nconst SUPPORTED = [\"react\", \"vue\", \"svelte\"] as const;\ntype Framework = (typeof SUPPORTED)[number];\n\nexport interface CreateArgs {\n name: string;\n framework: Framework;\n dir: string;\n}\n\nexport function parseCreateArgs(argv: ReadonlyArray<string>): CreateArgs {\n const name = argv[0];\n if (!name || name.startsWith(\"-\")) {\n throw new Error(\n \"mountly-mcp create requires a project name. e.g. mountly-mcp create my-app --framework react\",\n );\n }\n\n let framework: Framework = \"react\";\n let dir = `./${name}`;\n\n for (let i = 1; i < argv.length; i += 1) {\n const arg = argv[i];\n if (arg === \"--framework\" || arg === \"-f\") {\n const value = argv[++i];\n if (value === undefined) throw new Error(`mountly-mcp: ${arg} needs a value`);\n if (!SUPPORTED.includes(value as Framework)) {\n throw new Error(\n `mountly-mcp: framework \"${value}\" not supported. Available: ${SUPPORTED.join(\", \")}`,\n );\n }\n framework = value as Framework;\n } else if (arg === \"--dir\") {\n const value = argv[++i];\n if (value === undefined) throw new Error(\"mountly-mcp: --dir needs a value\");\n dir = value;\n } else {\n throw new Error(`mountly-mcp: unknown create option '${arg}'`);\n }\n }\n\n return { name, framework, dir };\n}\n\nfunction slugify(name: string): string {\n return name\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n}\n\nfunction renderTemplates(dir: string, ctx: Record<string, string>): void {\n for (const entry of readdirSync(dir)) {\n const full = join(dir, entry);\n if (statSync(full).isDirectory()) {\n renderTemplates(full, ctx);\n continue;\n }\n if (!entry.endsWith(\".tmpl\")) continue;\n let content = readFileSync(full, \"utf8\");\n for (const [key, value] of Object.entries(ctx)) {\n content = content.split(`{{${key}}}`).join(value);\n }\n writeFileSync(full.replace(/\\.tmpl$/, \"\"), content);\n unlinkSync(full);\n }\n}\n\nexport async function createProject(args: CreateArgs): Promise<void> {\n const target = resolve(args.dir);\n if (existsSync(target)) {\n throw new Error(`mountly-mcp: target directory already exists: ${target}`);\n }\n\n const tplDir = join(TEMPLATES, args.framework);\n if (!existsSync(tplDir)) {\n throw new Error(\n `mountly-mcp: template for framework \"${args.framework}\" is missing at ${tplDir}`,\n );\n }\n\n mkdirSync(target, { recursive: true });\n cpSync(tplDir, target, { recursive: true });\n\n const slug = slugify(args.name) || \"app\";\n renderTemplates(target, {\n name: args.name,\n slug,\n uri: `ui://${slug}/dashboard`,\n framework: args.framework,\n });\n\n process.stdout.write(\n [\n \"\",\n `✓ Created MCP App ${args.name} (${args.framework}) at ${target}`,\n \"\",\n \"Next steps:\",\n ` cd ${args.dir}`,\n \" pnpm install\",\n \" pnpm dev # sandboxed host + live reload\",\n \" pnpm verify # conformance check\",\n \"\",\n \"Do not clone the Mountly monorepo for greenfield apps — this scaffold is the canonical path.\",\n \"\",\n ].join(\"\\n\"),\n );\n}\n"],"mappings":";AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,SAAS,MAAM,eAAe;AACvC,SAAS,qBAAqB;AAE9B,IAAM,YAAY,QAAQ,cAAc,YAAY,GAAG,CAAC;AAExD,IAAM,eAAe,QAAQ,WAAW,IAAI;AAC5C,IAAM,YAAY,QAAQ,cAAc,WAAW;
|
|
1
|
+
{"version":3,"sources":["../src/create.ts"],"sourcesContent":["import {\n cpSync,\n existsSync,\n mkdirSync,\n readFileSync,\n readdirSync,\n statSync,\n unlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n/** Package root: dist/cli.js → .. ; src/cli.ts during tests → .. when run via dist. */\nconst PACKAGE_ROOT = resolve(__dirname, \"..\");\nconst TEMPLATES = resolve(PACKAGE_ROOT, \"templates\");\n\n/**\n * The version of mountly-mcp doing the scaffolding, so the generated\n * package.json pins the API this CLI actually ships.\n *\n * Hardcoding the pin in the templates meant a major release left them behind:\n * `^3.1.0` cannot resolve to 4.0.0, so every scaffolded app installed a\n * package without the exports its own vite.config imported.\n */\nfunction ownVersion(): string {\n const pkg = JSON.parse(readFileSync(join(PACKAGE_ROOT, \"package.json\"), \"utf8\")) as {\n version?: string;\n };\n if (!pkg.version) throw new Error(\"mountly-mcp: cannot read own version from package.json\");\n return pkg.version;\n}\n\nconst SUPPORTED = [\"react\", \"vue\", \"svelte\", \"vanilla\"] as const;\ntype Framework = (typeof SUPPORTED)[number];\n\nexport interface CreateArgs {\n name: string;\n framework: Framework;\n dir: string;\n}\n\nexport function parseCreateArgs(argv: ReadonlyArray<string>): CreateArgs {\n const name = argv[0];\n if (!name || name.startsWith(\"-\")) {\n throw new Error(\n \"mountly-mcp create requires a project name. e.g. mountly-mcp create my-app --framework react\",\n );\n }\n\n let framework: Framework = \"react\";\n let dir = `./${name}`;\n\n for (let i = 1; i < argv.length; i += 1) {\n const arg = argv[i];\n if (arg === \"--framework\" || arg === \"-f\") {\n const value = argv[++i];\n if (value === undefined) throw new Error(`mountly-mcp: ${arg} needs a value`);\n if (!SUPPORTED.includes(value as Framework)) {\n throw new Error(\n `mountly-mcp: framework \"${value}\" not supported. Available: ${SUPPORTED.join(\", \")}`,\n );\n }\n framework = value as Framework;\n } else if (arg === \"--dir\") {\n const value = argv[++i];\n if (value === undefined) throw new Error(\"mountly-mcp: --dir needs a value\");\n dir = value;\n } else {\n throw new Error(`mountly-mcp: unknown create option '${arg}'`);\n }\n }\n\n return { name, framework, dir };\n}\n\nfunction slugify(name: string): string {\n return name\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n}\n\nfunction renderTemplates(dir: string, ctx: Record<string, string>): void {\n for (const entry of readdirSync(dir)) {\n const full = join(dir, entry);\n if (statSync(full).isDirectory()) {\n renderTemplates(full, ctx);\n continue;\n }\n if (!entry.endsWith(\".tmpl\")) continue;\n let content = readFileSync(full, \"utf8\");\n for (const [key, value] of Object.entries(ctx)) {\n content = content.split(`{{${key}}}`).join(value);\n }\n writeFileSync(full.replace(/\\.tmpl$/, \"\"), content);\n unlinkSync(full);\n }\n}\n\nexport async function createProject(args: CreateArgs): Promise<void> {\n const target = resolve(args.dir);\n if (existsSync(target)) {\n throw new Error(`mountly-mcp: target directory already exists: ${target}`);\n }\n\n const tplDir = join(TEMPLATES, args.framework);\n if (!existsSync(tplDir)) {\n throw new Error(\n `mountly-mcp: template for framework \"${args.framework}\" is missing at ${tplDir}`,\n );\n }\n\n mkdirSync(target, { recursive: true });\n cpSync(tplDir, target, { recursive: true });\n\n const slug = slugify(args.name) || \"app\";\n renderTemplates(target, {\n name: args.name,\n slug,\n uri: `ui://${slug}/dashboard`,\n framework: args.framework,\n mcpVersion: ownVersion(),\n });\n\n process.stdout.write(\n [\n \"\",\n `✓ Created MCP App ${args.name} (${args.framework}) at ${target}`,\n \"\",\n \"Next steps:\",\n ` cd ${args.dir}`,\n \" pnpm install\",\n \" pnpm dev # sandboxed host + live reload\",\n \" pnpm verify # conformance check\",\n \"\",\n \"Do not clone the Mountly monorepo for greenfield apps — this scaffold is the canonical path.\",\n \"\",\n ].join(\"\\n\"),\n );\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.js
CHANGED
|
@@ -5,12 +5,12 @@ import {
|
|
|
5
5
|
MCP_VIEW_GLOBAL_KEY,
|
|
6
6
|
getPublishedMcpView,
|
|
7
7
|
publishMcpView
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-5L3QHYWK.js";
|
|
9
9
|
import {
|
|
10
10
|
App,
|
|
11
11
|
PostMessageTransport,
|
|
12
12
|
runBridge
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-2HNXL5A4.js";
|
|
14
14
|
import {
|
|
15
15
|
EXTENSION_ID,
|
|
16
16
|
LATEST_PROTOCOL_VERSION,
|
package/dist/react/index.js
CHANGED
|
@@ -14,8 +14,8 @@ import {
|
|
|
14
14
|
useToolInputPartial,
|
|
15
15
|
useToolResult,
|
|
16
16
|
useUpdateModelContext
|
|
17
|
-
} from "../chunk-
|
|
18
|
-
import "../chunk-
|
|
17
|
+
} from "../chunk-SXF23BZD.js";
|
|
18
|
+
import "../chunk-5L3QHYWK.js";
|
|
19
19
|
export {
|
|
20
20
|
McpContext,
|
|
21
21
|
createMcpView,
|
package/dist/svelte/index.js
CHANGED
package/dist/vue/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mountly-mcp",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.2",
|
|
4
4
|
"description": "Build MCP Apps (SEP-1865) views from React, Vue or Svelte components — new or existing. One build emits the ui:// resource, its sidecar, and the MCP server wiring.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"chatgpt",
|
|
@@ -8,13 +8,13 @@
|
|
|
8
8
|
"mcp",
|
|
9
9
|
"mcp-apps",
|
|
10
10
|
"mcp-ui",
|
|
11
|
+
"mcp-view",
|
|
11
12
|
"model-context-protocol",
|
|
12
13
|
"react",
|
|
13
14
|
"sep-1865",
|
|
14
15
|
"svelte",
|
|
15
16
|
"vite-plugin",
|
|
16
|
-
"vue"
|
|
17
|
-
"mcp-view"
|
|
17
|
+
"vue"
|
|
18
18
|
],
|
|
19
19
|
"license": "Apache-2.0",
|
|
20
20
|
"repository": {
|
|
@@ -107,8 +107,8 @@
|
|
|
107
107
|
"dependencies": {
|
|
108
108
|
"@modelcontextprotocol/ext-apps": "^1.7.5",
|
|
109
109
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
110
|
-
"mountly": "^0.
|
|
111
|
-
"mountly-react": "^2.0
|
|
110
|
+
"mountly": "^1.0.0",
|
|
111
|
+
"mountly-react": "^2.1.0",
|
|
112
112
|
"zod": "^4.4.3"
|
|
113
113
|
},
|
|
114
114
|
"devDependencies": {
|
|
@@ -125,16 +125,16 @@
|
|
|
125
125
|
"tsup": "^8.5.1",
|
|
126
126
|
"typescript": "^7.0.2",
|
|
127
127
|
"vite": "^8.1.5",
|
|
128
|
-
"mountly-svelte": "^2.0
|
|
129
|
-
"mountly-vue": "^2.0
|
|
128
|
+
"mountly-svelte": "^2.1.0",
|
|
129
|
+
"mountly-vue": "^2.1.0"
|
|
130
130
|
},
|
|
131
131
|
"peerDependencies": {
|
|
132
132
|
"@json-render/core": "^0.20.0",
|
|
133
133
|
"@json-render/react": "^0.20.0",
|
|
134
134
|
"ai": "^7.0.65",
|
|
135
135
|
"axe-core": ">=4.10.0",
|
|
136
|
-
"mountly-svelte": "^2.0
|
|
137
|
-
"mountly-vue": "^2.0
|
|
136
|
+
"mountly-svelte": "^2.1.0",
|
|
137
|
+
"mountly-vue": "^2.1.0",
|
|
138
138
|
"playwright": ">=1.62.1",
|
|
139
139
|
"react": "^19",
|
|
140
140
|
"react-dom": "^19",
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# {{name}}
|
|
2
|
+
|
|
3
|
+
MCP App scaffolded with `mountly-mcp create`.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pnpm install
|
|
7
|
+
pnpm dev # sandboxed host on :5179
|
|
8
|
+
pnpm verify # conformance
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
- View: `src/view.ts` (`publishMcpView`)
|
|
12
|
+
- Server: `server.mjs` (`registerMcpApps`)
|
|
13
|
+
- Fixtures: `mcp.fixtures.json` (tool args when using `--server`)
|
|
14
|
+
|
|
15
|
+
No framework, no adapter: a View is just `mount` / `update` / `unmount`, so
|
|
16
|
+
`publishMcpView` takes the object directly. The bridge drives it exactly as it
|
|
17
|
+
drives a React, Vue or Svelte View. Add a framework later by swapping
|
|
18
|
+
`publishMcpView(...)` for that framework's `createMcpView(Component)`.
|
|
19
|
+
|
|
20
|
+
Do not clone the Mountly monorepo for greenfield apps — this project is the canonical path.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{name}}",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "vite build",
|
|
8
|
+
"dev": "mountly-mcp dev --server ./server.mjs",
|
|
9
|
+
"verify": "mountly-mcp verify",
|
|
10
|
+
"verify:strict": "mountly-mcp verify --strict",
|
|
11
|
+
"typecheck": "tsc --noEmit"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
15
|
+
"mountly-mcp": "^{{mcpVersion}}",
|
|
16
|
+
"zod": "^4.4.3"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"typescript": "^5.9.0",
|
|
20
|
+
"vite": "^8.2.1"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { readMcpAppManifest } from "mountly-mcp/artifact";
|
|
3
|
+
import { registerMcpApps } from "mountly-mcp/server";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
|
|
6
|
+
const URI = "{{uri}}";
|
|
7
|
+
|
|
8
|
+
function sampleCards() {
|
|
9
|
+
return [
|
|
10
|
+
{ label: "Requests", value: Math.floor(1200 + Math.random() * 200) },
|
|
11
|
+
{ label: "Latency", value: `${Math.floor(30 + Math.random() * 20)} ms` },
|
|
12
|
+
{ label: "Uptime", value: "99.9%" },
|
|
13
|
+
];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Default export for `mountly-mcp dev --server ./server.mjs`. */
|
|
17
|
+
export default async function createServer() {
|
|
18
|
+
const server = new McpServer(
|
|
19
|
+
{ name: "{{slug}}", version: "0.0.1" },
|
|
20
|
+
{ capabilities: { tools: {}, resources: {} } },
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const { artifacts } = await readMcpAppManifest("dist/mountly-mcp.manifest.json");
|
|
24
|
+
|
|
25
|
+
await registerMcpApps(server, {
|
|
26
|
+
views: artifacts.map((artifact) => ({ artifact })),
|
|
27
|
+
tools: [
|
|
28
|
+
{
|
|
29
|
+
name: "showDashboard",
|
|
30
|
+
resourceUri: URI,
|
|
31
|
+
config: {
|
|
32
|
+
description: "Show an interactive metrics dashboard",
|
|
33
|
+
inputSchema: {
|
|
34
|
+
topic: z.string().describe("Dashboard topic"),
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
handler: async ({ topic }) => ({
|
|
38
|
+
structuredContent: {
|
|
39
|
+
topic,
|
|
40
|
+
cards: [
|
|
41
|
+
{ label: "Requests", value: 1284 },
|
|
42
|
+
{ label: "Latency", value: "42 ms" },
|
|
43
|
+
{ label: "Uptime", value: "99.9%" },
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
}),
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: "refreshDashboard",
|
|
50
|
+
resourceUri: URI,
|
|
51
|
+
visibility: ["app"],
|
|
52
|
+
config: {
|
|
53
|
+
description: "Refresh dashboard metrics (app-only)",
|
|
54
|
+
inputSchema: {
|
|
55
|
+
reason: z.string().optional(),
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
handler: async ({ reason }) => ({
|
|
59
|
+
structuredContent: {
|
|
60
|
+
refreshedAt: new Date().toISOString(),
|
|
61
|
+
cards: sampleCards(),
|
|
62
|
+
reason: reason ?? null,
|
|
63
|
+
},
|
|
64
|
+
}),
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
return server;
|
|
70
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { publishMcpView, type McpViewProps } from "mountly-mcp";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* No framework, no adapter. A View is just `mount` / `update` / `unmount`, so
|
|
5
|
+
* `publishMcpView` takes this object directly — the bridge drives it exactly
|
|
6
|
+
* as it drives a React, Vue or Svelte View.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
interface DashboardCard {
|
|
10
|
+
label: string;
|
|
11
|
+
value: string | number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface DashboardData {
|
|
15
|
+
topic?: string;
|
|
16
|
+
refreshedAt?: string;
|
|
17
|
+
cards?: DashboardCard[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface DashboardResult {
|
|
21
|
+
structuredContent?: DashboardData;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const STYLES = `
|
|
25
|
+
:host, :root { color-scheme: light dark; }
|
|
26
|
+
.card {
|
|
27
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
28
|
+
border: 1px solid var(--color-border-primary, #e5e5e5);
|
|
29
|
+
border-radius: 12px;
|
|
30
|
+
padding: 20px;
|
|
31
|
+
background: var(--color-background-secondary, #fafafa);
|
|
32
|
+
color: var(--color-text-primary, #171717);
|
|
33
|
+
}
|
|
34
|
+
.eyebrow {
|
|
35
|
+
margin: 0 0 8px;
|
|
36
|
+
color: var(--color-text-secondary, #737373);
|
|
37
|
+
font-size: 11px;
|
|
38
|
+
text-transform: uppercase;
|
|
39
|
+
letter-spacing: .1em;
|
|
40
|
+
}
|
|
41
|
+
h1 { margin: 0 0 16px; font-size: 20px; font-weight: 600; }
|
|
42
|
+
.grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-bottom: 16px; }
|
|
43
|
+
.metric {
|
|
44
|
+
border: 1px solid var(--color-border-primary, #e5e5e5);
|
|
45
|
+
border-radius: 8px;
|
|
46
|
+
padding: 12px;
|
|
47
|
+
background: var(--color-background-primary, #fff);
|
|
48
|
+
}
|
|
49
|
+
.label {
|
|
50
|
+
color: var(--color-text-secondary, #737373);
|
|
51
|
+
font-size: 11px;
|
|
52
|
+
text-transform: uppercase;
|
|
53
|
+
}
|
|
54
|
+
.value { margin-top: 4px; font-size: 22px; font-weight: 600; }
|
|
55
|
+
.row { display: flex; align-items: center; gap: 10px; }
|
|
56
|
+
button {
|
|
57
|
+
border: 1px solid var(--color-border-primary, #e5e5e5);
|
|
58
|
+
border-radius: 8px;
|
|
59
|
+
padding: 8px 14px;
|
|
60
|
+
background: var(--color-background-primary, #fff);
|
|
61
|
+
color: inherit;
|
|
62
|
+
cursor: pointer;
|
|
63
|
+
font: inherit;
|
|
64
|
+
font-size: 13px;
|
|
65
|
+
}
|
|
66
|
+
button:disabled { opacity: .6; cursor: wait; }
|
|
67
|
+
.status { font-size: 12px; color: var(--color-text-secondary, #737373); }
|
|
68
|
+
`;
|
|
69
|
+
|
|
70
|
+
/** Per-container state, so several instances on one page stay independent. */
|
|
71
|
+
const instances = new WeakMap<Element, { data?: DashboardData; status: string; busy: boolean }>();
|
|
72
|
+
|
|
73
|
+
function el(tag: string, className?: string, text?: string): HTMLElement {
|
|
74
|
+
const node = document.createElement(tag);
|
|
75
|
+
if (className) node.className = className;
|
|
76
|
+
// textContent, never innerHTML: tool results are data, not markup.
|
|
77
|
+
if (text !== undefined) node.textContent = text;
|
|
78
|
+
return node;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function render(container: Element, props: McpViewProps): void {
|
|
82
|
+
const state = instances.get(container);
|
|
83
|
+
if (!state) return;
|
|
84
|
+
|
|
85
|
+
const data = state.data;
|
|
86
|
+
const cards = data?.cards ?? [];
|
|
87
|
+
|
|
88
|
+
container.replaceChildren();
|
|
89
|
+
|
|
90
|
+
const style = document.createElement("style");
|
|
91
|
+
style.textContent = STYLES;
|
|
92
|
+
|
|
93
|
+
const card = el("div", "card");
|
|
94
|
+
card.append(el("p", "eyebrow", "MCP App · Mountly"));
|
|
95
|
+
card.append(el("h1", undefined, data?.topic ? `${data.topic} Dashboard` : "Dashboard"));
|
|
96
|
+
|
|
97
|
+
const grid = el("div", "grid");
|
|
98
|
+
const shown: DashboardCard[] = cards.length > 0 ? cards : [
|
|
99
|
+
{ label: "—", value: "—" },
|
|
100
|
+
{ label: "—", value: "—" },
|
|
101
|
+
{ label: "—", value: "—" },
|
|
102
|
+
];
|
|
103
|
+
for (const c of shown) {
|
|
104
|
+
const metric = el("div", "metric");
|
|
105
|
+
metric.append(el("div", "label", c.label));
|
|
106
|
+
metric.append(el("div", "value", String(c.value)));
|
|
107
|
+
grid.append(metric);
|
|
108
|
+
}
|
|
109
|
+
card.append(grid);
|
|
110
|
+
|
|
111
|
+
const row = el("div", "row");
|
|
112
|
+
const button = el("button") as HTMLButtonElement;
|
|
113
|
+
button.type = "button";
|
|
114
|
+
button.textContent = "Refresh data";
|
|
115
|
+
button.disabled = state.busy;
|
|
116
|
+
button.addEventListener("click", () => void refresh(container, props));
|
|
117
|
+
const status = el("span", "status", state.status);
|
|
118
|
+
row.append(button, status);
|
|
119
|
+
card.append(row);
|
|
120
|
+
|
|
121
|
+
container.append(style, card);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function refresh(container: Element, props: McpViewProps): Promise<void> {
|
|
125
|
+
const state = instances.get(container);
|
|
126
|
+
if (!state || state.busy) return;
|
|
127
|
+
state.busy = true;
|
|
128
|
+
state.status = "Refreshing…";
|
|
129
|
+
render(container, props);
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
const next = (await props.mcp.callServerTool({
|
|
133
|
+
name: "refreshDashboard",
|
|
134
|
+
arguments: { reason: "User clicked refresh" },
|
|
135
|
+
})) as DashboardResult;
|
|
136
|
+
const content = next.structuredContent;
|
|
137
|
+
if (content) {
|
|
138
|
+
state.data = { ...state.data, ...content, topic: content.topic ?? state.data?.topic };
|
|
139
|
+
}
|
|
140
|
+
state.status = content?.refreshedAt
|
|
141
|
+
? `Refreshed ${new Date(content.refreshedAt).toLocaleTimeString()}`
|
|
142
|
+
: "Refreshed.";
|
|
143
|
+
} catch (error) {
|
|
144
|
+
state.status = error instanceof Error ? error.message : "Refresh failed.";
|
|
145
|
+
} finally {
|
|
146
|
+
state.busy = false;
|
|
147
|
+
render(container, props);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
publishMcpView({
|
|
152
|
+
mount(container, props) {
|
|
153
|
+
const typed = props as McpViewProps;
|
|
154
|
+
instances.set(container, {
|
|
155
|
+
data: (typed.toolResult as DashboardResult | undefined)?.structuredContent,
|
|
156
|
+
status: "Connected.",
|
|
157
|
+
busy: false,
|
|
158
|
+
});
|
|
159
|
+
render(container, typed);
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
// The bridge calls this on every new tool-result or host-context change.
|
|
163
|
+
// Updating in place keeps focus and scroll, which a remount would lose.
|
|
164
|
+
update(container, props) {
|
|
165
|
+
const typed = props as McpViewProps;
|
|
166
|
+
const state = instances.get(container);
|
|
167
|
+
if (!state) return;
|
|
168
|
+
const incoming = (typed.toolResult as DashboardResult | undefined)?.structuredContent;
|
|
169
|
+
if (incoming) state.data = incoming;
|
|
170
|
+
render(container, typed);
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
unmount(container) {
|
|
174
|
+
instances.delete(container);
|
|
175
|
+
container.replaceChildren();
|
|
176
|
+
},
|
|
177
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"noEmit": true,
|
|
10
|
+
"isolatedModules": true,
|
|
11
|
+
"resolveJsonModule": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["src/**/*.ts"]
|
|
14
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { defineConfig } from "vite";
|
|
2
|
+
import { mountlyMcpViews } from "mountly-mcp/vite";
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
plugins: [
|
|
6
|
+
mountlyMcpViews({
|
|
7
|
+
apps: [
|
|
8
|
+
{
|
|
9
|
+
entry: "src/view.ts",
|
|
10
|
+
uri: "{{uri}}",
|
|
11
|
+
name: "dashboard",
|
|
12
|
+
description: "Interactive metrics dashboard",
|
|
13
|
+
displayModes: ["inline", "fullscreen"],
|
|
14
|
+
prefersBorder: true,
|
|
15
|
+
awaitToolResult: true,
|
|
16
|
+
},
|
|
17
|
+
],
|
|
18
|
+
}),
|
|
19
|
+
],
|
|
20
|
+
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/bridge/index.ts"],"sourcesContent":["/**\n * View-side bridge that turns an MCP Apps `App` into a mountly view module\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 type McpUiDisplayMode,\n type McpUiToolInputNotification,\n type McpUiToolInputPartialNotification,\n type McpUiToolResultNotification,\n} from \"@modelcontextprotocol/ext-apps\";\n\nimport { MCP_ERROR_CODES } from \"../schema.js\";\nimport type { McpView, McpViewProps } from \"../types.js\";\n\nexport interface RunBridgeOptions {\n /** Pre-constructed App (used by jsdom tests); otherwise one is created here. */\n app?: App;\n view: McpView;\n container: Element;\n /**\n * If true (default), mount is deferred until the first tool-result arrives;\n * the View 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 /**\n * Render progressive states from `ui/notifications/tool-input-partial`.\n *\n * Off by default — the spec lets views ignore partials, and they carry\n * best-effort recovered JSON that MUST NOT drive critical operations. When\n * on, partials arrive as `toolInputPartial` (mounting the View early if\n * needed) and are cleared once the complete `tool-input` lands.\n */\n streamToolInput?: boolean;\n /** Display modes this view supports. Declared to the host during `ui/initialize`. */\n availableDisplayModes?: ReadonlyArray<McpUiDisplayMode>;\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\n/**\n * `data-mountly-mcp-state` is the View's machine-readable outcome, read by\n * `verify --render`. It only ever moves forward — `mounted` once the View\n * has mounted, `error` if the boundary replaced it — so a state written here\n * can never be clobbered by a later lifecycle step.\n */\nfunction renderErrorBoundary(container: Element, code: string, message?: string): void {\n container.setAttribute(\"data-mountly-mcp-state\", \"error\");\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 view,\n container,\n awaitToolResult = true,\n streamToolInput = false,\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: McpUiToolInputNotification[\"params\"] | undefined;\n let toolInputPartial: McpUiToolInputPartialNotification[\"params\"] | undefined;\n let toolResult: McpUiToolResultNotification[\"params\"] | undefined;\n let mounted = false;\n let stopped = false;\n let pending: Promise<void> = Promise.resolve();\n\n function currentProps(): McpViewProps {\n return {\n mcp: app,\n toolInput,\n toolInputPartial,\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 void app.sendLog({ level: \"error\", data: { code, message } }).catch(() => undefined);\n } catch {\n // ignore — log channel may not be ready\n }\n renderErrorBoundary(container, code, message);\n }\n\n function renderWith(props: McpViewProps): Promise<void> {\n pending = pending.then(async () => {\n if (stopped) return;\n try {\n if (!mounted) {\n const r = view.mount(container, props);\n if (r instanceof Promise) await r;\n mounted = true;\n container.setAttribute(\"data-mountly-mcp-state\", \"mounted\");\n } else if (view.update) {\n const r = view.update(container, props);\n if (r instanceof Promise) await r;\n } else {\n await view.unmount(container);\n mounted = false;\n const r = view.mount(container, props);\n if (r instanceof Promise) await r;\n mounted = true;\n }\n } catch (error) {\n notifyError(MCP_ERROR_CODES.VIEW_MOUNT_THREW, error);\n }\n });\n return pending;\n }\n\n async function unmountView(): Promise<void> {\n await pending;\n if (!mounted) return;\n mounted = false;\n await view.unmount(container);\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 const handleToolInput = (params: McpUiToolInputNotification[\"params\"]) => {\n toolInput = params;\n toolInputPartial = undefined;\n if (!awaitToolResult) return;\n if (mounted || toolResult !== undefined) void renderWith(currentProps());\n };\n\n const handleToolInputPartial = (params: McpUiToolInputPartialNotification[\"params\"]) => {\n // Views MAY ignore partial input per spec; opt in with `streamToolInput`.\n // Consumers wanting finer control can subscribe via app.addEventListener.\n if (!streamToolInput) return;\n toolInputPartial = params;\n void renderWith(currentProps());\n };\n\n const handleToolResult = (params: McpUiToolResultNotification[\"params\"]) => {\n toolResult = params;\n void renderWith(currentProps());\n };\n\n const handleToolCancelled = () => {\n void renderWith(currentProps());\n };\n\n const handleHostContextChanged = () => {\n if (mounted) void renderWith(currentProps());\n };\n\n app.addEventListener(\"toolinput\", handleToolInput);\n app.addEventListener(\"toolinputpartial\", handleToolInputPartial);\n app.addEventListener(\"toolresult\", handleToolResult);\n app.addEventListener(\"toolcancelled\", handleToolCancelled);\n app.addEventListener(\"hostcontextchanged\", handleHostContextChanged);\n\n const previousTeardown = app.onteardown;\n const bridgeTeardown: NonNullable<typeof app.onteardown> = async (params, extra) => {\n stopped = true;\n detachTeardown();\n detachAppHandlers();\n await unmountView();\n return (await previousTeardown?.(params, extra)) ?? {};\n };\n app.onteardown = bridgeTeardown;\n\n function detachAppHandlers(): void {\n app.removeEventListener(\"toolinput\", handleToolInput);\n app.removeEventListener(\"toolinputpartial\", handleToolInputPartial);\n app.removeEventListener(\"toolresult\", handleToolResult);\n app.removeEventListener(\"toolcancelled\", handleToolCancelled);\n app.removeEventListener(\"hostcontextchanged\", handleHostContextChanged);\n if (app.onteardown === bridgeTeardown) app.onteardown = previousTeardown;\n }\n\n const ready = (async () => {\n try {\n if (!options.app) {\n // App.connect() defaults to PostMessageTransport(window.parent, window.parent)\n // and owns automatic resize setup.\n await app.connect();\n }\n\n if (!awaitToolResult) {\n await renderWith(currentProps());\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const code = /timeout/i.test(message)\n ? MCP_ERROR_CODES.INITIALIZE_TIMEOUT\n : MCP_ERROR_CODES.INITIALIZE_FAILED;\n notifyError(code, error);\n throw error;\n }\n })();\n\n let detachTeardown: () => void = () => undefined;\n\n function stopBridge(): void {\n if (stopped) return;\n stopped = true;\n detachTeardown();\n detachAppHandlers();\n void unmountView();\n if (!options.app) void app.close().catch(() => undefined);\n }\n\n detachTeardown =\n options.onTeardown?.(stopBridge) ??\n (typeof window !== \"undefined\"\n ? (() => {\n const h = () => stopBridge();\n window.addEventListener(\"beforeunload\", h);\n return () => window.removeEventListener(\"beforeunload\", h);\n })()\n : () => undefined);\n\n return {\n ready,\n app,\n stop: stopBridge,\n };\n}\n\nexport { App, PostMessageTransport };\nexport type {\n McpUiAppCapabilities,\n McpUiHostCapabilities,\n McpUiHostContext,\n McpUiToolInputNotification,\n McpUiToolInputPartialNotification,\n McpUiToolResultNotification,\n McpUiToolCancelledNotification,\n McpUiHostContextChangedNotification,\n} from \"@modelcontextprotocol/ext-apps\";\n"],"mappings":"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","names":[]}
|
|
File without changes
|