mountly-mcp 0.0.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +283 -20
- package/dist/artifact/index.d.ts +49 -0
- package/dist/artifact/index.js +21 -0
- package/dist/artifact/index.js.map +1 -0
- package/dist/bridge/iframe-entry.d.ts +1 -0
- package/dist/bridge/iframe-entry.js +270 -220
- package/dist/bridge/index.d.ts +20 -15
- package/dist/bridge/index.js +2 -2
- package/dist/build/emit-html.d.ts +30 -0
- package/dist/build/emit-meta.d.ts +17 -0
- package/dist/build/index.d.ts +33 -48
- package/dist/build/index.js +9 -135
- package/dist/build/index.js.map +1 -1
- package/dist/chunk-33DMFKKD.js +111 -0
- package/dist/chunk-33DMFKKD.js.map +1 -0
- package/dist/chunk-4HC3OWAQ.js +185 -0
- package/dist/chunk-4HC3OWAQ.js.map +1 -0
- package/dist/chunk-5EHNJCMF.js +58 -0
- package/dist/chunk-5EHNJCMF.js.map +1 -0
- package/dist/chunk-6QHKKFO3.js +30 -0
- package/dist/chunk-6QHKKFO3.js.map +1 -0
- package/dist/chunk-AEVQKFXX.js +119 -0
- package/dist/chunk-AEVQKFXX.js.map +1 -0
- package/dist/chunk-DKK26TRS.js +199 -0
- package/dist/chunk-DKK26TRS.js.map +1 -0
- package/dist/chunk-JVQZGKBL.js +171 -0
- package/dist/chunk-JVQZGKBL.js.map +1 -0
- package/dist/chunk-KWVGW5Q7.js +582 -0
- package/dist/chunk-KWVGW5Q7.js.map +1 -0
- package/dist/{chunk-A64ZILAS.js → chunk-M4WFJR3C.js} +3 -2
- package/dist/{chunk-A64ZILAS.js.map → chunk-M4WFJR3C.js.map} +1 -1
- package/dist/chunk-MTJDRWGD.js +196 -0
- package/dist/chunk-MTJDRWGD.js.map +1 -0
- package/dist/chunk-SADNMGP6.js +16 -0
- package/dist/chunk-SADNMGP6.js.map +1 -0
- package/dist/chunk-UECZA34Q.js +52 -0
- package/dist/chunk-UECZA34Q.js.map +1 -0
- package/dist/chunk-YJA4H6IB.js +13 -0
- package/dist/chunk-YJA4H6IB.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +297 -0
- package/dist/cli.js.map +1 -0
- package/dist/connect-server-7L4HGKKL.js +7 -0
- package/dist/connect-server-7L4HGKKL.js.map +1 -0
- package/dist/create.d.ts +10 -0
- package/dist/create.js +107 -0
- package/dist/create.js.map +1 -0
- package/dist/dev/connect-server.d.ts +13 -0
- package/dist/dev/host-entry.d.ts +13 -0
- package/dist/dev/host-entry.js +18330 -0
- package/dist/dev/index.d.ts +45 -0
- package/dist/dev/index.js +14 -0
- package/dist/dev/index.js.map +1 -0
- package/dist/dev/sandbox-entry.d.ts +7 -0
- package/dist/dev/sandbox-entry.js +80 -0
- package/dist/html.d.ts +4 -0
- package/dist/index.d.ts +7 -26
- package/dist/index.js +16 -3
- package/dist/json-render/app.d.ts +22 -0
- package/dist/json-render/app.js +10 -0
- package/dist/json-render/app.js.map +1 -0
- package/dist/json-render/generative-view.d.ts +57 -0
- package/dist/json-render/index.d.ts +10 -0
- package/dist/json-render/index.js +120 -0
- package/dist/json-render/index.js.map +1 -0
- package/dist/json-render/mcp.d.ts +49 -0
- package/dist/json-render/mcp.js +11 -0
- package/dist/json-render/mcp.js.map +1 -0
- package/dist/json-render/server.d.ts +72 -0
- package/dist/json-render/server.js +98 -0
- package/dist/json-render/server.js.map +1 -0
- package/dist/json-render/spec-stream.d.ts +26 -0
- package/dist/json-render/use-spec-stream.d.ts +26 -0
- package/dist/publish.d.ts +10 -0
- package/dist/react/context.d.ts +14 -0
- package/dist/react/create-view.d.ts +8 -0
- package/dist/react/hooks.d.ts +46 -0
- package/dist/react/index.d.ts +3 -0
- package/dist/react/index.js +36 -0
- package/dist/react/index.js.map +1 -0
- package/dist/sandbox/index.d.ts +16 -0
- package/dist/sandbox/index.js +8 -0
- package/dist/sandbox/index.js.map +1 -0
- package/dist/schema.d.ts +20 -0
- package/dist/server/index.d.ts +73 -0
- package/dist/server/index.js +235 -0
- package/dist/server/index.js.map +1 -0
- package/dist/svelte/index.d.ts +29 -0
- package/dist/svelte/index.js +13 -0
- package/dist/svelte/index.js.map +1 -0
- package/dist/testing/index.d.ts +27 -0
- package/dist/testing/index.js +312 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/render-artifact.d.ts +35 -0
- package/dist/types.d.ts +65 -0
- package/dist/vite/index.d.ts +23 -0
- package/dist/vite/index.js +211 -0
- package/dist/vite/index.js.map +1 -0
- package/dist/vue/index.d.ts +51 -0
- package/dist/vue/index.js +93 -0
- package/dist/vue/index.js.map +1 -0
- package/package.json +162 -20
- package/templates/react/.gitignore.tmpl +4 -0
- package/templates/react/README.md.tmpl +15 -0
- package/templates/react/mcp.fixtures.json.tmpl +4 -0
- package/templates/react/package.json.tmpl +27 -0
- package/templates/react/server.mjs.tmpl +70 -0
- package/templates/react/src/view.tsx.tmpl +160 -0
- package/templates/react/tsconfig.json.tmpl +15 -0
- package/templates/react/vite.config.ts.tmpl +22 -0
- package/templates/svelte/.gitignore.tmpl +4 -0
- package/templates/svelte/README.md.tmpl +14 -0
- package/templates/svelte/mcp.fixtures.json.tmpl +4 -0
- package/templates/svelte/package.json.tmpl +26 -0
- package/templates/svelte/server.mjs.tmpl +70 -0
- package/templates/svelte/src/Dashboard.svelte.tmpl +158 -0
- package/templates/svelte/src/view.ts.tmpl +14 -0
- package/templates/svelte/tsconfig.json.tmpl +14 -0
- package/templates/svelte/vite.config.ts.tmpl +22 -0
- package/templates/vue/.gitignore.tmpl +4 -0
- package/templates/vue/README.md.tmpl +14 -0
- package/templates/vue/mcp.fixtures.json.tmpl +4 -0
- package/templates/vue/package.json.tmpl +26 -0
- package/templates/vue/server.mjs.tmpl +70 -0
- package/templates/vue/src/Dashboard.vue.tmpl +159 -0
- package/templates/vue/src/view.ts.tmpl +14 -0
- package/templates/vue/tsconfig.json.tmpl +15 -0
- package/templates/vue/vite.config.ts.tmpl +22 -0
- package/dist/chunk-BUGGLMNA.js +0 -132
- package/dist/chunk-BUGGLMNA.js.map +0 -1
- package/dist/types-B9JAWm4W.d.ts +0 -62
|
@@ -0,0 +1,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,160 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
createMcpView,
|
|
4
|
+
useHostStyles,
|
|
5
|
+
useMcpApp,
|
|
6
|
+
useToolResult,
|
|
7
|
+
} from "mountly-mcp/react";
|
|
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
|
+
:root {
|
|
26
|
+
color-scheme: light dark;
|
|
27
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
28
|
+
background: var(--color-background-primary, #ffffff);
|
|
29
|
+
color: var(--color-text-primary, #171717);
|
|
30
|
+
}
|
|
31
|
+
body { margin: 0; padding: 20px; }
|
|
32
|
+
.card {
|
|
33
|
+
border: 1px solid var(--color-border-primary, #e5e5e5);
|
|
34
|
+
border-radius: 12px;
|
|
35
|
+
padding: 20px;
|
|
36
|
+
background: var(--color-background-secondary, #fafafa);
|
|
37
|
+
}
|
|
38
|
+
.eyebrow {
|
|
39
|
+
margin: 0 0 8px;
|
|
40
|
+
color: var(--color-text-secondary, #737373);
|
|
41
|
+
font-size: 11px;
|
|
42
|
+
text-transform: uppercase;
|
|
43
|
+
letter-spacing: .1em;
|
|
44
|
+
}
|
|
45
|
+
h1 { margin: 0 0 4px; font-size: 20px; font-weight: 600; }
|
|
46
|
+
.subtitle {
|
|
47
|
+
color: var(--color-text-secondary, #737373);
|
|
48
|
+
font-size: 14px;
|
|
49
|
+
margin: 0 0 16px;
|
|
50
|
+
}
|
|
51
|
+
.grid {
|
|
52
|
+
display: grid;
|
|
53
|
+
grid-template-columns: repeat(3, 1fr);
|
|
54
|
+
gap: 10px;
|
|
55
|
+
margin-bottom: 16px;
|
|
56
|
+
}
|
|
57
|
+
.metric {
|
|
58
|
+
border: 1px solid var(--color-border-primary, #e5e5e5);
|
|
59
|
+
border-radius: 8px;
|
|
60
|
+
padding: 12px;
|
|
61
|
+
background: var(--color-background-primary, #ffffff);
|
|
62
|
+
}
|
|
63
|
+
.label {
|
|
64
|
+
color: var(--color-text-secondary, #737373);
|
|
65
|
+
font-size: 11px;
|
|
66
|
+
text-transform: uppercase;
|
|
67
|
+
letter-spacing: .05em;
|
|
68
|
+
}
|
|
69
|
+
.value { margin-top: 4px; font-size: 22px; font-weight: 600; }
|
|
70
|
+
.row { display: flex; align-items: center; gap: 10px; }
|
|
71
|
+
button {
|
|
72
|
+
border: 1px solid var(--color-border-primary, #e5e5e5);
|
|
73
|
+
border-radius: 8px;
|
|
74
|
+
padding: 8px 14px;
|
|
75
|
+
background: var(--color-background-primary, #ffffff);
|
|
76
|
+
color: inherit;
|
|
77
|
+
cursor: pointer;
|
|
78
|
+
font-size: 13px;
|
|
79
|
+
}
|
|
80
|
+
button:hover:not(:disabled) { background: var(--color-background-secondary, #f5f5f5); }
|
|
81
|
+
button:disabled { opacity: 0.6; cursor: wait; }
|
|
82
|
+
.status { font-size: 12px; color: var(--color-text-secondary, #737373); }
|
|
83
|
+
`;
|
|
84
|
+
|
|
85
|
+
function Dashboard() {
|
|
86
|
+
useHostStyles();
|
|
87
|
+
const app = useMcpApp();
|
|
88
|
+
const result = useToolResult<DashboardResult>();
|
|
89
|
+
const [data, setData] = useState<DashboardData | undefined>(result?.structuredContent);
|
|
90
|
+
const [busy, setBusy] = useState(false);
|
|
91
|
+
const [status, setStatus] = useState("Connected.");
|
|
92
|
+
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
if (result?.structuredContent) setData(result.structuredContent);
|
|
95
|
+
}, [result]);
|
|
96
|
+
|
|
97
|
+
const cards = data?.cards ?? [];
|
|
98
|
+
const title = data?.topic ? `${data.topic} Dashboard` : "Dashboard";
|
|
99
|
+
|
|
100
|
+
async function refresh() {
|
|
101
|
+
setBusy(true);
|
|
102
|
+
setStatus("Refreshing…");
|
|
103
|
+
try {
|
|
104
|
+
const next = await app.callServerTool({
|
|
105
|
+
name: "refreshDashboard",
|
|
106
|
+
arguments: { reason: "User clicked refresh" },
|
|
107
|
+
});
|
|
108
|
+
const content = (next as DashboardResult).structuredContent;
|
|
109
|
+
if (content) {
|
|
110
|
+
setData((prev) => ({
|
|
111
|
+
...prev,
|
|
112
|
+
...content,
|
|
113
|
+
topic: content.topic ?? prev?.topic,
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
setStatus(
|
|
117
|
+
content?.refreshedAt
|
|
118
|
+
? `Refreshed ${new Date(content.refreshedAt).toLocaleTimeString()}`
|
|
119
|
+
: "Refreshed.",
|
|
120
|
+
);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
setStatus(error instanceof Error ? error.message : "Refresh failed.");
|
|
123
|
+
} finally {
|
|
124
|
+
setBusy(false);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<div className="card">
|
|
130
|
+
<p className="eyebrow">MCP App · Mountly</p>
|
|
131
|
+
<h1>{title}</h1>
|
|
132
|
+
<p className="subtitle">
|
|
133
|
+
Built with <code>mountly-mcp</code> — try Refresh to call an app-only tool.
|
|
134
|
+
</p>
|
|
135
|
+
<div className="grid">
|
|
136
|
+
{cards.length === 0
|
|
137
|
+
? [1, 2, 3].map((i) => (
|
|
138
|
+
<div className="metric" key={i}>
|
|
139
|
+
<div className="label">—</div>
|
|
140
|
+
<div className="value">—</div>
|
|
141
|
+
</div>
|
|
142
|
+
))
|
|
143
|
+
: cards.map((c) => (
|
|
144
|
+
<div className="metric" key={c.label}>
|
|
145
|
+
<div className="label">{c.label}</div>
|
|
146
|
+
<div className="value">{String(c.value)}</div>
|
|
147
|
+
</div>
|
|
148
|
+
))}
|
|
149
|
+
</div>
|
|
150
|
+
<div className="row">
|
|
151
|
+
<button type="button" disabled={busy} onClick={() => void refresh()}>
|
|
152
|
+
Refresh data
|
|
153
|
+
</button>
|
|
154
|
+
<span className="status">{status}</span>
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
createMcpView(Dashboard, { shadow: true, styles });
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
|
+
"jsx": "react-jsx",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"noEmit": true,
|
|
11
|
+
"isolatedModules": true,
|
|
12
|
+
"resolveJsonModule": true
|
|
13
|
+
},
|
|
14
|
+
"include": ["src"]
|
|
15
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { defineConfig } from "vite";
|
|
2
|
+
import react from "@vitejs/plugin-react";
|
|
3
|
+
import { mountlyMcpViews } from "mountly-mcp/vite";
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
plugins: [
|
|
7
|
+
react(),
|
|
8
|
+
mountlyMcpViews({
|
|
9
|
+
apps: [
|
|
10
|
+
{
|
|
11
|
+
entry: "src/view.tsx",
|
|
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,14 @@
|
|
|
1
|
+
# {{name}}
|
|
2
|
+
|
|
3
|
+
MCP App scaffolded with `mountly-mcp create --framework svelte`.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pnpm install
|
|
7
|
+
pnpm dev
|
|
8
|
+
pnpm verify
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
- View: `src/view.ts` + `src/Dashboard.svelte`
|
|
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,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": "^3.1.0",
|
|
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,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,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": "vue-tsc --noEmit"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
15
|
+
"mountly-mcp": "^3.1.0",
|
|
16
|
+
"mountly-vue": "^2.0.3",
|
|
17
|
+
"vue": "^3.5.41",
|
|
18
|
+
"zod": "^4.4.3"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@vitejs/plugin-vue": "^6.0.8",
|
|
22
|
+
"typescript": "^5.9.0",
|
|
23
|
+
"vite": "^8.2.1",
|
|
24
|
+
"vue-tsc": "^2.2.0"
|
|
25
|
+
}
|
|
26
|
+
}
|