yszl-mcp
Version:
Scenic Area Tourist Flow Query Server with official MCP SDK
298 lines (272 loc) • 8.23 kB
JavaScript
/**
* 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);
}
}