mcp-dashboards 2.0.0 → 2.1.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 +27 -30
- package/dist/mcp-app.html +117 -117
- package/dist/preview-server.d.ts +8 -0
- package/dist/preview-server.d.ts.map +1 -0
- package/dist/preview-server.js +115 -0
- package/dist/preview-server.js.map +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +66 -88
- package/dist/server.js.map +1 -1
- package/package.json +3 -2
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import http from "node:http";
|
|
2
|
+
export interface PreviewUrls {
|
|
3
|
+
httpUrl: string;
|
|
4
|
+
fileUrl: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function getPreviewUrls(data: any): Promise<PreviewUrls | null>;
|
|
7
|
+
export declare function getPreviewServer(): http.Server | null;
|
|
8
|
+
//# sourceMappingURL=preview-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview-server.d.ts","sourceRoot":"","sources":["../preview-server.ts"],"names":[],"mappings":"AAEA,OAAO,IAAI,MAAM,WAAW,CAAC;AAyG7B,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,cAAc,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAiB3E;AAED,wBAAgB,gBAAgB,IAAI,IAAI,CAAC,MAAM,GAAG,IAAI,CAErD"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import http from "node:http";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = path.dirname(__filename);
|
|
9
|
+
const DIST_DIR = __filename.endsWith(".ts") ? path.join(__dirname, "dist") : __dirname;
|
|
10
|
+
const MAX_CHARTS = 50;
|
|
11
|
+
const TEMP_DIR = path.join(os.tmpdir(), "mcp-dashboards");
|
|
12
|
+
const chartStore = new Map();
|
|
13
|
+
let cachedHtml = null;
|
|
14
|
+
let httpServer = null;
|
|
15
|
+
let serverPort = null;
|
|
16
|
+
async function loadHtml() {
|
|
17
|
+
if (cachedHtml)
|
|
18
|
+
return cachedHtml;
|
|
19
|
+
const htmlPath = path.join(DIST_DIR, "mcp-app.html");
|
|
20
|
+
cachedHtml = await fs.readFile(htmlPath, "utf-8");
|
|
21
|
+
return cachedHtml;
|
|
22
|
+
}
|
|
23
|
+
function injectChartData(html, data) {
|
|
24
|
+
const payload = JSON.stringify(data).replace(/<\//g, "<\\/");
|
|
25
|
+
return html.replace("</head>", `<script>window.__CHART_DATA__=${payload};</script></head>`);
|
|
26
|
+
}
|
|
27
|
+
function storeChart(data) {
|
|
28
|
+
const id = crypto.randomBytes(6).toString("hex");
|
|
29
|
+
chartStore.set(id, data);
|
|
30
|
+
// Evict oldest if over limit
|
|
31
|
+
if (chartStore.size > MAX_CHARTS) {
|
|
32
|
+
const firstKey = chartStore.keys().next().value;
|
|
33
|
+
if (firstKey)
|
|
34
|
+
chartStore.delete(firstKey);
|
|
35
|
+
}
|
|
36
|
+
return id;
|
|
37
|
+
}
|
|
38
|
+
async function ensurePreviewServer() {
|
|
39
|
+
if (serverPort !== null)
|
|
40
|
+
return serverPort;
|
|
41
|
+
return new Promise((resolve, reject) => {
|
|
42
|
+
const server = http.createServer(async (req, res) => {
|
|
43
|
+
try {
|
|
44
|
+
if (!req.url) {
|
|
45
|
+
res.writeHead(404);
|
|
46
|
+
res.end("Not Found");
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const match = req.url.match(/^\/chart\/([a-f0-9]+)/);
|
|
50
|
+
if (!match) {
|
|
51
|
+
res.writeHead(404);
|
|
52
|
+
res.end("Not Found");
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const data = chartStore.get(match[1]);
|
|
56
|
+
if (!data) {
|
|
57
|
+
res.writeHead(404, { "Content-Type": "text/html" });
|
|
58
|
+
res.end("<h1>Chart not found</h1><p>It may have been evicted or never existed.</p>");
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const html = await loadHtml();
|
|
62
|
+
const injected = injectChartData(html, data);
|
|
63
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
64
|
+
res.end(injected);
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
res.writeHead(500);
|
|
68
|
+
res.end(`Server error: ${err instanceof Error ? err.message : String(err)}`);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
server.listen(0, "127.0.0.1", () => {
|
|
72
|
+
const addr = server.address();
|
|
73
|
+
if (typeof addr === "object" && addr) {
|
|
74
|
+
serverPort = addr.port;
|
|
75
|
+
httpServer = server;
|
|
76
|
+
process.stderr.write(`[mcp-dashboards] Preview server ready at http://localhost:${serverPort}\n`);
|
|
77
|
+
process.on("exit", () => server.close());
|
|
78
|
+
resolve(serverPort);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
reject(new Error("Failed to bind preview server"));
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
server.on("error", reject);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
async function writeTempHtml(id, data) {
|
|
88
|
+
await fs.mkdir(TEMP_DIR, { recursive: true });
|
|
89
|
+
const filePath = path.join(TEMP_DIR, `chart-${id}.html`);
|
|
90
|
+
const html = await loadHtml();
|
|
91
|
+
const injected = injectChartData(html, data);
|
|
92
|
+
await fs.writeFile(filePath, injected, "utf-8");
|
|
93
|
+
return pathToFileURL(filePath).href;
|
|
94
|
+
}
|
|
95
|
+
export async function getPreviewUrls(data) {
|
|
96
|
+
if (process.env.MCP_DASHBOARDS_DISABLE_PREVIEW === "1")
|
|
97
|
+
return null;
|
|
98
|
+
try {
|
|
99
|
+
const id = storeChart(data);
|
|
100
|
+
const port = await ensurePreviewServer();
|
|
101
|
+
const fileUrl = await writeTempHtml(id, data);
|
|
102
|
+
return {
|
|
103
|
+
httpUrl: `http://localhost:${port}/chart/${id}`,
|
|
104
|
+
fileUrl,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
process.stderr.write(`[mcp-dashboards] Preview unavailable: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
export function getPreviewServer() {
|
|
113
|
+
return httpServer;
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=preview-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview-server.js","sourceRoot":"","sources":["../preview-server.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../server.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../server.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAoPpE;;GAEG;AACH,wBAAgB,YAAY,IAAI,SAAS,CAs2CxC"}
|
package/dist/server.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { registerAppResource, registerAppTool, RESOURCE_MIME_TYPE, } from "@modelcontextprotocol/ext-apps/server";
|
|
1
|
+
import { getUiCapability, registerAppResource, registerAppTool, RESOURCE_MIME_TYPE, } from "@modelcontextprotocol/ext-apps/server";
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
3
|
import fs from "node:fs/promises";
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { z } from "zod";
|
|
8
|
+
import { getPreviewUrls } from "./preview-server.js";
|
|
8
9
|
// Works both from source (server.ts) and compiled (dist/server.js)
|
|
9
10
|
const __filename = fileURLToPath(import.meta.url);
|
|
10
11
|
const __dirname = path.dirname(__filename);
|
|
@@ -154,6 +155,7 @@ function _registerChartTool(server, name, meta, inputSchema, buildResult, summar
|
|
|
154
155
|
effects: EffectsParam,
|
|
155
156
|
},
|
|
156
157
|
_meta: { ui: { resourceUri: RESOURCE_URI } },
|
|
158
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
157
159
|
}, async (args) => {
|
|
158
160
|
const chartData = {
|
|
159
161
|
...buildResult(args),
|
|
@@ -162,15 +164,39 @@ function _registerChartTool(server, name, meta, inputSchema, buildResult, summar
|
|
|
162
164
|
typography: args.typography,
|
|
163
165
|
effects: args.effects,
|
|
164
166
|
};
|
|
165
|
-
return
|
|
166
|
-
content: [
|
|
167
|
-
{ type: "text", text: summarize(args) },
|
|
168
|
-
{ type: "text", text: JSON.stringify(chartData) },
|
|
169
|
-
],
|
|
170
|
-
structuredContent: chartData,
|
|
171
|
-
};
|
|
167
|
+
return await _buildChartResult(server, chartData, summarize(args));
|
|
172
168
|
});
|
|
173
169
|
}
|
|
170
|
+
// Tracks whether each server's connected client supports MCP Apps inline rendering.
|
|
171
|
+
// Set via oninitialized callback in createServer.
|
|
172
|
+
const _clientSupportsInline = new WeakMap();
|
|
173
|
+
// Builds the standard tool response. If the client doesn't support MCP Apps
|
|
174
|
+
// inline rendering, appends browser preview links (localhost HTTP + standalone file)
|
|
175
|
+
// to the text content. MCP Apps clients only get the structuredContent - they
|
|
176
|
+
// already render the chart inline, so preview URLs would be redundant clutter.
|
|
177
|
+
async function _buildChartResult(server, chartData, summary) {
|
|
178
|
+
const content = [
|
|
179
|
+
{ type: "text", text: summary },
|
|
180
|
+
];
|
|
181
|
+
if (!_clientSupportsInline.get(server)) {
|
|
182
|
+
const urls = await getPreviewUrls(chartData);
|
|
183
|
+
if (urls) {
|
|
184
|
+
content.push({
|
|
185
|
+
type: "text",
|
|
186
|
+
text: `\n## View this chart\n` +
|
|
187
|
+
`Your AI client doesn't render MCP Apps inline, so use one of these links to see the interactive chart in your browser:\n\n` +
|
|
188
|
+
`**Click to open (recommended):** ${urls.httpUrl}\n` +
|
|
189
|
+
` - Opens instantly in your default browser\n` +
|
|
190
|
+
` - Only works while this MCP server is running\n\n` +
|
|
191
|
+
`**Save or share:** ${urls.fileUrl}\n` +
|
|
192
|
+
` - Self-contained HTML file on your disk\n` +
|
|
193
|
+
` - Works offline, survives server restart, can be emailed or archived\n`,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
content.push({ type: "text", text: JSON.stringify(chartData) });
|
|
198
|
+
return { content, structuredContent: chartData };
|
|
199
|
+
}
|
|
174
200
|
/**
|
|
175
201
|
* Creates a new MCP Dashboards server instance.
|
|
176
202
|
*/
|
|
@@ -179,6 +205,12 @@ export function createServer() {
|
|
|
179
205
|
name: "MCP Dashboards",
|
|
180
206
|
version: "2.0.0",
|
|
181
207
|
});
|
|
208
|
+
// After initialize handshake, detect if client supports MCP Apps inline rendering.
|
|
209
|
+
// Clients that do will see the interactive chart; those that don't get preview URLs.
|
|
210
|
+
server.server.oninitialized = () => {
|
|
211
|
+
const caps = server.server.getClientCapabilities?.();
|
|
212
|
+
_clientSupportsInline.set(server, !!getUiCapability(caps));
|
|
213
|
+
};
|
|
182
214
|
// -- Shared HTML resource --
|
|
183
215
|
registerAppResource(server, RESOURCE_URI, RESOURCE_URI, { mimeType: RESOURCE_MIME_TYPE }, async () => {
|
|
184
216
|
const html = await fs.readFile(path.join(DIST_DIR, "mcp-app.html"), "utf-8");
|
|
@@ -200,6 +232,7 @@ export function createServer() {
|
|
|
200
232
|
effects: EffectsParam,
|
|
201
233
|
},
|
|
202
234
|
_meta: { ui: { resourceUri: RESOURCE_URI } },
|
|
235
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
203
236
|
}, async (args) => {
|
|
204
237
|
const chartData = {
|
|
205
238
|
type: "pie",
|
|
@@ -215,13 +248,7 @@ export function createServer() {
|
|
|
215
248
|
const summary = args.data
|
|
216
249
|
.map((d) => `${d.label}: ${d.value} (${total > 0 ? ((d.value / total) * 100).toFixed(1) : "0.0"}%)`)
|
|
217
250
|
.join(", ");
|
|
218
|
-
return {
|
|
219
|
-
content: [
|
|
220
|
-
{ type: "text", text: `${args.title}: ${summary}` },
|
|
221
|
-
{ type: "text", text: JSON.stringify(chartData) },
|
|
222
|
-
],
|
|
223
|
-
structuredContent: chartData,
|
|
224
|
-
};
|
|
251
|
+
return await _buildChartResult(server, chartData, `${args.title}: ${summary}`);
|
|
225
252
|
});
|
|
226
253
|
// -- Tool: render_bar_chart --
|
|
227
254
|
registerAppTool(server, "render_bar_chart", {
|
|
@@ -238,6 +265,7 @@ export function createServer() {
|
|
|
238
265
|
effects: EffectsParam,
|
|
239
266
|
},
|
|
240
267
|
_meta: { ui: { resourceUri: RESOURCE_URI } },
|
|
268
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
241
269
|
}, async (args) => {
|
|
242
270
|
const chartData = {
|
|
243
271
|
type: "bar",
|
|
@@ -253,13 +281,7 @@ export function createServer() {
|
|
|
253
281
|
const summary = args.datasets
|
|
254
282
|
.map((ds) => `${ds.label}: [${ds.data.join(", ")}]`)
|
|
255
283
|
.join("; ");
|
|
256
|
-
return {
|
|
257
|
-
content: [
|
|
258
|
-
{ type: "text", text: `${args.title} - ${summary}` },
|
|
259
|
-
{ type: "text", text: JSON.stringify(chartData) },
|
|
260
|
-
],
|
|
261
|
-
structuredContent: chartData,
|
|
262
|
-
};
|
|
284
|
+
return await _buildChartResult(server, chartData, `${args.title} - ${summary}`);
|
|
263
285
|
});
|
|
264
286
|
// -- Tool: render_line_chart --
|
|
265
287
|
registerAppTool(server, "render_line_chart", {
|
|
@@ -276,6 +298,7 @@ export function createServer() {
|
|
|
276
298
|
effects: EffectsParam,
|
|
277
299
|
},
|
|
278
300
|
_meta: { ui: { resourceUri: RESOURCE_URI } },
|
|
301
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
279
302
|
}, async (args) => {
|
|
280
303
|
const chartData = {
|
|
281
304
|
type: "line",
|
|
@@ -291,13 +314,7 @@ export function createServer() {
|
|
|
291
314
|
const summary = args.datasets
|
|
292
315
|
.map((ds) => `${ds.label}: [${ds.data.join(", ")}]`)
|
|
293
316
|
.join("; ");
|
|
294
|
-
return {
|
|
295
|
-
content: [
|
|
296
|
-
{ type: "text", text: `${args.title} - ${summary}` },
|
|
297
|
-
{ type: "text", text: JSON.stringify(chartData) },
|
|
298
|
-
],
|
|
299
|
-
structuredContent: chartData,
|
|
300
|
-
};
|
|
317
|
+
return await _buildChartResult(server, chartData, `${args.title} - ${summary}`);
|
|
301
318
|
});
|
|
302
319
|
// -- Tool: render_hero_metric --
|
|
303
320
|
const GemTypeEnum = z.enum([
|
|
@@ -397,6 +414,7 @@ export function createServer() {
|
|
|
397
414
|
effects: EffectsParam,
|
|
398
415
|
},
|
|
399
416
|
_meta: { ui: { resourceUri: RESOURCE_URI } },
|
|
417
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
400
418
|
}, async (args) => {
|
|
401
419
|
const chartData = {
|
|
402
420
|
type: "hero_metric",
|
|
@@ -404,13 +422,7 @@ export function createServer() {
|
|
|
404
422
|
};
|
|
405
423
|
const variant = args.variant || "big_number";
|
|
406
424
|
const summary = `${args.title}: [${variant}] ${args.value ?? ""}${args.unit ? " " + args.unit : ""}`;
|
|
407
|
-
return
|
|
408
|
-
content: [
|
|
409
|
-
{ type: "text", text: summary },
|
|
410
|
-
{ type: "text", text: JSON.stringify(chartData) },
|
|
411
|
-
],
|
|
412
|
-
structuredContent: chartData,
|
|
413
|
-
};
|
|
425
|
+
return await _buildChartResult(server, chartData, summary);
|
|
414
426
|
});
|
|
415
427
|
// -- Tool: render_dashboard --
|
|
416
428
|
const HeroSchema = z.object({
|
|
@@ -457,6 +469,7 @@ export function createServer() {
|
|
|
457
469
|
layout: z.enum(["default", "hero-center", "kpi-top"]).optional().describe("Layout variant: default (hero above KPIs), hero-center (hero prominent), kpi-top (KPIs first)"),
|
|
458
470
|
},
|
|
459
471
|
_meta: { ui: { resourceUri: RESOURCE_URI } },
|
|
472
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
460
473
|
}, async (args) => {
|
|
461
474
|
const chartData = {
|
|
462
475
|
type: "dashboard",
|
|
@@ -483,13 +496,7 @@ export function createServer() {
|
|
|
483
496
|
const heroCount = Array.isArray(args.hero) ? args.hero.length : 1;
|
|
484
497
|
parts.push(`Hero: ${heroCount} widget${heroCount > 1 ? "s" : ""}`);
|
|
485
498
|
}
|
|
486
|
-
return
|
|
487
|
-
content: [
|
|
488
|
-
{ type: "text", text: parts.join(" | ") },
|
|
489
|
-
{ type: "text", text: JSON.stringify(chartData) },
|
|
490
|
-
],
|
|
491
|
-
structuredContent: chartData,
|
|
492
|
-
};
|
|
499
|
+
return await _buildChartResult(server, chartData, parts.join(" | "));
|
|
493
500
|
});
|
|
494
501
|
// -- Tool: render_chart_catalog --
|
|
495
502
|
registerAppTool(server, "render_chart_catalog", {
|
|
@@ -499,6 +506,7 @@ export function createServer() {
|
|
|
499
506
|
theme: ThemeParam,
|
|
500
507
|
},
|
|
501
508
|
_meta: { ui: { resourceUri: RESOURCE_URI } },
|
|
509
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
502
510
|
}, async (args) => {
|
|
503
511
|
// CSS-delegated charts spread c.data into the payload, so array data
|
|
504
512
|
// must be wrapped as { data: [...] } to land on payload.data correctly.
|
|
@@ -595,20 +603,15 @@ export function createServer() {
|
|
|
595
603
|
theme: args.theme,
|
|
596
604
|
footer: { text: "mcp-dashboards", lastUpdated: "Also available: render_table, render_from_json, render_from_url, render_live_chart, poll_http" },
|
|
597
605
|
};
|
|
598
|
-
return
|
|
599
|
-
content: [
|
|
600
|
-
{ type: "text", text: "Chart Catalog: 22 visual previews of every embeddable chart type. Click any card to ask about it. Standalone-only tools (table, live, auto, URL) listed in footer." },
|
|
601
|
-
{ type: "text", text: JSON.stringify(chartData) },
|
|
602
|
-
],
|
|
603
|
-
structuredContent: chartData,
|
|
604
|
-
};
|
|
606
|
+
return await _buildChartResult(server, chartData, "Chart Catalog: 22 visual previews of every embeddable chart type. Click any card to ask about it. Standalone-only tools (table, live, auto, URL) listed in footer.");
|
|
605
607
|
});
|
|
606
608
|
// -- Tool: render_theme_catalog --
|
|
607
609
|
registerAppTool(server, "render_theme_catalog", {
|
|
608
610
|
title: "Theme Catalog",
|
|
609
|
-
description: "Show a visual catalog of all
|
|
611
|
+
description: "Show a visual catalog of all 21 available themes. Each card previews the theme's colors, typography, and effects. Click any card to use that theme.",
|
|
610
612
|
inputSchema: {},
|
|
611
613
|
_meta: { ui: { resourceUri: RESOURCE_URI } },
|
|
614
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
612
615
|
}, async () => {
|
|
613
616
|
return {
|
|
614
617
|
content: [
|
|
@@ -789,6 +792,7 @@ export function createServer() {
|
|
|
789
792
|
effects: EffectsParam,
|
|
790
793
|
},
|
|
791
794
|
_meta: { ui: { resourceUri: RESOURCE_URI } },
|
|
795
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
792
796
|
}, async (args) => {
|
|
793
797
|
const chartData = {
|
|
794
798
|
type: "scatter",
|
|
@@ -803,13 +807,7 @@ export function createServer() {
|
|
|
803
807
|
const summary = args.datasets
|
|
804
808
|
.map((ds) => `${ds.label}: ${ds.data.length} points`)
|
|
805
809
|
.join("; ");
|
|
806
|
-
return {
|
|
807
|
-
content: [
|
|
808
|
-
{ type: "text", text: `${args.title} - ${summary}` },
|
|
809
|
-
{ type: "text", text: JSON.stringify(chartData) },
|
|
810
|
-
],
|
|
811
|
-
structuredContent: chartData,
|
|
812
|
-
};
|
|
810
|
+
return await _buildChartResult(server, chartData, `${args.title} - ${summary}`);
|
|
813
811
|
});
|
|
814
812
|
// -- Tool: render_candlestick_chart --
|
|
815
813
|
registerAppTool(server, "render_candlestick_chart", {
|
|
@@ -825,6 +823,7 @@ export function createServer() {
|
|
|
825
823
|
effects: EffectsParam,
|
|
826
824
|
},
|
|
827
825
|
_meta: { ui: { resourceUri: RESOURCE_URI } },
|
|
826
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
828
827
|
}, async (args) => {
|
|
829
828
|
const chartData = {
|
|
830
829
|
type: "candlestick",
|
|
@@ -839,13 +838,7 @@ export function createServer() {
|
|
|
839
838
|
const first = args.data[0];
|
|
840
839
|
const last = args.data[args.data.length - 1];
|
|
841
840
|
const change = last ? ((last.c - first.o) / first.o * 100).toFixed(2) : "0";
|
|
842
|
-
return {
|
|
843
|
-
content: [
|
|
844
|
-
{ type: "text", text: `${args.title}: ${args.data.length} bars, ${first?.date ?? "?"} to ${last?.date ?? "?"}, change: ${change}%` },
|
|
845
|
-
{ type: "text", text: JSON.stringify(chartData) },
|
|
846
|
-
],
|
|
847
|
-
structuredContent: chartData,
|
|
848
|
-
};
|
|
841
|
+
return await _buildChartResult(server, chartData, `${args.title}: ${args.data.length} bars, ${first?.date ?? "?"} to ${last?.date ?? "?"}, change: ${change}%`);
|
|
849
842
|
});
|
|
850
843
|
// -- Tool: render_table --
|
|
851
844
|
registerAppTool(server, "render_table", {
|
|
@@ -865,6 +858,7 @@ export function createServer() {
|
|
|
865
858
|
effects: EffectsParam,
|
|
866
859
|
},
|
|
867
860
|
_meta: { ui: { resourceUri: RESOURCE_URI } },
|
|
861
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
868
862
|
}, async (args) => {
|
|
869
863
|
const chartData = {
|
|
870
864
|
type: "table",
|
|
@@ -877,13 +871,7 @@ export function createServer() {
|
|
|
877
871
|
typography: args.typography,
|
|
878
872
|
effects: args.effects,
|
|
879
873
|
};
|
|
880
|
-
return {
|
|
881
|
-
content: [
|
|
882
|
-
{ type: "text", text: `${args.title}: ${args.rows.length} rows, ${args.columns.length} columns` },
|
|
883
|
-
{ type: "text", text: JSON.stringify(chartData) },
|
|
884
|
-
],
|
|
885
|
-
structuredContent: chartData,
|
|
886
|
-
};
|
|
874
|
+
return await _buildChartResult(server, chartData, `${args.title}: ${args.rows.length} rows, ${args.columns.length} columns`);
|
|
887
875
|
});
|
|
888
876
|
// -- Tool: render_from_json --
|
|
889
877
|
registerAppTool(server, "render_from_json", {
|
|
@@ -897,6 +885,7 @@ export function createServer() {
|
|
|
897
885
|
}).optional(),
|
|
898
886
|
},
|
|
899
887
|
_meta: { ui: { resourceUri: RESOURCE_URI } },
|
|
888
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
900
889
|
}, async (args) => {
|
|
901
890
|
const chartData = {
|
|
902
891
|
type: "auto",
|
|
@@ -904,13 +893,7 @@ export function createServer() {
|
|
|
904
893
|
data: args.data,
|
|
905
894
|
options: args.options ?? {},
|
|
906
895
|
};
|
|
907
|
-
return {
|
|
908
|
-
content: [
|
|
909
|
-
{ type: "text", text: `Auto-visualizing: ${args.title}` },
|
|
910
|
-
{ type: "text", text: JSON.stringify(chartData) },
|
|
911
|
-
],
|
|
912
|
-
structuredContent: chartData,
|
|
913
|
-
};
|
|
896
|
+
return await _buildChartResult(server, chartData, `Auto-visualizing: ${args.title}`);
|
|
914
897
|
});
|
|
915
898
|
// -- Tool: render_from_url --
|
|
916
899
|
registerAppTool(server, "render_from_url", {
|
|
@@ -924,6 +907,7 @@ export function createServer() {
|
|
|
924
907
|
}).optional(),
|
|
925
908
|
},
|
|
926
909
|
_meta: { ui: { resourceUri: RESOURCE_URI } },
|
|
910
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: true },
|
|
927
911
|
}, async (args) => {
|
|
928
912
|
try {
|
|
929
913
|
const response = await fetch(args.url, {
|
|
@@ -943,13 +927,7 @@ export function createServer() {
|
|
|
943
927
|
data,
|
|
944
928
|
options: args.options ?? {},
|
|
945
929
|
};
|
|
946
|
-
return {
|
|
947
|
-
content: [
|
|
948
|
-
{ type: "text", text: `Fetched and visualizing: ${args.title} (from ${args.url})` },
|
|
949
|
-
{ type: "text", text: JSON.stringify(chartData) },
|
|
950
|
-
],
|
|
951
|
-
structuredContent: chartData,
|
|
952
|
-
};
|
|
930
|
+
return await _buildChartResult(server, chartData, `Fetched and visualizing: ${args.title} (from ${args.url})`);
|
|
953
931
|
}
|
|
954
932
|
catch (err) {
|
|
955
933
|
return {
|
|
@@ -964,7 +942,7 @@ export function createServer() {
|
|
|
964
942
|
filename: z.string().describe("Filename with extension (e.g. chart.png)"),
|
|
965
943
|
data: z.string().describe("File contents: base64-encoded binary or plain text"),
|
|
966
944
|
encoding: z.enum(["base64", "utf-8"]).describe("How data is encoded"),
|
|
967
|
-
}, async (args) => {
|
|
945
|
+
}, { readOnlyHint: false, destructiveHint: false, idempotentHint: true }, async (args) => {
|
|
968
946
|
try {
|
|
969
947
|
const sanitized = path.basename(args.filename);
|
|
970
948
|
const downloadsDir = path.join(os.homedir(), "Downloads");
|
|
@@ -1341,7 +1319,7 @@ export function createServer() {
|
|
|
1341
1319
|
headers: z.record(z.string(), z.string()).optional().describe("Extra HTTP headers (public APIs only - NEVER put API keys here)"),
|
|
1342
1320
|
method: z.enum(["GET", "POST"]).optional().describe("HTTP method. Default: GET"),
|
|
1343
1321
|
body: z.string().optional().describe("Request body for POST requests"),
|
|
1344
|
-
}, async (args) => {
|
|
1322
|
+
}, { readOnlyHint: true, destructiveHint: false, openWorldHint: true }, async (args) => {
|
|
1345
1323
|
try {
|
|
1346
1324
|
let fetchUrl;
|
|
1347
1325
|
let fetchHeaders = {};
|