UNPKG

yszl-mcp

Version:

Scenic Area Tourist Flow Query Server with official MCP SDK

298 lines (272 loc) 8.23 kB
/** * Module for handling stdio communication protocol for MCP using JSON-RPC 2.0 */ import readline from 'readline'; export class StdioTransport { constructor(toolInstances) { this.toolInstances = toolInstances; this.rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: false }); // 跟踪是否已经发送了初始化消息 this.initialized = false; } /** * Start listening for stdio messages */ start() { // 发送初始化消息 this.sendInitMessage(); this.rl.on('line', async (line) => { try { // 添加空输入检查 if (!line || line.trim() === '') { console.error('Empty input received, waiting for valid JSON input...'); return; } // Parse incoming JSON message const message = JSON.parse(line); // Process the message const response = await this.processMessage(message); // Send response back via stdout console.log(JSON.stringify(response)); } catch (error) { // Handle parsing or processing errors console.error(`Error processing message: ${error.message}`); console.log(JSON.stringify({ jsonrpc: "2.0", id: "error-1", // 使用字符串ID,不使用null error: { code: -32603, message: error.message, data: { stack: error.stack } } })); } }); // Handle process exit process.on('SIGINT', () => this.close()); process.on('SIGTERM', () => this.close()); } /** * Send initialization message according to MCP protocol using JSON-RPC 2.0 */ sendInitMessage() { if (this.initialized) return; // 发送初始化消息,包含服务器信息和可用工具 console.log(JSON.stringify({ jsonrpc: "2.0", method: "mcp.initialize", id: "init-1", params: { version: "1.0", capabilities: { tools: [ { name: "TouristFlow", description: "Get real-time tourist flow information", input_schema: { type: "object", properties: { scId: { type: "string", description: "Scenic Area ID" } }, required: ["scId"] } }, { name: "CumulativeTouristFlow", description: "Get cumulative tourist flow statistics", input_schema: { type: "object", properties: { scId: { type: "string", description: "Scenic Area ID" } }, required: ["scId"] } }, { name: "DailyTouristFlow", description: "Get daily tourist flow statistics", input_schema: { type: "object", properties: { scId: { type: "string", description: "Scenic Area ID" }, day: { type: "string", description: "Date in YYYYMMDD format, e.g. 20220101" } }, required: ["scId", "day"] } }, { name: "ProvinceOrigin", description: "Get tourist province origin distribution", input_schema: { type: "object", properties: { scId: { type: "string", description: "Scenic Area ID" }, day: { type: "string", description: "Date in YYYYMMDD format, e.g. 20220101" } }, required: ["scId", "day"] } }, { name: "GenderPortrait", description: "Get tourist gender distribution", input_schema: { type: "object", properties: { scId: { type: "string", description: "Scenic Area ID" }, day: { type: "string", description: "Date in YYYYMMDD format, e.g. 20220101" } }, required: ["scId", "day"] } }, { name: "AgePortrait", description: "Get tourist age distribution", input_schema: { type: "object", properties: { scId: { type: "string", description: "Scenic Area ID" }, day: { type: "string", description: "Date in YYYYMMDD format, e.g. 20220101" } }, required: ["scId", "day"] } } ] } } })); this.initialized = true; } /** * Process incoming messages and route to appropriate tool using JSON-RPC 2.0 */ async processMessage(message) { // 检查是否是有效的JSON-RPC 2.0请求 if (message.jsonrpc !== "2.0" || !message.method) { return { jsonrpc: "2.0", id: message.id || "invalid-req-1", // 使用原id或默认字符串id error: { code: -32600, message: "Invalid Request" } }; } // 处理ping请求 if (message.method === "mcp.ping") { return { jsonrpc: "2.0", id: message.id || "ping-resp-1", // 确保id不为null result: { status: "ok" } }; } // 处理工具执行请求 if (message.method === "mcp.tool_call") { if (!message.params || !message.params.name) { return { jsonrpc: "2.0", id: message.id || "invalid-params-1", // 确保id不为null error: { code: -32602, message: "Invalid params: missing tool name" } }; } const { name, args } = message.params; try { // Find and execute the requested tool const result = await this.executeTool(name, args); // Return successful response return { jsonrpc: "2.0", id: message.id || "tool-result-1", // 确保id不为null result: { content: result } }; } catch (error) { // Return error response return { jsonrpc: "2.0", id: message.id || "tool-error-1", // 确保id不为null error: { code: -32603, message: error.message } }; } } // 未知方法 return { jsonrpc: "2.0", id: message.id || "unknown-method-1", // 确保id不为null error: { code: -32601, message: `Method not found: ${message.method}` } }; } /** * Execute the requested tool with provided inputs */ async executeTool(toolName, inputs) { // Normalize tool name to match instance properties const toolKey = `${toolName.charAt(0).toLowerCase() + toolName.slice(1)}Tool`; // Check if tool exists if (!this.toolInstances[toolKey]) { throw new Error(`Tool not found: ${toolName}`); } try { console.error(`Executing tool ${toolName} with inputs:`, inputs); // Execute the tool const result = await this.toolInstances[toolKey].execute(inputs); console.error(`Tool ${toolName} execution result:`, result); return result; } catch (error) { console.error(`Tool ${toolName} execution error:`, error); throw error; } } /** * Close the readline interface */ close() { this.rl.close(); process.exit(0); } }