mountly-mcp 3.1.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/README.md +54 -29
- package/dist/bridge/iframe-entry.js +24 -16
- package/dist/bridge/index.d.ts +5 -5
- package/dist/bridge/index.js +2 -2
- package/dist/build/index.d.ts +3 -3
- package/dist/build/index.js +2 -2
- package/dist/{chunk-NLQDVNZP.js → chunk-AEVQKFXX.js} +12 -8
- package/dist/chunk-AEVQKFXX.js.map +1 -0
- package/dist/{chunk-SNWNF2XI.js → chunk-JVQZGKBL.js} +13 -13
- 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-DQLQOMDM.js → chunk-M4WFJR3C.js} +2 -2
- package/dist/{chunk-DQLQOMDM.js.map → chunk-M4WFJR3C.js.map} +1 -1
- package/dist/{chunk-4WAKOYMK.js → chunk-MTJDRWGD.js} +2 -2
- 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/cli.js +21 -10
- package/dist/cli.js.map +1 -1
- package/dist/create.d.ts +10 -0
- package/dist/create.js +107 -0
- package/dist/create.js.map +1 -0
- package/dist/dev/host-entry.js +2 -2
- package/dist/dev/index.d.ts +1 -1
- package/dist/dev/index.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +10 -2
- package/dist/json-render/{widget.d.ts → generative-view.d.ts} +9 -11
- package/dist/json-render/index.d.ts +1 -1
- package/dist/json-render/index.js +9 -8
- package/dist/json-render/index.js.map +1 -1
- package/dist/publish.d.ts +10 -0
- package/dist/react/context.d.ts +1 -1
- package/dist/react/create-view.d.ts +8 -0
- package/dist/react/hooks.d.ts +1 -1
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.js +6 -5
- package/dist/schema.d.ts +2 -2
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.js.map +1 -1
- package/dist/svelte/index.d.ts +10 -16
- package/dist/svelte/index.js +7 -3
- package/dist/svelte/index.js.map +1 -1
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.js +1 -1
- package/dist/testing/index.js.map +1 -1
- package/dist/types.d.ts +14 -4
- package/dist/vite/index.d.ts +6 -6
- package/dist/vite/index.js +7 -7
- package/dist/vite/index.js.map +1 -1
- package/dist/vue/index.d.ts +12 -17
- package/dist/vue/index.js +10 -6
- package/dist/vue/index.js.map +1 -1
- package/package.json +7 -11
- 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-4WAKOYMK.js.map +0 -1
- package/dist/chunk-KRAS266U.js +0 -260
- package/dist/chunk-KRAS266U.js.map +0 -1
- package/dist/chunk-NLQDVNZP.js.map +0 -1
- package/dist/chunk-SNWNF2XI.js.map +0 -1
- 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": "^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
|
+
}
|
|
@@ -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,159 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, ref, watch } from "vue";
|
|
3
|
+
import { useMcpApp, useToolResult } from "mountly-mcp/vue";
|
|
4
|
+
|
|
5
|
+
interface DashboardCard {
|
|
6
|
+
label: string;
|
|
7
|
+
value: string | number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface DashboardData {
|
|
11
|
+
topic?: string;
|
|
12
|
+
refreshedAt?: string;
|
|
13
|
+
cards?: DashboardCard[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface DashboardResult {
|
|
17
|
+
structuredContent?: DashboardData;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const mcp = useMcpApp();
|
|
21
|
+
const result = useToolResult<DashboardResult>();
|
|
22
|
+
const data = ref<DashboardData | undefined>();
|
|
23
|
+
const busy = ref(false);
|
|
24
|
+
const status = ref("Connected.");
|
|
25
|
+
|
|
26
|
+
watch(
|
|
27
|
+
result,
|
|
28
|
+
(value) => {
|
|
29
|
+
if (value?.structuredContent) data.value = value.structuredContent;
|
|
30
|
+
},
|
|
31
|
+
{ immediate: true },
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const cards = computed(() => data.value?.cards ?? []);
|
|
35
|
+
const title = computed(() =>
|
|
36
|
+
data.value?.topic ? `${data.value.topic} Dashboard` : "Dashboard",
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
async function refresh() {
|
|
40
|
+
busy.value = true;
|
|
41
|
+
status.value = "Refreshing…";
|
|
42
|
+
try {
|
|
43
|
+
const next = (await mcp.callServerTool({
|
|
44
|
+
name: "refreshDashboard",
|
|
45
|
+
arguments: { reason: "User clicked refresh" },
|
|
46
|
+
})) as DashboardResult;
|
|
47
|
+
const content = next.structuredContent;
|
|
48
|
+
if (content) {
|
|
49
|
+
data.value = {
|
|
50
|
+
...data.value,
|
|
51
|
+
...content,
|
|
52
|
+
topic: content.topic ?? data.value?.topic,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
status.value = content?.refreshedAt
|
|
56
|
+
? `Refreshed ${new Date(content.refreshedAt).toLocaleTimeString()}`
|
|
57
|
+
: "Refreshed.";
|
|
58
|
+
} catch (error) {
|
|
59
|
+
status.value = error instanceof Error ? error.message : "Refresh failed.";
|
|
60
|
+
} finally {
|
|
61
|
+
busy.value = false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
</script>
|
|
65
|
+
|
|
66
|
+
<template>
|
|
67
|
+
<div class="card">
|
|
68
|
+
<p class="eyebrow">MCP App · Mountly</p>
|
|
69
|
+
<h1>{{ title }}</h1>
|
|
70
|
+
<p class="subtitle">
|
|
71
|
+
Built with <code>mountly-mcp</code> — try Refresh to call an app-only tool.
|
|
72
|
+
</p>
|
|
73
|
+
<div class="grid">
|
|
74
|
+
<div v-if="cards.length === 0" v-for="i in 3" :key="i" class="metric">
|
|
75
|
+
<div class="label">—</div>
|
|
76
|
+
<div class="value">—</div>
|
|
77
|
+
</div>
|
|
78
|
+
<div v-for="c in cards" :key="c.label" class="metric">
|
|
79
|
+
<div class="label">{{ c.label }}</div>
|
|
80
|
+
<div class="value">{{ c.value }}</div>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
<div class="row">
|
|
84
|
+
<button type="button" :disabled="busy" @click="refresh">Refresh data</button>
|
|
85
|
+
<span class="status">{{ status }}</span>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
</template>
|
|
89
|
+
|
|
90
|
+
<style scoped>
|
|
91
|
+
.card {
|
|
92
|
+
border: 1px solid var(--color-border-primary, #e5e5e5);
|
|
93
|
+
border-radius: 12px;
|
|
94
|
+
padding: 20px;
|
|
95
|
+
background: var(--color-background-secondary, #fafafa);
|
|
96
|
+
}
|
|
97
|
+
.eyebrow {
|
|
98
|
+
margin: 0 0 8px;
|
|
99
|
+
color: var(--color-text-secondary, #737373);
|
|
100
|
+
font-size: 11px;
|
|
101
|
+
text-transform: uppercase;
|
|
102
|
+
letter-spacing: 0.1em;
|
|
103
|
+
}
|
|
104
|
+
h1 {
|
|
105
|
+
margin: 0 0 4px;
|
|
106
|
+
font-size: 20px;
|
|
107
|
+
font-weight: 600;
|
|
108
|
+
}
|
|
109
|
+
.subtitle {
|
|
110
|
+
color: var(--color-text-secondary, #737373);
|
|
111
|
+
font-size: 14px;
|
|
112
|
+
margin: 0 0 16px;
|
|
113
|
+
}
|
|
114
|
+
.grid {
|
|
115
|
+
display: grid;
|
|
116
|
+
grid-template-columns: repeat(3, 1fr);
|
|
117
|
+
gap: 10px;
|
|
118
|
+
margin-bottom: 16px;
|
|
119
|
+
}
|
|
120
|
+
.metric {
|
|
121
|
+
border: 1px solid var(--color-border-primary, #e5e5e5);
|
|
122
|
+
border-radius: 8px;
|
|
123
|
+
padding: 12px;
|
|
124
|
+
background: var(--color-background-primary, #ffffff);
|
|
125
|
+
}
|
|
126
|
+
.label {
|
|
127
|
+
color: var(--color-text-secondary, #737373);
|
|
128
|
+
font-size: 11px;
|
|
129
|
+
text-transform: uppercase;
|
|
130
|
+
letter-spacing: 0.05em;
|
|
131
|
+
}
|
|
132
|
+
.value {
|
|
133
|
+
margin-top: 4px;
|
|
134
|
+
font-size: 22px;
|
|
135
|
+
font-weight: 600;
|
|
136
|
+
}
|
|
137
|
+
.row {
|
|
138
|
+
display: flex;
|
|
139
|
+
align-items: center;
|
|
140
|
+
gap: 10px;
|
|
141
|
+
}
|
|
142
|
+
button {
|
|
143
|
+
border: 1px solid var(--color-border-primary, #e5e5e5);
|
|
144
|
+
border-radius: 8px;
|
|
145
|
+
padding: 8px 14px;
|
|
146
|
+
background: var(--color-background-primary, #ffffff);
|
|
147
|
+
color: inherit;
|
|
148
|
+
cursor: pointer;
|
|
149
|
+
font-size: 13px;
|
|
150
|
+
}
|
|
151
|
+
button:disabled {
|
|
152
|
+
opacity: 0.6;
|
|
153
|
+
cursor: wait;
|
|
154
|
+
}
|
|
155
|
+
.status {
|
|
156
|
+
font-size: 12px;
|
|
157
|
+
color: var(--color-text-secondary, #737373);
|
|
158
|
+
}
|
|
159
|
+
</style>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createMcpView } from "mountly-mcp/vue";
|
|
2
|
+
import Dashboard from "./Dashboard.vue";
|
|
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,15 @@
|
|
|
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
|
+
"jsx": "preserve"
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/**/*.ts", "src/**/*.vue"]
|
|
15
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { defineConfig } from "vite";
|
|
2
|
+
import vue from "@vitejs/plugin-vue";
|
|
3
|
+
import { mountlyMcpViews } from "mountly-mcp/vite";
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
plugins: [
|
|
7
|
+
vue(),
|
|
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
|
+
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/build/index.ts","../src/build/emit-html.ts","../src/build/emit-meta.ts"],"sourcesContent":["import { readFile, writeFile } from \"node:fs/promises\";\nimport { fileURLToPath } from \"node:url\";\nimport { resolve } from \"node:path\";\nimport type {\n McpUiDisplayMode,\n McpUiResourceCsp,\n McpUiResourcePermissions,\n} from \"@modelcontextprotocol/ext-apps\";\nimport type { McpResourceDeclaration } from \"../types.js\";\nimport type { McpAppArtifact } from \"../artifact/index.js\";\nimport { emitHtml } from \"./emit-html.js\";\nimport { emitMeta } from \"./emit-meta.js\";\n\n/**\n * Absolute path to the iframe bridge runtime shipped with this package.\n *\n * `buildMcpResource` inlines this file's contents into the emitted HTML so\n * the widget can speak the MCP Apps postMessage protocol. Use this when you\n * need to point another build tool at the runtime, or when you want a\n * stable, version-pinned alternative to passing `bridgeRuntimePath` yourself.\n *\n * Resolves relative to the installed package, so it works identically in\n * monorepos and in projects that installed mountly-mcp from npm.\n */\nexport function getBridgeRuntimePath(): string {\n // Ask the module resolver rather than walking up from __dirname: the subpath\n // is a declared export, so this holds wherever a bundler puts this file.\n return fileURLToPath(import.meta.resolve(\"mountly-mcp/bridge/iframe-entry.js\"));\n}\n\nfunction defaultBridgeRuntimePath(): string {\n return getBridgeRuntimePath();\n}\n\nexport interface BuildSelfContainedOptions {\n entry: string;\n uri: string;\n name: string;\n output: string;\n description?: string;\n awaitToolResult?: boolean;\n displayModes?: ReadonlyArray<McpUiDisplayMode>;\n /**\n * Render progressive states from `ui/notifications/tool-input-partial`.\n * Off by default — see `RunBridgeOptions.streamToolInput`.\n */\n streamToolInput?: boolean;\n csp?: McpUiResourceCsp;\n /** Sandbox permissions requested by the view (camera/microphone/geolocation/clipboardWrite). */\n permissions?: McpUiResourcePermissions;\n /** Dedicated sandbox origin (host-dependent format). */\n domain?: string;\n /** Whether the view requests the host to show a visible border + background. */\n prefersBorder?: boolean;\n cssEntry?: string;\n bridgeRuntimePath?: string;\n}\n\nexport interface BuildCdnOptions extends BuildSelfContainedOptions {\n cdn: { jsUrl: string; cssUrl: string };\n}\n\nexport type BuildOptions = BuildSelfContainedOptions | BuildCdnOptions;\n\nexport interface BuildResult {\n htmlPath: string;\n metaPath: string;\n declaration: McpResourceDeclaration;\n artifact: McpAppArtifact;\n}\n\nexport interface BuildFromSourceOptions extends Omit<\n BuildSelfContainedOptions,\n \"entry\" | \"cssEntry\"\n> {\n /** The bundled widget JS. */\n js: string;\n /** The widget's CSS, if it has any. */\n css?: string;\n}\n\n/**\n * Same as {@link buildMcpResource}, but taking the bundle in memory instead of\n * from disk — for bundler plugins that already hold the emitted sources and\n * shouldn't have to guess when they land on the filesystem.\n */\nexport async function buildMcpResourceFromSource(\n options: BuildFromSourceOptions,\n): Promise<BuildResult> {\n const { js, css, ...rest } = options;\n return writeResource({ ...rest, mode: \"self-contained\", js, css: css ?? \"\" });\n}\n\nexport async function buildMcpResource(options: BuildOptions): Promise<BuildResult> {\n if (!(\"cdn\" in options)) {\n const js = await readFile(options.entry, \"utf8\");\n const css = options.cssEntry ? await readFile(options.cssEntry, \"utf8\") : \"\";\n return writeResource({ ...options, mode: \"self-contained\", js, css });\n }\n return writeResource({ ...options, mode: \"cdn\" });\n}\n\ntype ResourceMeta = Omit<BuildSelfContainedOptions, \"entry\" | \"cssEntry\">;\n\ntype WriteResourceOptions =\n | (ResourceMeta & { mode: \"self-contained\"; js: string; css: string })\n | (ResourceMeta & { mode: \"cdn\"; cdn: BuildCdnOptions[\"cdn\"] });\n\nasync function writeResource(options: WriteResourceOptions): Promise<BuildResult> {\n const bridgeRuntimeJs = await readFile(\n options.bridgeRuntimePath ?? defaultBridgeRuntimePath(),\n \"utf8\",\n );\n\n const declaration = emitMeta({\n uri: options.uri,\n name: options.name,\n description: options.description,\n awaitToolResult: options.awaitToolResult,\n displayModes: options.displayModes,\n csp: options.mode === \"cdn\" ? mergeCdnIntoCsp(options.csp, options.cdn) : options.csp,\n permissions: options.permissions,\n domain: options.domain,\n prefersBorder: options.prefersBorder,\n });\n\n // Same values the sidecar declares, so the view's `ui/initialize` matches it.\n const config = {\n awaitToolResult: declaration.awaitToolResult,\n displayModes: declaration.displayModes,\n streamToolInput: options.streamToolInput ?? false,\n };\n\n const html =\n options.mode === \"cdn\"\n ? emitHtml({\n mode: \"cdn\",\n uri: options.uri,\n jsUrl: options.cdn.jsUrl,\n cssUrl: options.cdn.cssUrl,\n bridgeRuntimeJs,\n config,\n })\n : emitHtml({\n mode: \"self-contained\",\n uri: options.uri,\n js: options.js,\n css: options.css,\n bridgeRuntimeJs,\n config,\n });\n\n const metaPath = `${options.output}.meta.json`;\n await writeFile(options.output, html, \"utf8\");\n await writeFile(metaPath, JSON.stringify(declaration, null, 2), \"utf8\");\n\n const htmlPath = resolve(options.output);\n return {\n htmlPath,\n metaPath: resolve(metaPath),\n declaration,\n artifact: {\n name: declaration.name,\n uri: declaration.uri,\n htmlPath,\n metaPath: resolve(metaPath),\n declaration,\n },\n };\n}\n\nfunction mergeCdnIntoCsp(\n csp: McpUiResourceCsp | undefined,\n cdn: { jsUrl: string; cssUrl: string },\n): McpUiResourceCsp {\n const jsOrigin = new URL(cdn.jsUrl).origin;\n const cssOrigin = new URL(cdn.cssUrl).origin;\n const merged: McpUiResourceCsp = { ...(csp ?? {}) };\n const existing = new Set(merged.resourceDomains ?? []);\n existing.add(jsOrigin);\n existing.add(cssOrigin);\n merged.resourceDomains = Array.from(existing);\n return merged;\n}\n\nexport { emitHtml } from \"./emit-html.js\";\nexport { emitMeta } from \"./emit-meta.js\";\n","import { MCP_APPS_PROTOCOL_VERSION } from \"../schema.js\";\nimport type { McpUiDisplayMode } from \"@modelcontextprotocol/ext-apps\";\nimport { escapeInlineScript, serializeInlineScriptValue } from \"../html.js\";\n\n/**\n * Runtime config the emitted HTML hands to the inlined bridge\n * (`bridge/iframe-entry.ts` reads these off `globalThis`). Without it the view\n * falls back to `awaitToolResult: true` / `availableDisplayModes: [\"inline\"]`,\n * regardless of what the build declared in the sidecar.\n */\nexport interface EmitHtmlConfig {\n awaitToolResult?: boolean;\n displayModes?: ReadonlyArray<McpUiDisplayMode>;\n streamToolInput?: boolean;\n}\n\nexport interface EmitHtmlSelfContainedInput {\n mode: \"self-contained\";\n uri: string;\n js: string;\n css: string;\n bridgeRuntimeJs: string;\n config?: EmitHtmlConfig;\n}\n\nexport interface EmitHtmlCdnInput {\n mode: \"cdn\";\n uri: string;\n jsUrl: string;\n cssUrl: string;\n bridgeRuntimeJs: string;\n config?: EmitHtmlConfig;\n}\n\nexport type EmitHtmlInput = EmitHtmlSelfContainedInput | EmitHtmlCdnInput;\n\nfunction escapeAttr(value: string): string {\n return value\n .replace(/&/g, \"&\")\n .replace(/\"/g, \""\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\");\n}\n\nfunction escapeStyle(value: string): string {\n return value.replace(/<\\/style/gi, \"<\\\\/style\");\n}\n\n/**\n * Classic (non-module) script so it runs at parse time — before the deferred\n * module scripts that boot the bridge.\n */\nfunction configScript(config: EmitHtmlConfig | undefined): string[] {\n if (!config) return [];\n const assignments: string[] = [];\n if (config.awaitToolResult !== undefined) {\n assignments.push(\n `globalThis.__mountlyMcpAwaitToolResult__=${serializeInlineScriptValue(config.awaitToolResult)};`,\n );\n }\n if (config.displayModes !== undefined) {\n assignments.push(\n `globalThis.__mountlyMcpAvailableDisplayModes__=${serializeInlineScriptValue(config.displayModes)};`,\n );\n }\n if (config.streamToolInput !== undefined) {\n assignments.push(\n `globalThis.__mountlyMcpStreamToolInput__=${serializeInlineScriptValue(config.streamToolInput)};`,\n );\n }\n if (assignments.length === 0) return [];\n return [`<script>${assignments.join(\"\")}</script>`];\n}\n\nexport function emitHtml(input: EmitHtmlInput): string {\n const head = [\n `<meta charset=\"utf-8\">`,\n `<meta name=\"mountly-mcp-protocol\" content=\"${MCP_APPS_PROTOCOL_VERSION}\">`,\n `<meta name=\"mountly-mcp-uri\" content=\"${escapeAttr(input.uri)}\">`,\n ];\n\n if (input.mode === \"self-contained\") {\n head.push(`<style>${escapeStyle(input.css)}</style>`);\n const body = [\n `<div id=\"mountly-mcp-root\"></div>`,\n ...configScript(input.config),\n `<script type=\"module\">${escapeInlineScript(input.js)}\\n${escapeInlineScript(input.bridgeRuntimeJs)}</script>`,\n ];\n return template(head, body);\n }\n\n head.push(`<link rel=\"stylesheet\" href=\"${escapeAttr(input.cssUrl)}\">`);\n // The widget bundle MUST load before the bridge: module scripts execute in\n // document order, and the bridge reads `__mountlyMcpWidget__` synchronously.\n const body = [\n `<div id=\"mountly-mcp-root\"></div>`,\n ...configScript(input.config),\n `<script type=\"module\" src=\"${escapeAttr(input.jsUrl)}\"></script>`,\n `<script type=\"module\">${escapeInlineScript(input.bridgeRuntimeJs)}</script>`,\n ];\n return template(head, body);\n}\n\nfunction template(head: string[], body: string[]): string {\n return `<!doctype html>\n<html data-mountly-mcp>\n<head>\n${head.join(\"\\n\")}\n</head>\n<body>\n${body.join(\"\\n\")}\n</body>\n</html>`;\n}\n","import { MCP_APPS_MIME, MCP_APPS_PROTOCOL_VERSION } from \"../schema.js\";\nimport type {\n McpUiDisplayMode,\n McpUiResourceCsp,\n McpUiResourcePermissions,\n} from \"@modelcontextprotocol/ext-apps\";\nimport type { McpResourceDeclaration } from \"../types.js\";\n\nexport interface EmitMetaInput {\n uri: string;\n name: string;\n description?: string;\n awaitToolResult?: boolean;\n displayModes?: ReadonlyArray<McpUiDisplayMode>;\n csp?: McpUiResourceCsp;\n /** Sandbox permissions requested by the view (camera/microphone/geolocation/clipboardWrite). */\n permissions?: McpUiResourcePermissions;\n /** Dedicated sandbox origin requested by the view (host-dependent format). */\n domain?: string;\n /** Whether the view requests the host to show a visible border + background. */\n prefersBorder?: boolean;\n}\n\nfunction assertUiUri(uri: string): void {\n if (!uri.startsWith(\"ui://\")) {\n throw new Error(`mountly-mcp: UI resource uri must use the 'ui://' scheme (received '${uri}')`);\n }\n}\n\nexport function emitMeta(input: EmitMetaInput): McpResourceDeclaration {\n assertUiUri(input.uri);\n\n const ui: McpResourceDeclaration[\"_meta\"][\"ui\"] = {};\n if (input.csp) ui.csp = input.csp;\n if (input.permissions) ui.permissions = input.permissions;\n if (input.domain !== undefined) ui.domain = input.domain;\n if (input.prefersBorder !== undefined) ui.prefersBorder = input.prefersBorder;\n\n const decl: McpResourceDeclaration = {\n protocolVersion: MCP_APPS_PROTOCOL_VERSION,\n uri: input.uri,\n name: input.name,\n mimeType: MCP_APPS_MIME,\n awaitToolResult: input.awaitToolResult ?? true,\n displayModes: input.displayModes ?? [\"inline\"],\n _meta: { ui },\n };\n if (input.description !== undefined) decl.description = input.description;\n return decl;\n}\n"],"mappings":"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","names":["body"]}
|