Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ COPY . .
RUN npm run build

# Command will be provided by smithery.yaml
CMD ["node", "build/index.js", "-t", "streamable"]
CMD ["node", "build/index.js", "-t", "streamable"]
45 changes: 14 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# MCP Server Chart ![](https://badge.mcpx.dev?type=server 'MCP Server') [![build](https://github.com/antvis/mcp-server-chart/actions/workflows/build.yml/badge.svg)](https://github.com/antvis/mcp-server-chart/actions/workflows/build.yml) [![npm Version](https://img.shields.io/npm/v/@antv/mcp-server-chart.svg)](https://www.npmjs.com/package/@antv/mcp-server-chart) [![smithery badge](https://smithery.ai/badge/@antvis/mcp-server-chart)](https://smithery.ai/server/@antvis/mcp-server-chart) [![npm License](https://img.shields.io/npm/l/@antv/mcp-server-chart.svg)](https://www.npmjs.com/package/@antv/mcp-server-chart) [![Trust Score](https://archestra.ai/mcp-catalog/api/badge/quality/antvis/mcp-server-chart)](https://archestra.ai/mcp-catalog/antvis__mcp-server-chart)
# MCP Server Chart ![](https://badge.mcpx.dev?type=server "MCP Server") [![build](https://github.com/antvis/mcp-server-chart/actions/workflows/build.yml/badge.svg)](https://github.com/antvis/mcp-server-chart/actions/workflows/build.yml) [![npm Version](https://img.shields.io/npm/v/@antv/mcp-server-chart.svg)](https://www.npmjs.com/package/@antv/mcp-server-chart) [![smithery badge](https://smithery.ai/badge/@antvis/mcp-server-chart)](https://smithery.ai/server/@antvis/mcp-server-chart) [![npm License](https://img.shields.io/npm/l/@antv/mcp-server-chart.svg)](https://www.npmjs.com/package/@antv/mcp-server-chart) [![Trust Score](https://archestra.ai/mcp-catalog/api/badge/quality/antvis/mcp-server-chart)](https://archestra.ai/mcp-catalog/antvis__mcp-server-chart)

A Model Context Protocol server for generating charts using [AntV](https://github.com/antvis/). We can use this mcp server for _chart generation_ and _data analysis_.

Expand Down Expand Up @@ -68,10 +68,7 @@ To use with `Desktop APP`, such as Claude, VSCode, [Cline](https://cline.bot/mcp
"mcpServers": {
"mcp-server-chart": {
"command": "npx",
"args": [
"-y",
"@antv/mcp-server-chart"
]
"args": ["-y", "@antv/mcp-server-chart"]
}
}
}
Expand All @@ -84,12 +81,7 @@ On Window system:
"mcpServers": {
"mcp-server-chart": {
"command": "cmd",
"args": [
"/c",
"npx",
"-y",
"@antv/mcp-server-chart"
]
"args": ["/c", "npx", "-y", "@antv/mcp-server-chart"]
}
}
}
Expand Down Expand Up @@ -143,28 +135,28 @@ Then you can access the server at:

## 🎮 CLI Options

You can also use the following CLI options when running the MCP server. Command options by run cli with `-h`.
You can also use the following CLI options when running the MCP server. Command options by run cli with `-H`.

```plain
MCP Server Chart CLI

Options:
--transport, -t Specify the transport protocol: "stdio", "sse", or "streamable" (default: "stdio")
--host, -h Specify the host for SSE or streamable transport (default: localhost)
--port, -p Specify the port for SSE or streamable transport (default: 1122)
--endpoint, -e Specify the endpoint for the transport:
- For SSE: default is "/sse"
- For streamable: default is "/mcp"
--help, -h Show this help message
--help, -H Show this help message
```

## ⚙️ Environment Variables

| Variable | Description | Default | Example |
|----------|:------------|---------|---------|
| `VIS_REQUEST_SERVER` | Custom chart generation service URL for private deployment | `https://antv-studio.alipay.com/api/gpt-vis` | `https://your-server.com/api/chart` |
| `SERVICE_ID` | Service identifier for chart generation records | - | `your-service-id-123` |
| `DISABLED_TOOLS` | Comma-separated list of tool names to disable | - | `generate_fishbone_diagram,generate_mind_map` |

| Variable | Description | Default | Example |
| -------------------- | :--------------------------------------------------------- | -------------------------------------------- | --------------------------------------------- |
| `VIS_REQUEST_SERVER` | Custom chart generation service URL for private deployment | `https://antv-studio.alipay.com/api/gpt-vis` | `https://your-server.com/api/chart` |
| `SERVICE_ID` | Service identifier for chart generation records | - | `your-service-id-123` |
| `DISABLED_TOOLS` | Comma-separated list of tool names to disable | - | `generate_fishbone_diagram,generate_mind_map` |

### 📠 Private Deployment

Expand All @@ -175,10 +167,7 @@ Options:
"mcpServers": {
"mcp-server-chart": {
"command": "npx",
"args": [
"-y",
"@antv/mcp-server-chart"
],
"args": ["-y", "@antv/mcp-server-chart"],
"env": {
"VIS_REQUEST_SERVER": "<YOUR_VIS_REQUEST_SERVER>"
}
Expand Down Expand Up @@ -214,10 +203,7 @@ Next, you need to add the `SERVICE_ID` environment variable to the MCP server co
"mcpServers": {
"AntV Map": {
"command": "npx",
"args": [
"-y",
"@antv/mcp-server-chart"
],
"args": ["-y", "@antv/mcp-server-chart"],
"env": {
"SERVICE_ID": "***********************************"
}
Expand All @@ -239,10 +225,7 @@ You can disable specific chart generation tools using the `DISABLED_TOOLS` envir
"mcpServers": {
"mcp-server-chart": {
"command": "npx",
"args": [
"-y",
"@antv/mcp-server-chart"
],
"args": ["-y", "@antv/mcp-server-chart"],
"env": {
"DISABLED_TOOLS": "generate_fishbone_diagram,generate_mind_map"
}
Expand Down
51 changes: 46 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@ import {
} from "./server";

// Parse command line arguments
const { values } = parseArgs({
const parsed = parseArgs({
options: {
transport: {
type: "string",
short: "t",
default: "stdio",
},
host: {
type: "boolean",
short: "h",
default: false,
},
port: {
type: "string",
short: "p",
Expand All @@ -26,10 +31,43 @@ const { values } = parseArgs({
},
help: {
type: "boolean",
short: "h",
short: "H",
},
},
allowPositionals: true,
tokens: true,
Comment thread
Unexpectedlyc marked this conversation as resolved.
});
// Initialize default host value
let host = "localhost";
// Flag to track if we're expecting a host value in the next token
let isHostValue = false;

// Iterate through parsed command line tokens to extract host value
for (const item of parsed.tokens) {
// If current token is the host option flag
if (item.kind === "option" && item.name === "host") {
isHostValue = true;
continue;
}
// If we're expecting a host value (previous token was --host flag)
if (isHostValue) {
// Deep copy the token to avoid reference issues
const temp = JSON.parse(JSON.stringify(item));
// Extract host value or default to "0.0.0.0"
host = temp.value || "0.0.0.0";
isHostValue = false;
break;
}
}

// If --host flag was set but no value followed it, use "0.0.0.0" as default
if (isHostValue) {
host = "0.0.0.0";
}

// Create a deep copy of parsed values and assign the determined host
const values = JSON.parse(JSON.stringify(parsed.values));
values.host = host;

// Display help information if requested
if (values.help) {
Expand All @@ -38,11 +76,12 @@ MCP Server Chart CLI

Options:
--transport, -t Specify the transport protocol: "stdio", "sse", or "streamable" (default: "stdio")
--host, -h Specify the host for SSE or streamable transport (default: localhost)
--port, -p Specify the port for SSE or streamable transport (default: 1122)
--endpoint, -e Specify the endpoint for the transport:
- For SSE: default is "/sse"
- For streamable: default is "/mcp"
--help, -h Show this help message
--help, -H Show this help message
Comment thread
Unexpectedlyc marked this conversation as resolved.
`);
process.exit(0);
}
Expand All @@ -54,12 +93,14 @@ if (transport === "sse") {
const port = Number.parseInt(values.port as string, 10);
// Use provided endpoint or default to "/sse" for SSE
const endpoint = values.endpoint || "/sse";
runSSEServer(endpoint, port).catch(console.error);
const host = values.host || "localhost";
runSSEServer(host, port, endpoint).catch(console.error);
} else if (transport === "streamable") {
const port = Number.parseInt(values.port as string, 10);
// Use provided endpoint or default to "/mcp" for streamable
const endpoint = values.endpoint || "/mcp";
runHTTPStreamableServer(endpoint, port).catch(console.error);
const host = values.host || "localhost";
runHTTPStreamableServer(host, port, endpoint).catch(console.error);
} else {
runStdioServer().catch(console.error);
}
10 changes: 6 additions & 4 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,19 +78,21 @@ export async function runStdioServer(): Promise<void> {
* Runs the server with SSE transport.
*/
export async function runSSEServer(
endpoint = "/sse",
host = "localhost",
port = 1122,
endpoint = "/sse",
): Promise<void> {
const server = createServer();
await startSSEMcpServer(server, endpoint, port);
await startSSEMcpServer(server, endpoint, port, host);
Comment thread
Unexpectedlyc marked this conversation as resolved.
}

/**
* Runs the server with HTTP streamable transport.
*/
export async function runHTTPStreamableServer(
endpoint = "/mcp",
host = "localhost",
port = 1122,
endpoint = "/mcp",
): Promise<void> {
await startHTTPStreamableServer(createServer, endpoint, port);
await startHTTPStreamableServer(createServer, endpoint, port, host);
}
24 changes: 13 additions & 11 deletions src/services/sse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,40 @@ export const startSSEMcpServer = async (
server: Server,
endpoint = "/sse",
port = 1122,
host = "localhost",
): Promise<void> => {
const app = express();
app.use(express.json());

const transports: Record<string, SSEServerTransport> = {};

app.get(endpoint, async (req, res) => {
try {
const transport = new SSEServerTransport('/messages', res);
const transport = new SSEServerTransport("/messages", res);
transports[transport.sessionId] = transport;
transport.onclose = () => delete transports[transport.sessionId];
await server.connect(transport);
} catch (error) {
if (!res.headersSent) res.status(500).send('Error establishing SSE stream');
if (!res.headersSent)
res.status(500).send("Error establishing SSE stream");
}
});

app.post('/messages', async (req, res) => {
app.post("/messages", async (req, res) => {
const sessionId = req.query.sessionId as string;
if (!sessionId) return res.status(400).send('Missing sessionId parameter');
if (!sessionId) return res.status(400).send("Missing sessionId parameter");

const transport = transports[sessionId];
if (!transport) return res.status(404).send('Session not found');
if (!transport) return res.status(404).send("Session not found");

try {
await transport.handlePostMessage(req, res, req.body);
} catch (error) {
if (!res.headersSent) res.status(500).send('Error handling request');
if (!res.headersSent) res.status(500).send("Error handling request");
}
});

app.listen(port, () => {
console.log(`SSE Server listening on http://localhost:${port}${endpoint}`);
app.listen(port, host, () => {
console.log(`SSE Server listening on http://${host}:${port}${endpoint}`);
});
};
21 changes: 13 additions & 8 deletions src/services/streamable.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
import type { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import express from "express";
import cors from "cors";
import express from "express";

export const startHTTPStreamableServer = async (
createServer: () => Server,
endpoint = "/mcp",
port = 1122,
host = "localhost",
): Promise<void> => {
const app = express();
app.use(express.json());
app.use(cors({ origin: '*', exposedHeaders: ['Mcp-Session-Id'] }));
app.use(cors({ origin: "*", exposedHeaders: ["Mcp-Session-Id"] }));

app.post(endpoint, async (req, res) => {
try {
const server = createServer();
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
res.on('close', () => {
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
});
res.on("close", () => {
transport.close();
server.close();
});
Expand All @@ -25,8 +28,8 @@ export const startHTTPStreamableServer = async (
} catch (error) {
if (!res.headersSent) {
res.status(500).json({
jsonrpc: '2.0',
error: { code: -32603, message: 'Internal server error' },
jsonrpc: "2.0",
error: { code: -32603, message: "Internal server error" },
id: null,
});
}
Expand All @@ -49,7 +52,9 @@ export const startHTTPStreamableServer = async (
});
});

app.listen(port, () => {
console.log(`Streamable HTTP Server listening on http://localhost:${port}${endpoint}`);
app.listen(port, host, () => {
console.log(
`Streamable HTTP Server listening on http://${host}:${port}${endpoint}`,
);
});
};