Skip to content

Commit 8e6c518

Browse files
committed
Feat: Add support for uploading generated images to MinIO
1 parent a810850 commit 8e6c518

7 files changed

Lines changed: 501 additions & 95 deletions

File tree

README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,45 @@ Options:
141141

142142
You can use AntV's project [GPT-Vis-SSR](https://github.com/antvis/GPT-Vis/tree/main/bindings/gpt-vis-ssr) to deploy an HTTP service in a private environment, and then pass the URL address through env `VIS_REQUEST_SERVER`.
143143

144+
You can also choose to upload the image to your MinIO server by configuring the following environment variables:
145+
146+
```json
147+
{
148+
"mcpServers": {
149+
"mcp-server-chart": {
150+
"command": "npx",
151+
"args": [
152+
"-y",
153+
"@antv/mcp-server-chart"
154+
],
155+
"env": {
156+
"VIS_GENERATE_STRATEGY": "minio",
157+
"MINIO_ENDPOINT": "<YOUR_MINIO_ENDPOINT>",
158+
"MINIO_PORT": "<YOUR_MINIO_PORT>",
159+
"MINIO_USE_SSL": "<USE_SSL>",
160+
"MINIO_ACCESS_KEY": "<YOUR_MINIO_ACCESS_KEY>",
161+
"MINIO_SECRET_KEY": "<YOUR_MINIO_SECRET_KEY>",
162+
"MINIO_BUCKET_NAME": "<YOUR_MINIO_BUCKET_NAME>",
163+
"MINIO_USE_PUBLIC_BUCKET": "<USE_PUBLIC_BUCKET>"
164+
}
165+
}
166+
}
167+
}
168+
```
169+
170+
- `VIS_GENERATE_STRATEGY`: Specifies the image service strategy.
171+
- Default: `antvis`
172+
- Available values: `antvis` | `minio`
173+
- Note: If using `minio`, you must configure the MINIO_xxx variables below.
174+
- `MINIO_ENDPOINT`: The address of the MinIO server (without port). Default: `127.0.0.1`.
175+
- `MINIO_PORT`: The port of the MinIO server. Default: `9000`.
176+
- `MINIO_USE_SSL`: Whether to use SSL for the MinIO server. Default: `false`.
177+
- `MINIO_ACCESS_KEY`: The access key for connecting to the MinIO server. Default: `minio`.
178+
- `MINIO_SECRET_KEY`: The secret key for connecting to the MinIO server. Default: `minioadmin`.
179+
- `MINIO_BUCKET_NAME`: The bucket used for uploads. If the bucket does not exist, it will be created automatically. Default: `mcp-server-chart`.
180+
- `MINIO_USE_PUBLIC_BUCKET`: Whether the bucket should allow public access. Default: `true`.
181+
- `MINIO_OBJECT_EXPIRY_SECONDS`: If `MINIO_USE_PUBLIC_BUCKET` is false, this sets the expiration time (in seconds) for objects. Default: 3600.
182+
144183
- **Method**: `POST`
145184
- **Parameter**: Which will be passed to `GPT-Vis-SSR` for renderring. Such as, `{ "type": "line", "data": [{ "time": "2025-05", "value": "512" }, { "time": "2025-06", "value": "1024" }] }`.
146185
- **Return**: The return object of HTTP service.

package.json

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@antv/mcp-server-chart",
33
"description": "A Model Context Protocol server for generating charts using AntV, This is a TypeScript-based MCP server that provides chart generation capabilities. It allows you to create various types of charts through MCP tools.",
4-
"version": "0.6.1",
4+
"version": "0.6.2",
55
"main": "build/index.js",
66
"types": "build/index.d.ts",
77
"exports": {
@@ -26,8 +26,15 @@
2626
"bin": {
2727
"mcp-server-chart": "./build/index.js"
2828
},
29-
"files": ["build"],
30-
"keywords": ["antv", "mcp", "data-visualization", "chart"],
29+
"files": [
30+
"build"
31+
],
32+
"keywords": [
33+
"antv",
34+
"mcp",
35+
"data-visualization",
36+
"chart"
37+
],
3138
"repository": {
3239
"type": "git",
3340
"url": "https://github.com/antvis/mcp-server-chart"
@@ -38,8 +45,12 @@
3845
},
3946
"license": "MIT",
4047
"dependencies": {
48+
"@antv/gpt-vis-ssr": "^0.1.6",
4149
"@modelcontextprotocol/sdk": "^1.11.4",
4250
"axios": "^1.9.0",
51+
"canvas": "^3.1.0",
52+
"minio": "^8.0.5",
53+
"uuid": "^11.1.0",
4354
"zod": "^3.25.16",
4455
"zod-to-json-schema": "^3.24.5"
4556
},

src/server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export function createServer(): Server {
1818
const server = new Server(
1919
{
2020
name: "mcp-server-chart",
21-
version: "0.6.1",
21+
version: "0.6.2",
2222
},
2323
{
2424
capabilities: {

src/utils/GenerateStrategy.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { generateChartUrl as antvisGenerate } from "./generate";
2+
import { MinioGenerator } from "./generateMinio";
3+
4+
enum GenerateStrategyType {
5+
Antvis = "ANTVIS",
6+
Minio = "MINIO",
7+
}
8+
9+
interface GenerateStrategy {
10+
generate(type: string, options: Record<string, any>): Promise<string>;
11+
}
12+
13+
class AntvisGenerateStrategy implements GenerateStrategy {
14+
async generate(
15+
type: string,
16+
options: Record<string, any>,
17+
): Promise<string> {
18+
return await antvisGenerate(type, options);
19+
}
20+
}
21+
22+
class MinioGenerateStrategy implements GenerateStrategy {
23+
async generate(
24+
type: string,
25+
options: Record<string, any>,
26+
): Promise<string> {
27+
const minioGenerator = MinioGenerator.getInstance();
28+
return await minioGenerator.generate(type, options);
29+
}
30+
}
31+
32+
const strategies: Record<GenerateStrategyType, GenerateStrategy> = {
33+
[GenerateStrategyType.Antvis]: new AntvisGenerateStrategy(),
34+
[GenerateStrategyType.Minio]: new MinioGenerateStrategy(),
35+
};
36+
37+
export function getStrategy(type: string): GenerateStrategy {
38+
const matchedStrategyType = Object.keys(GenerateStrategyType).find(
39+
(key) => key.toLowerCase() === type.toLowerCase(),
40+
);
41+
42+
if (matchedStrategyType) {
43+
const enumValue =
44+
GenerateStrategyType[
45+
matchedStrategyType as keyof typeof GenerateStrategyType
46+
];
47+
return strategies[enumValue];
48+
}
49+
50+
throw new Error(
51+
`Strategy "${type}" not found. Available strategies: ${Object.values(
52+
GenerateStrategyType,
53+
).join(", ")}`,
54+
);
55+
}

src/utils/callTool.ts

Lines changed: 84 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,84 @@
1-
import { ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
2-
import { z } from "zod";
3-
import * as Charts from "../charts";
4-
import { generateChartUrl } from "./generate";
5-
import { ValidateError } from "./validator";
6-
7-
// Chart type mapping
8-
const CHART_TYPE_MAP = {
9-
generate_area_chart: "area",
10-
generate_bar_chart: "bar",
11-
generate_boxplot_chart: "boxplot",
12-
generate_column_chart: "column",
13-
generate_dual_axes_chart: "dual-axes",
14-
generate_fishbone_diagram: "fishbone-diagram",
15-
generate_flow_diagram: "flow-diagram",
16-
generate_funnel_chart: "funnel",
17-
generate_histogram_chart: "histogram",
18-
generate_line_chart: "line",
19-
generate_liquid_chart: "liquid",
20-
generate_mind_map: "mind-map",
21-
generate_network_graph: "network-graph",
22-
generate_organization_chart: "organization-chart",
23-
generate_pie_chart: "pie",
24-
generate_radar_chart: "radar",
25-
generate_sankey_chart: "sankey",
26-
generate_scatter_chart: "scatter",
27-
generate_treemap_chart: "treemap",
28-
generate_venn_chart: "venn",
29-
generate_violin_chart: "violin",
30-
generate_word_cloud_chart: "word-cloud",
31-
} as const;
32-
33-
/**
34-
* Call a tool to generate a chart based on the provided name and arguments.
35-
* @param tool The name of the tool to call, e.g., "generate_area_chart".
36-
* @param args The arguments for the tool, which should match the expected schema for the chart type.
37-
* @returns
38-
*/
39-
export async function callTool(tool: string, args: object = {}) {
40-
const chartType = CHART_TYPE_MAP[tool as keyof typeof CHART_TYPE_MAP];
41-
42-
if (!chartType) {
43-
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${tool}.`);
44-
}
45-
46-
try {
47-
// Validate input using Zod before sending to API.
48-
// Select the appropriate schema based on the chart type.
49-
const schema = Charts[chartType].schema;
50-
51-
if (schema) {
52-
// Use safeParse instead of parse and try-catch.
53-
const result = z.object(schema).safeParse(args);
54-
if (!result.success) {
55-
throw new McpError(
56-
ErrorCode.InvalidParams,
57-
`Invalid parameters: ${result.error.message}`,
58-
);
59-
}
60-
}
61-
62-
const url = await generateChartUrl(chartType, args);
63-
64-
return {
65-
content: [
66-
{
67-
type: "text",
68-
text: url,
69-
},
70-
],
71-
};
72-
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
73-
} catch (error: any) {
74-
if (error instanceof McpError) throw error;
75-
if (error instanceof ValidateError)
76-
throw new McpError(ErrorCode.InvalidParams, error.message);
77-
throw new McpError(
78-
ErrorCode.InternalError,
79-
`Failed to generate chart: ${error?.message || "Unknown error."}`,
80-
);
81-
}
82-
}
1+
import { ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
2+
import { z } from "zod";
3+
import * as Charts from "../charts";
4+
import { getStrategy } from "./GenerateStrategy";
5+
import { getVisGenerateStrategy } from "./env";
6+
import { ValidateError } from "./validator";
7+
8+
// Chart type mapping
9+
const CHART_TYPE_MAP = {
10+
generate_area_chart: "area",
11+
generate_bar_chart: "bar",
12+
generate_boxplot_chart: "boxplot",
13+
generate_column_chart: "column",
14+
generate_dual_axes_chart: "dual-axes",
15+
generate_fishbone_diagram: "fishbone-diagram",
16+
generate_flow_diagram: "flow-diagram",
17+
generate_funnel_chart: "funnel",
18+
generate_histogram_chart: "histogram",
19+
generate_line_chart: "line",
20+
generate_liquid_chart: "liquid",
21+
generate_mind_map: "mind-map",
22+
generate_network_graph: "network-graph",
23+
generate_organization_chart: "organization-chart",
24+
generate_pie_chart: "pie",
25+
generate_radar_chart: "radar",
26+
generate_sankey_chart: "sankey",
27+
generate_scatter_chart: "scatter",
28+
generate_treemap_chart: "treemap",
29+
generate_venn_chart: "venn",
30+
generate_violin_chart: "violin",
31+
generate_word_cloud_chart: "word-cloud",
32+
} as const;
33+
34+
/**
35+
* Call a tool to generate a chart based on the provided name and arguments.
36+
* @param tool The name of the tool to call, e.g., "generate_area_chart".
37+
* @param args The arguments for the tool, which should match the expected schema for the chart type.
38+
* @returns
39+
*/
40+
export async function callTool(tool: string, args: object = {}) {
41+
const chartType = CHART_TYPE_MAP[tool as keyof typeof CHART_TYPE_MAP];
42+
43+
if (!chartType) {
44+
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${tool}.`);
45+
}
46+
47+
try {
48+
// Validate input using Zod before sending to API.
49+
// Select the appropriate schema based on the chart type.
50+
const schema = Charts[chartType].schema;
51+
52+
if (schema) {
53+
// Use safeParse instead of parse and try-catch.
54+
const result = z.object(schema).safeParse(args);
55+
if (!result.success) {
56+
throw new McpError(
57+
ErrorCode.InvalidParams,
58+
`Invalid parameters: ${result.error.message}`,
59+
);
60+
}
61+
}
62+
63+
const generateStrategy = getStrategy(getVisGenerateStrategy());
64+
const url = await generateStrategy.generate(chartType, args);
65+
66+
return {
67+
content: [
68+
{
69+
type: "text",
70+
text: url,
71+
},
72+
],
73+
};
74+
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
75+
} catch (error: any) {
76+
if (error instanceof McpError) throw error;
77+
if (error instanceof ValidateError)
78+
throw new McpError(ErrorCode.InvalidParams, error.message);
79+
throw new McpError(
80+
ErrorCode.InternalError,
81+
`Failed to generate chart: ${error?.message || "Unknown error."}`,
82+
);
83+
}
84+
}

0 commit comments

Comments
 (0)