@gluip/chart-canvas-mcp 0.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/dist/api-server.js +18 -0
- package/dist/api.js +32 -0
- package/dist/index.js +216 -0
- package/dist/state.js +35 -0
- package/dist/types.js +1 -0
- package/package.json +52 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import cors from "cors";
|
|
3
|
+
import { stateManager } from "./state.js";
|
|
4
|
+
const app = express();
|
|
5
|
+
const PORT = 3000;
|
|
6
|
+
app.use(cors());
|
|
7
|
+
app.use(express.json());
|
|
8
|
+
// Get current canvas state
|
|
9
|
+
app.get("/state", (req, res) => {
|
|
10
|
+
res.json(stateManager.getState());
|
|
11
|
+
});
|
|
12
|
+
// Health check
|
|
13
|
+
app.get("/health", (req, res) => {
|
|
14
|
+
res.json({ status: "ok" });
|
|
15
|
+
});
|
|
16
|
+
app.listen(PORT, () => {
|
|
17
|
+
console.log(`API server listening on http://localhost:${PORT}`);
|
|
18
|
+
});
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import cors from "cors";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import { stateManager } from "./state.js";
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
8
|
+
export function startApiServer() {
|
|
9
|
+
const app = express();
|
|
10
|
+
const PORT = 3000;
|
|
11
|
+
app.use(cors());
|
|
12
|
+
app.use(express.json());
|
|
13
|
+
// API endpoints
|
|
14
|
+
app.get("/state", (req, res) => {
|
|
15
|
+
res.json(stateManager.getState());
|
|
16
|
+
});
|
|
17
|
+
app.get("/health", (req, res) => {
|
|
18
|
+
res.json({ status: "ok" });
|
|
19
|
+
});
|
|
20
|
+
// Serve frontend static files in production
|
|
21
|
+
const frontendDistPath = path.join(__dirname, "../../frontend/dist");
|
|
22
|
+
app.use(express.static(frontendDistPath));
|
|
23
|
+
// Fallback to index.html for client-side routing
|
|
24
|
+
app.get("*", (req, res) => {
|
|
25
|
+
res.sendFile(path.join(frontendDistPath, "index.html"));
|
|
26
|
+
});
|
|
27
|
+
app.listen(PORT, () => {
|
|
28
|
+
console.error(`Server running on http://localhost:${PORT}`);
|
|
29
|
+
console.error(`- API: http://localhost:${PORT}/state`);
|
|
30
|
+
console.error(`- Frontend: http://localhost:${PORT}`);
|
|
31
|
+
});
|
|
32
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import { stateManager } from "./state.js";
|
|
6
|
+
import { startApiServer } from "./api.js";
|
|
7
|
+
import open from "open";
|
|
8
|
+
const server = new Server({
|
|
9
|
+
name: "chart-canvas-server",
|
|
10
|
+
version: "0.1.0",
|
|
11
|
+
}, {
|
|
12
|
+
capabilities: {
|
|
13
|
+
tools: {},
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
// Register tools
|
|
17
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
18
|
+
return {
|
|
19
|
+
tools: [
|
|
20
|
+
{
|
|
21
|
+
name: "addVisualization",
|
|
22
|
+
description: "Add a new chart visualization to the canvas",
|
|
23
|
+
inputSchema: {
|
|
24
|
+
type: "object",
|
|
25
|
+
properties: {
|
|
26
|
+
type: {
|
|
27
|
+
type: "string",
|
|
28
|
+
enum: ["line", "bar", "scatter", "table", "flowchart", "pie"],
|
|
29
|
+
description: "Type of visualization to create",
|
|
30
|
+
},
|
|
31
|
+
series: {
|
|
32
|
+
type: "array",
|
|
33
|
+
items: {
|
|
34
|
+
type: "object",
|
|
35
|
+
properties: {
|
|
36
|
+
name: {
|
|
37
|
+
type: "string",
|
|
38
|
+
description: "Name of the series (e.g., '2015', 'Product A')",
|
|
39
|
+
},
|
|
40
|
+
data: {
|
|
41
|
+
type: "array",
|
|
42
|
+
items: {
|
|
43
|
+
type: "array",
|
|
44
|
+
items: { type: "number" },
|
|
45
|
+
minItems: 2,
|
|
46
|
+
maxItems: 2,
|
|
47
|
+
},
|
|
48
|
+
description: "Array of [x, y] coordinate pairs for this series",
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
required: ["name", "data"],
|
|
52
|
+
},
|
|
53
|
+
description: "Array of data series to display (required for charts)",
|
|
54
|
+
},
|
|
55
|
+
table: {
|
|
56
|
+
type: "object",
|
|
57
|
+
properties: {
|
|
58
|
+
headers: {
|
|
59
|
+
type: "array",
|
|
60
|
+
items: { type: "string" },
|
|
61
|
+
description: "Column headers for the table",
|
|
62
|
+
},
|
|
63
|
+
rows: {
|
|
64
|
+
type: "array",
|
|
65
|
+
items: {
|
|
66
|
+
type: "array",
|
|
67
|
+
items: { type: ["string", "number"] },
|
|
68
|
+
},
|
|
69
|
+
description: "Table rows, each row is an array of values",
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
required: ["headers", "rows"],
|
|
73
|
+
description: "Table data (required for table type)",
|
|
74
|
+
},
|
|
75
|
+
mermaid: {
|
|
76
|
+
type: "string",
|
|
77
|
+
description: "Mermaid diagram syntax (required for flowchart type)",
|
|
78
|
+
},
|
|
79
|
+
title: {
|
|
80
|
+
type: "string",
|
|
81
|
+
description: "Optional title for the chart",
|
|
82
|
+
},
|
|
83
|
+
description: {
|
|
84
|
+
type: "string",
|
|
85
|
+
description: "Optional description or explanation for the chart",
|
|
86
|
+
},
|
|
87
|
+
xLabels: {
|
|
88
|
+
type: "array",
|
|
89
|
+
items: { type: "string" },
|
|
90
|
+
description: "Optional labels for the x-axis (e.g., dates). Should match the number of data points.",
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
required: ["type"],
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: "removeVisualization",
|
|
98
|
+
description: "Remove a visualization from the canvas by ID",
|
|
99
|
+
inputSchema: {
|
|
100
|
+
type: "object",
|
|
101
|
+
properties: {
|
|
102
|
+
id: {
|
|
103
|
+
type: "string",
|
|
104
|
+
description: "ID of the visualization to remove",
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
required: ["id"],
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: "clearCanvas",
|
|
112
|
+
description: "Remove all visualizations from the canvas",
|
|
113
|
+
inputSchema: {
|
|
114
|
+
type: "object",
|
|
115
|
+
properties: {},
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: "showCanvas",
|
|
120
|
+
description: "Open the canvas in a browser window to view visualizations",
|
|
121
|
+
inputSchema: {
|
|
122
|
+
type: "object",
|
|
123
|
+
properties: {},
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
};
|
|
128
|
+
});
|
|
129
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
130
|
+
const { name, arguments: args } = request.params;
|
|
131
|
+
switch (name) {
|
|
132
|
+
case "addVisualization": {
|
|
133
|
+
const { type, series, table, mermaid, title, description, xLabels } = args;
|
|
134
|
+
const viz = stateManager.addVisualization({
|
|
135
|
+
type,
|
|
136
|
+
series,
|
|
137
|
+
table,
|
|
138
|
+
mermaid,
|
|
139
|
+
title,
|
|
140
|
+
description,
|
|
141
|
+
xLabels,
|
|
142
|
+
});
|
|
143
|
+
return {
|
|
144
|
+
content: [
|
|
145
|
+
{
|
|
146
|
+
type: "text",
|
|
147
|
+
text: `Added ${type} chart with ID ${viz.id}. View it at http://localhost:3000`,
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
case "removeVisualization": {
|
|
153
|
+
const { id } = args;
|
|
154
|
+
const removed = stateManager.removeVisualization(id);
|
|
155
|
+
return {
|
|
156
|
+
content: [
|
|
157
|
+
{
|
|
158
|
+
type: "text",
|
|
159
|
+
text: removed
|
|
160
|
+
? `Removed visualization ${id}`
|
|
161
|
+
: `Visualization ${id} not found`,
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
case "clearCanvas": {
|
|
167
|
+
stateManager.clearCanvas();
|
|
168
|
+
return {
|
|
169
|
+
content: [
|
|
170
|
+
{
|
|
171
|
+
type: "text",
|
|
172
|
+
text: "Cleared all visualizations from canvas",
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
case "showCanvas": {
|
|
178
|
+
try {
|
|
179
|
+
await open("http://localhost:3000");
|
|
180
|
+
return {
|
|
181
|
+
content: [
|
|
182
|
+
{
|
|
183
|
+
type: "text",
|
|
184
|
+
text: "Opened canvas in browser at http://localhost:3000",
|
|
185
|
+
},
|
|
186
|
+
],
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
return {
|
|
191
|
+
content: [
|
|
192
|
+
{
|
|
193
|
+
type: "text",
|
|
194
|
+
text: `Failed to open browser: ${error instanceof Error ? error.message : String(error)}`,
|
|
195
|
+
},
|
|
196
|
+
],
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
default:
|
|
201
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
async function main() {
|
|
205
|
+
// Start the API server for frontend
|
|
206
|
+
startApiServer();
|
|
207
|
+
// Start MCP server on stdio
|
|
208
|
+
const transport = new StdioServerTransport();
|
|
209
|
+
await server.connect(transport);
|
|
210
|
+
console.error("Chart Canvas MCP Server running");
|
|
211
|
+
console.error("API server running on http://localhost:3000");
|
|
212
|
+
}
|
|
213
|
+
main().catch((error) => {
|
|
214
|
+
console.error("Fatal error:", error);
|
|
215
|
+
process.exit(1);
|
|
216
|
+
});
|
package/dist/state.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { randomUUID } from "crypto";
|
|
2
|
+
class StateManager {
|
|
3
|
+
state = {
|
|
4
|
+
visualizations: [],
|
|
5
|
+
};
|
|
6
|
+
getState() {
|
|
7
|
+
return JSON.parse(JSON.stringify(this.state));
|
|
8
|
+
}
|
|
9
|
+
addVisualization(viz) {
|
|
10
|
+
// Flowcharts krijgen een grotere default grootte
|
|
11
|
+
const isFlowchart = viz.type === "flowchart";
|
|
12
|
+
const defaultWidth = isFlowchart ? 10 : 6;
|
|
13
|
+
const defaultHeight = isFlowchart ? 8 : 4;
|
|
14
|
+
const newViz = {
|
|
15
|
+
id: randomUUID(),
|
|
16
|
+
...viz,
|
|
17
|
+
// Auto-position: calculate next available spot
|
|
18
|
+
x: (this.state.visualizations.length % 2) * 6,
|
|
19
|
+
y: Math.floor(this.state.visualizations.length / 2) * 4,
|
|
20
|
+
w: defaultWidth,
|
|
21
|
+
h: defaultHeight,
|
|
22
|
+
};
|
|
23
|
+
this.state.visualizations.push(newViz);
|
|
24
|
+
return newViz;
|
|
25
|
+
}
|
|
26
|
+
removeVisualization(id) {
|
|
27
|
+
const initialLength = this.state.visualizations.length;
|
|
28
|
+
this.state.visualizations = this.state.visualizations.filter((v) => v.id !== id);
|
|
29
|
+
return this.state.visualizations.length < initialLength;
|
|
30
|
+
}
|
|
31
|
+
clearCanvas() {
|
|
32
|
+
this.state.visualizations = [];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export const stateManager = new StateManager();
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gluip/chart-canvas-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for creating interactive visualizations (charts, diagrams, tables) through AI assistants",
|
|
5
|
+
"author": "Martijn",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"bin": {
|
|
10
|
+
"chart-canvas-mcp": "dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"README.md",
|
|
15
|
+
"LICENSE"
|
|
16
|
+
],
|
|
17
|
+
"keywords": [
|
|
18
|
+
"mcp",
|
|
19
|
+
"model-context-protocol",
|
|
20
|
+
"visualization",
|
|
21
|
+
"charts",
|
|
22
|
+
"echarts",
|
|
23
|
+
"mermaid",
|
|
24
|
+
"ai",
|
|
25
|
+
"llm"
|
|
26
|
+
],
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/martijn/chart-canvas.git"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsc",
|
|
33
|
+
"build:all": "npm run build && cd ../frontend && npm run build",
|
|
34
|
+
"dev": "tsx src/index.ts",
|
|
35
|
+
"dev:api": "tsx src/api-server.ts",
|
|
36
|
+
"start": "node dist/index.js",
|
|
37
|
+
"start:prod": "npm run build:all && node dist/index.js"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
41
|
+
"express": "^4.21.2",
|
|
42
|
+
"cors": "^2.8.5",
|
|
43
|
+
"open": "^10.1.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/express": "^5.0.0",
|
|
47
|
+
"@types/cors": "^2.8.17",
|
|
48
|
+
"@types/node": "^22.10.5",
|
|
49
|
+
"typescript": "^5.7.3",
|
|
50
|
+
"tsx": "^4.19.2"
|
|
51
|
+
}
|
|
52
|
+
}
|