mountly-mcp 3.1.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.
Files changed (95) hide show
  1. package/README.md +59 -29
  2. package/dist/bridge/iframe-entry.js +24 -16
  3. package/dist/bridge/index.d.ts +5 -5
  4. package/dist/bridge/index.js +2 -2
  5. package/dist/build/index.d.ts +3 -3
  6. package/dist/build/index.js +2 -2
  7. package/dist/{chunk-NLQDVNZP.js → chunk-AEVQKFXX.js} +12 -8
  8. package/dist/chunk-AEVQKFXX.js.map +1 -0
  9. package/dist/{chunk-SNWNF2XI.js → chunk-JVQZGKBL.js} +13 -13
  10. package/dist/chunk-JVQZGKBL.js.map +1 -0
  11. package/dist/chunk-KWVGW5Q7.js +582 -0
  12. package/dist/chunk-KWVGW5Q7.js.map +1 -0
  13. package/dist/{chunk-DQLQOMDM.js → chunk-M4WFJR3C.js} +2 -2
  14. package/dist/{chunk-DQLQOMDM.js.map → chunk-M4WFJR3C.js.map} +1 -1
  15. package/dist/{chunk-4WAKOYMK.js → chunk-MTJDRWGD.js} +2 -2
  16. package/dist/chunk-MTJDRWGD.js.map +1 -0
  17. package/dist/chunk-SADNMGP6.js +16 -0
  18. package/dist/chunk-SADNMGP6.js.map +1 -0
  19. package/dist/cli.js +21 -10
  20. package/dist/cli.js.map +1 -1
  21. package/dist/create.d.ts +10 -0
  22. package/dist/create.js +113 -0
  23. package/dist/create.js.map +1 -0
  24. package/dist/dev/host-entry.js +2 -2
  25. package/dist/dev/index.d.ts +1 -1
  26. package/dist/dev/index.js +1 -1
  27. package/dist/index.d.ts +1 -0
  28. package/dist/index.js +10 -2
  29. package/dist/json-render/{widget.d.ts → generative-view.d.ts} +9 -11
  30. package/dist/json-render/index.d.ts +1 -1
  31. package/dist/json-render/index.js +9 -8
  32. package/dist/json-render/index.js.map +1 -1
  33. package/dist/publish.d.ts +10 -0
  34. package/dist/react/context.d.ts +1 -1
  35. package/dist/react/create-view.d.ts +8 -0
  36. package/dist/react/hooks.d.ts +1 -1
  37. package/dist/react/index.d.ts +1 -1
  38. package/dist/react/index.js +6 -5
  39. package/dist/schema.d.ts +2 -2
  40. package/dist/server/index.d.ts +2 -2
  41. package/dist/server/index.js.map +1 -1
  42. package/dist/svelte/index.d.ts +10 -16
  43. package/dist/svelte/index.js +7 -3
  44. package/dist/svelte/index.js.map +1 -1
  45. package/dist/testing/index.d.ts +1 -1
  46. package/dist/testing/index.js +1 -1
  47. package/dist/testing/index.js.map +1 -1
  48. package/dist/types.d.ts +14 -4
  49. package/dist/vite/index.d.ts +6 -6
  50. package/dist/vite/index.js +7 -7
  51. package/dist/vite/index.js.map +1 -1
  52. package/dist/vue/index.d.ts +12 -17
  53. package/dist/vue/index.js +10 -6
  54. package/dist/vue/index.js.map +1 -1
  55. package/package.json +11 -15
  56. package/templates/react/.gitignore.tmpl +4 -0
  57. package/templates/react/README.md.tmpl +15 -0
  58. package/templates/react/mcp.fixtures.json.tmpl +4 -0
  59. package/templates/react/package.json.tmpl +27 -0
  60. package/templates/react/server.mjs.tmpl +70 -0
  61. package/templates/react/src/view.tsx.tmpl +160 -0
  62. package/templates/react/tsconfig.json.tmpl +15 -0
  63. package/templates/react/vite.config.ts.tmpl +22 -0
  64. package/templates/svelte/.gitignore.tmpl +4 -0
  65. package/templates/svelte/README.md.tmpl +14 -0
  66. package/templates/svelte/mcp.fixtures.json.tmpl +4 -0
  67. package/templates/svelte/package.json.tmpl +26 -0
  68. package/templates/svelte/server.mjs.tmpl +70 -0
  69. package/templates/svelte/src/Dashboard.svelte.tmpl +158 -0
  70. package/templates/svelte/src/view.ts.tmpl +14 -0
  71. package/templates/svelte/tsconfig.json.tmpl +14 -0
  72. package/templates/svelte/vite.config.ts.tmpl +22 -0
  73. package/templates/vanilla/.gitignore.tmpl +4 -0
  74. package/templates/vanilla/README.md.tmpl +20 -0
  75. package/templates/vanilla/mcp.fixtures.json.tmpl +4 -0
  76. package/templates/vanilla/package.json.tmpl +22 -0
  77. package/templates/vanilla/server.mjs.tmpl +70 -0
  78. package/templates/vanilla/src/view.ts.tmpl +177 -0
  79. package/templates/vanilla/tsconfig.json.tmpl +14 -0
  80. package/templates/vanilla/vite.config.ts.tmpl +20 -0
  81. package/templates/vue/.gitignore.tmpl +4 -0
  82. package/templates/vue/README.md.tmpl +14 -0
  83. package/templates/vue/mcp.fixtures.json.tmpl +4 -0
  84. package/templates/vue/package.json.tmpl +26 -0
  85. package/templates/vue/server.mjs.tmpl +70 -0
  86. package/templates/vue/src/Dashboard.vue.tmpl +159 -0
  87. package/templates/vue/src/view.ts.tmpl +14 -0
  88. package/templates/vue/tsconfig.json.tmpl +15 -0
  89. package/templates/vue/vite.config.ts.tmpl +22 -0
  90. package/dist/chunk-4WAKOYMK.js.map +0 -1
  91. package/dist/chunk-KRAS266U.js +0 -260
  92. package/dist/chunk-KRAS266U.js.map +0 -1
  93. package/dist/chunk-NLQDVNZP.js.map +0 -1
  94. package/dist/chunk-SNWNF2XI.js.map +0 -1
  95. package/dist/react/create-widget.d.ts +0 -11
@@ -0,0 +1,26 @@
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": "svelte-check --tsconfig ./tsconfig.json"
12
+ },
13
+ "dependencies": {
14
+ "@modelcontextprotocol/sdk": "^1.30.0",
15
+ "mountly-mcp": "^{{mcpVersion}}",
16
+ "mountly-svelte": "^2.0.3",
17
+ "svelte": "^5.56.9",
18
+ "zod": "^4.4.3"
19
+ },
20
+ "devDependencies": {
21
+ "@sveltejs/vite-plugin-svelte": "^7.3.0",
22
+ "svelte-check": "^4.0.0",
23
+ "typescript": "^5.9.0",
24
+ "vite": "^8.2.1"
25
+ }
26
+ }
@@ -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,158 @@
1
+ <script lang="ts">
2
+ import type { McpViewProps } from "mountly-mcp";
3
+
4
+ interface DashboardCard {
5
+ label: string;
6
+ value: string | number;
7
+ }
8
+
9
+ interface DashboardData {
10
+ topic?: string;
11
+ refreshedAt?: string;
12
+ cards?: DashboardCard[];
13
+ }
14
+
15
+ interface DashboardResult {
16
+ structuredContent?: DashboardData;
17
+ }
18
+
19
+ let { mcp, toolResult }: McpViewProps = $props();
20
+
21
+ let data = $state<DashboardData | undefined>();
22
+ let busy = $state(false);
23
+ let status = $state("Connected.");
24
+
25
+ $effect(() => {
26
+ const next = (toolResult as DashboardResult | undefined)?.structuredContent;
27
+ if (next) data = next;
28
+ });
29
+
30
+ const cards = $derived(data?.cards ?? []);
31
+ const title = $derived(data?.topic ? `${data.topic} Dashboard` : "Dashboard");
32
+
33
+ async function refresh() {
34
+ busy = true;
35
+ status = "Refreshing…";
36
+ try {
37
+ const next = (await mcp.callServerTool({
38
+ name: "refreshDashboard",
39
+ arguments: { reason: "User clicked refresh" },
40
+ })) as DashboardResult;
41
+ const content = next.structuredContent;
42
+ if (content) {
43
+ data = {
44
+ ...data,
45
+ ...content,
46
+ topic: content.topic ?? data?.topic,
47
+ };
48
+ }
49
+ status = content?.refreshedAt
50
+ ? `Refreshed ${new Date(content.refreshedAt).toLocaleTimeString()}`
51
+ : "Refreshed.";
52
+ } catch (error) {
53
+ status = error instanceof Error ? error.message : "Refresh failed.";
54
+ } finally {
55
+ busy = false;
56
+ }
57
+ }
58
+ </script>
59
+
60
+ <div class="card">
61
+ <p class="eyebrow">MCP App · Mountly</p>
62
+ <h1>{title}</h1>
63
+ <p class="subtitle">
64
+ Built with <code>mountly-mcp</code> — try Refresh to call an app-only tool.
65
+ </p>
66
+ <div class="grid">
67
+ {#if cards.length === 0}
68
+ {#each [1, 2, 3] as i (i)}
69
+ <div class="metric">
70
+ <div class="label">—</div>
71
+ <div class="value">—</div>
72
+ </div>
73
+ {/each}
74
+ {:else}
75
+ {#each cards as c (c.label)}
76
+ <div class="metric">
77
+ <div class="label">{c.label}</div>
78
+ <div class="value">{c.value}</div>
79
+ </div>
80
+ {/each}
81
+ {/if}
82
+ </div>
83
+ <div class="row">
84
+ <button type="button" disabled={busy} onclick={refresh}>Refresh data</button>
85
+ <span class="status">{status}</span>
86
+ </div>
87
+ </div>
88
+
89
+ <style>
90
+ .card {
91
+ border: 1px solid var(--color-border-primary, #e5e5e5);
92
+ border-radius: 12px;
93
+ padding: 20px;
94
+ background: var(--color-background-secondary, #fafafa);
95
+ }
96
+ .eyebrow {
97
+ margin: 0 0 8px;
98
+ color: var(--color-text-secondary, #737373);
99
+ font-size: 11px;
100
+ text-transform: uppercase;
101
+ letter-spacing: 0.1em;
102
+ }
103
+ h1 {
104
+ margin: 0 0 4px;
105
+ font-size: 20px;
106
+ font-weight: 600;
107
+ }
108
+ .subtitle {
109
+ color: var(--color-text-secondary, #737373);
110
+ font-size: 14px;
111
+ margin: 0 0 16px;
112
+ }
113
+ .grid {
114
+ display: grid;
115
+ grid-template-columns: repeat(3, 1fr);
116
+ gap: 10px;
117
+ margin-bottom: 16px;
118
+ }
119
+ .metric {
120
+ border: 1px solid var(--color-border-primary, #e5e5e5);
121
+ border-radius: 8px;
122
+ padding: 12px;
123
+ background: var(--color-background-primary, #ffffff);
124
+ }
125
+ .label {
126
+ color: var(--color-text-secondary, #737373);
127
+ font-size: 11px;
128
+ text-transform: uppercase;
129
+ letter-spacing: 0.05em;
130
+ }
131
+ .value {
132
+ margin-top: 4px;
133
+ font-size: 22px;
134
+ font-weight: 600;
135
+ }
136
+ .row {
137
+ display: flex;
138
+ align-items: center;
139
+ gap: 10px;
140
+ }
141
+ button {
142
+ border: 1px solid var(--color-border-primary, #e5e5e5);
143
+ border-radius: 8px;
144
+ padding: 8px 14px;
145
+ background: var(--color-background-primary, #ffffff);
146
+ color: inherit;
147
+ cursor: pointer;
148
+ font-size: 13px;
149
+ }
150
+ button:disabled {
151
+ opacity: 0.6;
152
+ cursor: wait;
153
+ }
154
+ .status {
155
+ font-size: 12px;
156
+ color: var(--color-text-secondary, #737373);
157
+ }
158
+ </style>
@@ -0,0 +1,14 @@
1
+ import { createMcpView } from "mountly-mcp/svelte";
2
+ import Dashboard from "./Dashboard.svelte";
3
+
4
+ const styles = `
5
+ :root {
6
+ color-scheme: light dark;
7
+ font-family: system-ui, -apple-system, sans-serif;
8
+ background: var(--color-background-primary, #ffffff);
9
+ color: var(--color-text-primary, #171717);
10
+ }
11
+ body { margin: 0; padding: 20px; }
12
+ `;
13
+
14
+ createMcpView(Dashboard, { shadow: true, styles });
@@ -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", "src/**/*.svelte"]
14
+ }
@@ -0,0 +1,22 @@
1
+ import { defineConfig } from "vite";
2
+ import { svelte } from "@sveltejs/vite-plugin-svelte";
3
+ import { mountlyMcpViews } from "mountly-mcp/vite";
4
+
5
+ export default defineConfig({
6
+ plugins: [
7
+ svelte(),
8
+ mountlyMcpViews({
9
+ apps: [
10
+ {
11
+ entry: "src/view.ts",
12
+ uri: "{{uri}}",
13
+ name: "dashboard",
14
+ description: "Interactive metrics dashboard",
15
+ displayModes: ["inline", "fullscreen"],
16
+ prefersBorder: true,
17
+ awaitToolResult: true,
18
+ },
19
+ ],
20
+ }),
21
+ ],
22
+ });
@@ -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
+ });
@@ -0,0 +1,4 @@
1
+ node_modules
2
+ dist
3
+ .DS_Store
4
+ *.log
@@ -0,0 +1,14 @@
1
+ # {{name}}
2
+
3
+ MCP App scaffolded with `mountly-mcp create --framework vue`.
4
+
5
+ ```bash
6
+ pnpm install
7
+ pnpm dev
8
+ pnpm verify
9
+ ```
10
+
11
+ - View: `src/view.ts` + `src/Dashboard.vue`
12
+ - Server: `server.mjs` (`registerMcpApps`)
13
+
14
+ 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
+ }