Skip to content

Commit b2046fe

Browse files
Susuperlihustcc
andauthored
feat: migrate HTTP servers to use Express.js (#12)
* refactor(server): refactor server implementation to simplify code and improve maintainability - Remove complex request handling logic, switch to Express framework - Remove unnecessary cleanup functions and server factory - Simplify SSE and Streamable server implementations - Add CORS support - Optimize error handling and response format * refactor(utils): remove unused utility classes - Remove unused utility classes: InMemoryEventStore, cleanupHelpers, getBody, httpServer, and shutdownManager - Keep utility classes that are still in use: zodToJsonSchema, renderMermaid, and logger * docs(README): Update the run guide and development instructions - Add detailed steps for local development and global installation - Add startup methods for different transport protocols - Update access point information - Add new npm scripts in package.json * chore: bump version from 0.1.3 to 0.2.0 --------- Co-authored-by: hustcc <i@hust.cc>
1 parent ee4b382 commit b2046fe

12 files changed

Lines changed: 128 additions & 689 deletions

File tree

README.md

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@ Also, you can use it on aliyun, modelscope, glama.ai, smithery.ai or others with
5353

5454
## 🚰 Run with SSE or Streamable transport
5555

56-
Install the package globally.
56+
### Option 1: Global Installation
57+
58+
Install the package globally:
5759

5860
```bash
5961
npm install -g mcp-mermaid
@@ -69,10 +71,28 @@ mcp-mermaid -t sse
6971
mcp-mermaid -t streamable
7072
```
7173

74+
### Option 2: Local Development
75+
76+
If you're working with the source code locally:
77+
78+
```bash
79+
# Clone and setup
80+
git clone https://github.com/hustcc/mcp-mermaid.git
81+
cd mcp-mermaid
82+
npm install
83+
npm run build
84+
85+
# Run with npm scripts
86+
npm run start:sse # SSE transport on port 3033
87+
npm run start:streamable # Streamable transport on port 1122
88+
```
89+
90+
### Access Points
91+
7292
Then you can access the server at:
73-
- SSE transport: `http://localhost:3033/sse`
74-
- Streamable transport: `http://localhost:3033/mcp`
7593

94+
- SSE transport: `http://localhost:3033/sse`
95+
- Streamable transport: `http://localhost:1122/mcp` (local) or `http://localhost:3033/mcp` (global)
7696

7797
## 🎮 CLI Options
7898

@@ -90,7 +110,6 @@ Options:
90110
--help, -h Show this help message
91111
```
92112

93-
94113
## 🔨 Development
95114

96115
Install dependencies:
@@ -105,12 +124,36 @@ Build the server:
105124
npm run build
106125
```
107126

108-
Start the MCP server:
127+
### Start the MCP server
128+
129+
**Using MCP Inspector (for debugging):**
109130

110131
```bash
111132
npm run start
112133
```
113134

135+
**Using different transport protocols:**
136+
137+
```bash
138+
# SSE transport (Server-Sent Events)
139+
npm run start:sse
140+
141+
# Streamable HTTP transport
142+
npm run start:streamable
143+
```
144+
145+
**Direct node commands:**
146+
147+
```bash
148+
# SSE transport on port 3033
149+
node build/index.js --transport sse --port 3033
150+
151+
# Streamable HTTP transport on port 1122
152+
node build/index.js --transport streamable --port 1122
153+
154+
# STDIO transport (for MCP client integration)
155+
node build/index.js --transport stdio
156+
```
114157

115158
## 📄 License
116159

package.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
{
22
"name": "mcp-mermaid",
33
"description": "❤️ Generate mermaid diagram and chart with AI MCP dynamically.",
4-
"version": "0.1.3",
4+
"version": "0.2.0",
55
"main": "build/index.js",
66
"type": "module",
77
"scripts": {
88
"test": "vitest",
99
"prebuild": "rm -rf build/*",
1010
"build": "tsc && tsc-alias -p tsconfig.json",
1111
"start": "npx @modelcontextprotocol/inspector node build/index.js",
12+
"start:sse": "node build/index.js --transport sse --port 3033",
13+
"start:streamable": "node build/index.js --transport streamable --port 1122",
1214
"prepare": "husky && npm run build",
1315
"prepublishOnly": "npm run build",
1416
"postinstall": "playwright install --with-deps chromium"
@@ -20,6 +22,8 @@
2022
"keywords": ["mcp", "mermaid", "data-visualization", "chart", "diagram"],
2123
"dependencies": {
2224
"@modelcontextprotocol/sdk": "^1.11.4",
25+
"cors": "^2.8.5",
26+
"express": "^5.1.0",
2327
"mermaid-isomorphic": "^3.0.4",
2428
"playwright": "^1.52.0",
2529
"zod": "^3.25.16",
@@ -28,6 +32,8 @@
2832
"devDependencies": {
2933
"@biomejs/biome": "1.9.4",
3034
"@modelcontextprotocol/inspector": "^0.12.0",
35+
"@types/cors": "^2.8.19",
36+
"@types/express": "^5.0.3",
3137
"@types/node": "^22.15.21",
3238
"husky": "^9.1.7",
3339
"lint-staged": "^15.5.2",

src/server.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@ import {
1212
} from "./services";
1313
import { schema, tool } from "./tools";
1414
import { renderMermaid } from "./utils";
15-
import { createServerCleanup } from "./utils/cleanupHelpers";
1615
import { Logger } from "./utils/logger";
17-
import { shutdownManager } from "./utils/shutdownManager";
1816

1917
/**
2018
* Creates and configures an MCP server for mermaid generation.
@@ -36,9 +34,6 @@ export function createServer(): Server {
3634

3735
server.onerror = (error) => Logger.error("MCP Error", error);
3836

39-
// Register server cleanup with shutdown manager
40-
shutdownManager.registerCleanup(createServerCleanup(server));
41-
4237
return server;
4338
}
4439

src/services/sse.ts

Lines changed: 26 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,110 +1,45 @@
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";
5-
import { Logger } from "../utils/logger";
3+
import type { Request, Response } from "express";
4+
import express from "express";
65

76
export const startSSEMcpServer = async (
87
server: Server,
98
endpoint = "/sse",
109
port = 3033,
1110
): Promise<void> => {
12-
const activeTransports: Record<string, SSEServerTransport> = {};
11+
const app = express();
12+
app.use(express.json());
1313

14-
// Define the request handler for SSE-specific logic
15-
const handleRequest: RequestHandlers["handleRequest"] = async (
16-
req: IncomingMessage,
17-
res: ServerResponse,
18-
) => {
19-
if (!req.url) {
20-
res.writeHead(400).end("No URL");
21-
return;
22-
}
23-
24-
const reqUrl = new URL(req.url, "http://localhost");
14+
const transports: Record<string, SSEServerTransport> = {};
2515

26-
// Handle GET requests to the SSE endpoint
27-
if (req.method === "GET" && reqUrl.pathname === endpoint) {
16+
app.get(endpoint, async (req: Request, res: Response) => {
17+
try {
2818
const transport = new SSEServerTransport("/messages", res);
29-
30-
activeTransports[transport.sessionId] = transport;
31-
32-
let closed = false;
33-
34-
res.on("close", async () => {
35-
closed = true;
36-
37-
try {
38-
await server.close();
39-
} catch (error) {
40-
Logger.error("Error closing server", error);
41-
}
42-
43-
delete activeTransports[transport.sessionId];
44-
});
45-
46-
try {
47-
await server.connect(transport);
48-
49-
await transport.send({
50-
jsonrpc: "2.0",
51-
method: "sse/connection",
52-
params: { message: "SSE Connection established" },
53-
});
54-
} catch (error) {
55-
if (!closed) {
56-
Logger.error("Error connecting to server", error);
57-
res.writeHead(500).end("Error connecting to server");
58-
}
59-
}
60-
61-
return;
19+
transports[transport.sessionId] = transport;
20+
transport.onclose = () => delete transports[transport.sessionId];
21+
await server.connect(transport);
22+
} catch (error) {
23+
if (!res.headersSent)
24+
res.status(500).send("Error establishing SSE stream");
6225
}
26+
});
6327

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-
}
28+
app.post("/messages", async (req: Request, res: Response) => {
29+
const sessionId = req.query.sessionId as string;
30+
if (!sessionId) return res.status(400).send("Missing sessionId parameter");
8731

88-
// If we reach here, no handler matched
89-
res.writeHead(404).end("Not found");
90-
};
32+
const transport = transports[sessionId];
33+
if (!transport) return res.status(404).send("Session not found");
9134

92-
// Custom cleanup for SSE server
93-
const cleanup = () => {
94-
// Close all active transports
95-
for (const transport of Object.values(activeTransports)) {
96-
try {
97-
transport.close();
98-
} catch (error) {
99-
Logger.error("Error closing SSE transport", error);
100-
}
35+
try {
36+
await transport.handlePostMessage(req, res, req.body);
37+
} catch (error) {
38+
if (!res.headersSent) res.status(500).send("Error handling request");
10139
}
102-
};
40+
});
10341

104-
// Create the HTTP server using our factory
105-
createBaseHttpServer(port, endpoint, {
106-
handleRequest,
107-
cleanup,
108-
serverType: "SSE Server",
42+
app.listen(port, () => {
43+
console.log(`SSE Server listening on http://localhost:${port}${endpoint}`);
10944
});
11045
};

src/services/stdio.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
11
import type { Server } from "@modelcontextprotocol/sdk/server/index.js";
22
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3-
import { createTransportCleanup } from "../utils/cleanupHelpers";
4-
import { shutdownManager } from "../utils/shutdownManager";
53

64
export async function startStdioMcpServer(server: Server): Promise<void> {
75
const transport = new StdioServerTransport();
8-
9-
// Register transport cleanup
10-
shutdownManager.registerCleanup(createTransportCleanup(transport));
11-
126
await server.connect(transport);
7+
console.log("STDIO MCP Server started");
138
}

0 commit comments

Comments
 (0)