Skip to content

Commit 375eb86

Browse files
authored
optimize: code of --host and add logger (#211)
* refactor: optimize --host code * feat: update _meta description * refactor: server code * feat: add logger
1 parent bd46b3c commit 375eb86

File tree

7 files changed

+104
-72
lines changed

7 files changed

+104
-72
lines changed

src/index.ts

Lines changed: 1 addition & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
} from "./server";
88

99
// Parse command line arguments
10-
const parsed = parseArgs({
10+
const { values } = parseArgs({
1111
options: {
1212
transport: {
1313
type: "string",
@@ -24,7 +24,6 @@ const parsed = parseArgs({
2424
short: "p",
2525
default: "1122",
2626
},
27-
2827
endpoint: {
2928
type: "string",
3029
short: "e",
@@ -35,40 +34,7 @@ const parsed = parseArgs({
3534
short: "H",
3635
},
3736
},
38-
allowPositionals: true,
39-
tokens: true,
4037
});
41-
// Initialize default host value
42-
let host = "localhost";
43-
// Flag to track if we're expecting a host value in the next token
44-
let isHostValue = false;
45-
46-
// Iterate through parsed command line tokens to extract host value
47-
for (const item of parsed.tokens) {
48-
// If current token is the host option flag
49-
if (item.kind === "option" && item.name === "host") {
50-
isHostValue = true;
51-
continue;
52-
}
53-
// If we're expecting a host value (previous token was --host flag)
54-
if (isHostValue) {
55-
// Deep copy the token to avoid reference issues
56-
const temp = JSON.parse(JSON.stringify(item));
57-
// Extract host value or default to "0.0.0.0"
58-
host = temp.value || "0.0.0.0";
59-
isHostValue = false;
60-
break;
61-
}
62-
}
63-
64-
// If --host flag was set but no value followed it, use "0.0.0.0" as default
65-
if (isHostValue) {
66-
host = "0.0.0.0";
67-
}
68-
69-
// Create a deep copy of parsed values and assign the determined host
70-
const values = JSON.parse(JSON.stringify(parsed.values));
71-
values.host = host;
7238

7339
// Display help information if requested
7440
if (values.help) {

src/server.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
} from "./services";
1212
import { callTool } from "./utils/callTool";
1313
import { getDisabledTools } from "./utils/env";
14+
import { logger } from "./utils/logger";
1415

1516
/**
1617
* Creates and configures an MCP server for chart generation.
@@ -30,8 +31,12 @@ export function createServer(): Server {
3031

3132
setupToolHandlers(server);
3233

33-
server.onerror = (error: Error) => console.error("[MCP Error]", error);
34+
server.onerror = (e: Error) => {
35+
logger.error("Server encountered an error, shutting down", e);
36+
};
37+
3438
process.on("SIGINT", async () => {
39+
logger.info("SIGINT received, shutting down server...");
3540
await server.close();
3641
process.exit(0);
3742
});
@@ -57,13 +62,18 @@ function getEnabledTools() {
5762
* Sets up tool handlers for the MCP server.
5863
*/
5964
function setupToolHandlers(server: Server): void {
65+
logger.info("setting up tool handlers...");
6066
server.setRequestHandler(ListToolsRequestSchema, async () => ({
6167
tools: getEnabledTools().map((chart) => chart.tool),
6268
}));
6369

70+
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
6471
server.setRequestHandler(CallToolRequestSchema, async (request: any) => {
72+
logger.info("calling tool", request.params.name, request.params.arguments);
73+
6574
return await callTool(request.params.name, request.params.arguments);
6675
});
76+
logger.info("tool handlers set up");
6777
}
6878

6979
/**

src/services/sse.ts

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Server } from "@modelcontextprotocol/sdk/server/index.js";
22
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
3-
import express, { Request, Response } from "express";
3+
import express, { type Request, type Response } from "express";
4+
import { logger } from "../utils/logger";
45

56
export const startSSEMcpServer = async (
67
server: Server,
@@ -14,32 +15,41 @@ export const startSSEMcpServer = async (
1415
const transports: Record<string, SSEServerTransport> = {};
1516

1617
app.get(endpoint, async (req: Request, res: Response) => {
17-
try {
18-
const transport = new SSEServerTransport("/messages", res);
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");
25-
}
18+
const transport = new SSEServerTransport("/messages", res);
19+
transports[transport.sessionId] = transport;
20+
21+
transport.onclose = () => {
22+
delete transports[transport.sessionId];
23+
logger.info(`SSE Server disconnected: sessionId=${transport.sessionId}`);
24+
};
25+
26+
await server.connect(transport);
27+
logger.info(`SSE Server connected: sessionId=${transport.sessionId}`);
2628
});
2729

28-
app.post('/messages', async (req: Request, res: Response) => {
30+
app.post("/messages", async (req: Request, res: Response) => {
2931
const sessionId = req.query.sessionId as string;
30-
if (!sessionId) return res.status(400).send("Missing sessionId parameter");
32+
if (!sessionId) {
33+
logger.warn("SSE Server sessionId parameter is missing");
34+
return res.status(400).send("Missing sessionId parameter");
35+
}
3136

3237
const transport = transports[sessionId];
33-
if (!transport) return res.status(404).send("Session not found");
38+
if (!transport) {
39+
logger.warn(`SSE Server session not found: sessionId=${sessionId}`);
40+
return res.status(404).send("Session not found");
41+
}
3442

3543
try {
44+
logger.info(`SSE Server handling message: sessionId=${sessionId}`);
3645
await transport.handlePostMessage(req, res, req.body);
37-
} catch (error) {
46+
} catch (e) {
47+
logger.error("SSE Server error handling message", e);
3848
if (!res.headersSent) res.status(500).send("Error handling request");
3949
}
4050
});
4151

4252
app.listen(port, host, () => {
43-
console.log(`SSE Server listening on http://${host}:${port}${endpoint}`);
53+
logger.success(`SSE Server listening on http://${host}:${port}${endpoint}`);
4454
});
4555
};

src/services/stdio.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import type { Server } from "@modelcontextprotocol/sdk/server/index.js";
22
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3+
import { logger } from "../utils/logger";
34

45
export async function startStdioMcpServer(server: Server): Promise<void> {
56
const transport = new StdioServerTransport();
67
await server.connect(transport);
8+
logger.success("Stdio MCP Server started");
79
}

src/services/streamable.ts

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import type { Server } from "@modelcontextprotocol/sdk/server/index.js";
22
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
3-
import express, { Request, Response } from "express";
43
import cors from "cors";
4+
import express, { type Request, type Response } from "express";
5+
import { logger } from "../utils/logger";
56

67
export const startHTTPStreamableServer = async (
78
createServer: () => Server,
@@ -14,18 +15,27 @@ export const startHTTPStreamableServer = async (
1415
app.use(cors({ origin: "*", exposedHeaders: ["Mcp-Session-Id"] }));
1516

1617
app.post(endpoint, async (req: Request, res: Response) => {
18+
// In stateless mode, create a new transport for each request to prevent
19+
// request ID collisions. Different clients may use the same JSON-RPC request IDs,
20+
// which would cause responses to be routed to the wrong HTTP connections if
21+
// the transport state is shared.
1722
try {
1823
const server = createServer();
1924
const transport = new StreamableHTTPServerTransport({
2025
sessionIdGenerator: undefined,
26+
enableJsonResponse: true,
2127
});
28+
2229
res.on("close", () => {
2330
transport.close();
24-
server.close();
31+
logger.info("HTTP Streamable Server response closed");
2532
});
33+
2634
await server.connect(transport);
2735
await transport.handleRequest(req, res, req.body);
28-
} catch (error) {
36+
logger.info("HTTP Streamable Server response connected");
37+
} catch (e) {
38+
logger.error("HTTP Streamable Server response error", e);
2939
if (!res.headersSent) {
3040
res.status(500).json({
3141
jsonrpc: "2.0",
@@ -36,24 +46,8 @@ export const startHTTPStreamableServer = async (
3646
}
3747
});
3848

39-
app.get(endpoint, (req: Request, res: Response) => {
40-
res.status(405).json({
41-
jsonrpc: "2.0",
42-
error: { code: -32000, message: "Method not allowed" },
43-
id: null,
44-
});
45-
});
46-
47-
app.delete(endpoint, (req: Request, res: Response) => {
48-
res.status(405).json({
49-
jsonrpc: "2.0",
50-
error: { code: -32000, message: "Method not allowed" },
51-
id: null,
52-
});
53-
});
54-
5549
app.listen(port, host, () => {
56-
console.log(
50+
logger.success(
5751
`Streamable HTTP Server listening on http://${host}:${port}${endpoint}`,
5852
);
5953
});

src/utils/callTool.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
22
import { z } from "zod";
33
import * as Charts from "../charts";
44
import { generateChartUrl, generateMap } from "./generate";
5+
import { logger } from "./logger";
56
import { ValidateError } from "./validator";
67

78
// Chart type mapping
@@ -40,9 +41,11 @@ const CHART_TYPE_MAP = {
4041
* @returns
4142
*/
4243
export async function callTool(tool: string, args: object = {}) {
44+
logger.info(`Calling tool: ${tool}`);
4345
const chartType = CHART_TYPE_MAP[tool as keyof typeof CHART_TYPE_MAP];
4446

4547
if (!chartType) {
48+
logger.error(`Unknown tool: ${tool}`);
4649
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${tool}.`);
4750
}
4851

@@ -55,6 +58,7 @@ export async function callTool(tool: string, args: object = {}) {
5558
// Use safeParse instead of parse and try-catch.
5659
const result = z.object(schema).safeParse(args);
5760
if (!result.success) {
61+
logger.error(`Invalid parameters: ${result.error.message}`);
5862
throw new McpError(
5963
ErrorCode.InvalidParams,
6064
`Invalid parameters: ${result.error.message}`,
@@ -75,6 +79,7 @@ export async function callTool(tool: string, args: object = {}) {
7579
}
7680

7781
const url = await generateChartUrl(chartType, args);
82+
logger.info(`Generated chart URL: ${url}`);
7883

7984
return {
8085
content: [
@@ -85,12 +90,15 @@ export async function callTool(tool: string, args: object = {}) {
8590
],
8691
_meta: {
8792
description:
88-
"Charts spec configuration, you can use this config to generate the corresponding chart.",
93+
"This is the chart's spec and configuration, which can be renderred to corresponding chart by AntV GPT-Vis chart components.",
8994
spec: { type: chartType, ...args },
9095
},
9196
};
9297
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
9398
} catch (error: any) {
99+
logger.error(
100+
`Failed to generate chart: ${error.message || "Unknown error"}.`,
101+
);
94102
if (error instanceof McpError) throw error;
95103
if (error instanceof ValidateError)
96104
throw new McpError(ErrorCode.InvalidParams, error.message);

src/utils/logger.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* Unified logger for consistent logging across the application
3+
*/
4+
const prefix = "[MCP-Server-Chart]";
5+
6+
/**
7+
* Log info message
8+
*/
9+
function info(message: string, ...args: unknown[]): void {
10+
console.log(`${prefix} ℹ️ ${message}`, ...args);
11+
}
12+
13+
/**
14+
* Log warning message
15+
*/
16+
function warn(message: string, ...args: unknown[]): void {
17+
console.warn(`${prefix} ⚠️ ${message}`, ...args);
18+
}
19+
20+
/**
21+
* Log error message
22+
*/
23+
function error(message: string, error?: unknown): void {
24+
console.error(`${prefix}${message}`, error || "");
25+
}
26+
27+
/**
28+
* Log success message
29+
*/
30+
function success(message: string, ...args: unknown[]): void {
31+
console.log(`${prefix}${message}`, ...args);
32+
}
33+
34+
/**
35+
* Logger object for backward compatibility
36+
*/
37+
export const logger = {
38+
info,
39+
warn,
40+
error,
41+
success,
42+
};

0 commit comments

Comments
 (0)