Skip to content

Commit 96453b1

Browse files
Susuperlihustcc
andauthored
feat: migrate HTTP servers to use Express.js (#179)
* feat: migrate HTTP servers to use Express.js - Replace raw Node.js HTTP servers with Express.js framework - Add comprehensive Express server utility with CORS, health checks, and error handling - Refactor SSE and Streamable services to use Express routes - Improve server logging with endpoint information - Add proper TypeScript types for Express middleware - Maintain full backward compatibility with existing MCP protocol - Fix parameter validation examples and documentation Resolves #178 * refactor: optimize Express migration - reduce code duplication - Remove unused httpServer.ts file (149 lines removed) - Consolidate CORS, middleware and endpoints setup into single function - Simplify Express server utility by combining duplicate code - Remove redundant exports and clean up utils/index.ts - Maintain all functionality while reducing overall code size * refactor: simplify Express implementation following mcp-echarts pattern * refactor: simplify Express-based MCP services following official SDK patterns - Streamline SSE service - Streamline Streamable service - Follow official TypeScript SDK examples for minimal implementations - Add cors dependency for proper browser support * refactor: remove unused InMemoryEventStore and getBody utility functions * Update package.json --------- Co-authored-by: hustcc <i@hust.cc>
1 parent 5eb9999 commit 96453b1

File tree

7 files changed

+86
-560
lines changed

7 files changed

+86
-560
lines changed

package.json

Lines changed: 16 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.8.3",
4+
"version": "0.9.0-beta.0",
55
"main": "build/index.js",
66
"types": "build/index.d.ts",
77
"exports": {
@@ -30,8 +30,17 @@
3030
"registry": "https://registry.npmjs.org/",
3131
"access": "public"
3232
},
33-
"files": ["build"],
34-
"keywords": ["antv", "mcp", "data-visualization", "chart", "graph", "map"],
33+
"files": [
34+
"build"
35+
],
36+
"keywords": [
37+
"antv",
38+
"mcp",
39+
"data-visualization",
40+
"chart",
41+
"graph",
42+
"map"
43+
],
3544
"repository": {
3645
"type": "git",
3746
"url": "https://github.com/antvis/mcp-server-chart"
@@ -44,12 +53,16 @@
4453
"dependencies": {
4554
"@modelcontextprotocol/sdk": "^1.11.4",
4655
"axios": "^1.11.0",
56+
"cors": "^2.8.5",
57+
"express": "^5.1.0",
4758
"zod": "^3.25.16",
4859
"zod-to-json-schema": "^3.24.5"
4960
},
5061
"devDependencies": {
5162
"@biomejs/biome": "1.9.4",
5263
"@modelcontextprotocol/inspector": "^0.14.2",
64+
"@types/cors": "^2.8.19",
65+
"@types/express": "^5.0.3",
5366
"@types/node": "^22.15.21",
5467
"husky": "^9.1.7",
5568
"lint-staged": "^15.5.2",

src/services/sse.ts

Lines changed: 29 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,107 +1,43 @@
1-
import type { IncomingMessage, ServerResponse } from "node:http";
21
import type { Server } from "@modelcontextprotocol/sdk/server/index.js";
32
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
4-
import { type RequestHandlers, createBaseHttpServer } from "../utils";
3+
import express from "express";
54

65
export const startSSEMcpServer = async (
76
server: Server,
87
endpoint = "/sse",
98
port = 1122,
109
): Promise<void> => {
11-
const activeTransports: Record<string, SSEServerTransport> = {};
12-
13-
// Define the request handler for SSE-specific logic
14-
const handleRequest: RequestHandlers["handleRequest"] = async (
15-
req: IncomingMessage,
16-
res: ServerResponse,
17-
) => {
18-
if (!req.url) {
19-
res.writeHead(400).end("No URL");
20-
return;
21-
}
22-
23-
const reqUrl = new URL(req.url, "http://localhost");
24-
25-
// Handle GET requests to the SSE endpoint
26-
if (req.method === "GET" && reqUrl.pathname === endpoint) {
27-
const transport = new SSEServerTransport("/messages", res);
28-
29-
activeTransports[transport.sessionId] = transport;
30-
31-
let closed = false;
32-
33-
res.on("close", async () => {
34-
closed = true;
35-
36-
try {
37-
await server.close();
38-
} catch (error) {
39-
console.error("Error closing server:", error);
40-
}
41-
42-
delete activeTransports[transport.sessionId];
43-
});
44-
45-
try {
46-
await server.connect(transport);
47-
48-
await transport.send({
49-
jsonrpc: "2.0",
50-
method: "sse/connection",
51-
params: { message: "SSE Connection established" },
52-
});
53-
} catch (error) {
54-
if (!closed) {
55-
console.error("Error connecting to server:", error);
56-
57-
res.writeHead(500).end("Error connecting to server");
58-
}
59-
}
60-
61-
return;
10+
const app = express();
11+
app.use(express.json());
12+
13+
const transports: Record<string, SSEServerTransport> = {};
14+
15+
app.get(endpoint, async (req, res) => {
16+
try {
17+
const transport = new SSEServerTransport('/messages', res);
18+
transports[transport.sessionId] = transport;
19+
transport.onclose = () => delete transports[transport.sessionId];
20+
await server.connect(transport);
21+
} catch (error) {
22+
if (!res.headersSent) res.status(500).send('Error establishing SSE stream');
6223
}
24+
});
6325

64-
// Handle POST requests to the messages endpoint
65-
if (req.method === "POST" && req.url?.startsWith("/messages")) {
66-
const sessionId = new URL(
67-
req.url,
68-
"https://example.com",
69-
).searchParams.get("sessionId");
70-
71-
if (!sessionId) {
72-
res.writeHead(400).end("No sessionId");
73-
return;
74-
}
75-
76-
const activeTransport: SSEServerTransport | undefined =
77-
activeTransports[sessionId];
78-
79-
if (!activeTransport) {
80-
res.writeHead(400).end("No active transport");
81-
return;
82-
}
83-
84-
await activeTransport.handlePostMessage(req, res);
85-
return;
86-
}
87-
88-
// If we reach here, no handler matched
89-
res.writeHead(404).end("Not found");
90-
};
91-
92-
// Custom cleanup for SSE server
93-
const cleanup = () => {
94-
// Close all active transports
95-
for (const transport of Object.values(activeTransports)) {
96-
transport.close();
26+
app.post('/messages', async (req, res) => {
27+
const sessionId = req.query.sessionId as string;
28+
if (!sessionId) return res.status(400).send('Missing sessionId parameter');
29+
30+
const transport = transports[sessionId];
31+
if (!transport) return res.status(404).send('Session not found');
32+
33+
try {
34+
await transport.handlePostMessage(req, res, req.body);
35+
} catch (error) {
36+
if (!res.headersSent) res.status(500).send('Error handling request');
9737
}
98-
server.close();
99-
};
38+
});
10039

101-
// Create the HTTP server using our factory
102-
createBaseHttpServer(port, endpoint, {
103-
handleRequest,
104-
cleanup,
105-
serverType: "SSE Server",
40+
app.listen(port, () => {
41+
console.log(`SSE Server listening on http://localhost:${port}${endpoint}`);
10642
});
10743
};

0 commit comments

Comments
 (0)