Skip to content
Open
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
19 changes: 17 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,17 @@
"registry": "https://registry.npmjs.org/",
"access": "public"
},
"files": ["build"],
"keywords": ["antv", "mcp", "data-visualization", "chart", "graph", "map"],
"files": [
"build"
],
"keywords": [
"antv",
"mcp",
"data-visualization",
"chart",
"graph",
"map"
],
"repository": {
"type": "git",
"url": "https://github.com/antvis/mcp-server-chart"
Expand All @@ -50,6 +59,12 @@
"axios": "^1.11.0",
"cors": "^2.8.5",
"express": "^5.1.0",
"zod": "^3.25.16",
"zod-to-json-schema": "^3.24.5"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@modelcontextprotocol/inspector": "^0.14.2",
"zod": "^4.3.5"
},
"devDependencies": {
Expand Down
29 changes: 29 additions & 0 deletions src/services/sse.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import express from "express";
import express, { type Request, type Response } from "express";
import { logger } from "../utils/logger";

Expand All @@ -11,6 +12,17 @@ export const startSSEMcpServer = async (
): 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);
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');

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

Expand All @@ -35,7 +47,24 @@ export const startSSEMcpServer = async (
logger.warn("SSE Server sessionId parameter is missing");
return res.status(400).send("Missing sessionId parameter");
}
});

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

const transport = transports[sessionId];
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');
}
});

app.listen(port, () => {
console.log(`SSE Server listening on http://localhost:${port}${endpoint}`);
const transport = connections[sessionId];
if (!transport) {
logger.warn(`SSE Server session not found: sessionId=${sessionId}`);
Expand Down
40 changes: 40 additions & 0 deletions src/services/streamable.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
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 cors from "cors";
import express, { type Request, type Response } from "express";
import { logger } from "../utils/logger";
Expand All @@ -8,6 +10,26 @@ export const startHTTPStreamableServer = async (
createServer: () => Server,
endpoint = "/mcp",
port = 1122,
): Promise<void> => {
const app = express();
app.use(express.json());
app.use(cors({ origin: '*', exposedHeaders: ['Mcp-Session-Id'] }));

app.post(endpoint, async (req, res) => {
try {
const server = createServer();
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
res.on('close', () => {
transport.close();
server.close();
});
} catch (error) {
if (!res.headersSent) {
res.status(500).json({
jsonrpc: '2.0',
error: { code: -32603, message: 'Internal server error' },
host = "localhost",
): Promise<void> => {
const app = express();
Expand Down Expand Up @@ -46,6 +68,24 @@ export const startHTTPStreamableServer = async (
}
});

app.get(endpoint, (req, res) => {
res.status(405).json({
jsonrpc: "2.0",
error: { code: -32000, message: "Method not allowed" },
id: null
});
});

app.delete(endpoint, (req, res) => {
res.status(405).json({
jsonrpc: "2.0",
error: { code: -32000, message: "Method not allowed" },
id: null
});
});

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