mountly-mcp 4.0.0 → 4.0.1

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 CHANGED
@@ -23,6 +23,11 @@ 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
33
  | Skill | Try it |
package/dist/create.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- declare const SUPPORTED: readonly ["react", "vue", "svelte"];
1
+ declare const SUPPORTED: readonly ["react", "vue", "svelte", "vanilla"];
2
2
  type Framework = (typeof SUPPORTED)[number];
3
3
  export interface CreateArgs {
4
4
  name: string;
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
- var SUPPORTED = ["react", "vue", "svelte"];
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
  [
@@ -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":"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","names":[]}
1
+ {"version":3,"sources":["../src/create.ts"],"sourcesContent":["import {\n cpSync,\n existsSync,\n mkdirSync,\n readFileSync,\n readdirSync,\n statSync,\n unlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mountly-mcp",
3
- "version": "4.0.0",
3
+ "version": "4.0.1",
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",
@@ -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.4.0",
111
- "mountly-react": "^2.0.2",
110
+ "mountly": "^0.5.0",
111
+ "mountly-react": "^2.0.3",
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.3",
129
- "mountly-vue": "^2.0.3"
128
+ "mountly-svelte": "^2.0.4",
129
+ "mountly-vue": "^2.0.4"
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.3",
137
- "mountly-vue": "^2.0.3",
136
+ "mountly-svelte": "^2.0.4",
137
+ "mountly-vue": "^2.0.4",
138
138
  "playwright": ">=1.62.1",
139
139
  "react": "^19",
140
140
  "react-dom": "^19",
@@ -12,7 +12,7 @@
12
12
  },
13
13
  "dependencies": {
14
14
  "@modelcontextprotocol/sdk": "^1.30.0",
15
- "mountly-mcp": "^3.1.0",
15
+ "mountly-mcp": "^{{mcpVersion}}",
16
16
  "react": "^19.2.8",
17
17
  "react-dom": "^19.2.8",
18
18
  "zod": "^4.4.3"
@@ -12,7 +12,7 @@
12
12
  },
13
13
  "dependencies": {
14
14
  "@modelcontextprotocol/sdk": "^1.30.0",
15
- "mountly-mcp": "^3.1.0",
15
+ "mountly-mcp": "^{{mcpVersion}}",
16
16
  "mountly-svelte": "^2.0.3",
17
17
  "svelte": "^5.56.9",
18
18
  "zod": "^4.4.3"
@@ -0,0 +1,4 @@
1
+ node_modules
2
+ dist
3
+ .DS_Store
4
+ *.log
@@ -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,4 @@
1
+ {
2
+ "server-usage": { "topic": "server usage" },
3
+ "billing": { "topic": "billing" }
4
+ }
@@ -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
+ });
@@ -12,7 +12,7 @@
12
12
  },
13
13
  "dependencies": {
14
14
  "@modelcontextprotocol/sdk": "^1.30.0",
15
- "mountly-mcp": "^3.1.0",
15
+ "mountly-mcp": "^{{mcpVersion}}",
16
16
  "mountly-vue": "^2.0.3",
17
17
  "vue": "^3.5.41",
18
18
  "zod": "^4.4.3"