UNPKG

yszl-mcp

Version:

Scenic Area Tourist Flow Query Server with official MCP SDK

494 lines (459 loc) 13.2 kB
/** * 景区客流查询服务 - 使用官方MCP SDK */ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; import dotenv from 'dotenv'; import express from 'express'; import cors from 'cors'; // 创建Express应用 const app = express(); // 启用CORS和JSON解析 app.use(cors()); app.use(express.json()); // 导入工具 import { TouristFlowTool } from './src/tools/touristFlowTool.js'; import { CumulativeTouristFlowTool } from './src/tools/cumulativeTouristFlowTool.js'; import { DailyTouristFlowTool } from './src/tools/dailyTouristFlowTool.js'; import { ProvinceOriginTool } from './src/tools/provinceOriginTool.js'; import { GenderPortraitTool } from './src/tools/genderPortraitTool.js'; import { AgePortraitTool } from './src/tools/agePortraitTool.js'; // 加载环境变量 console.log(JSON.stringify({ jsonrpc: "2.0", method: "log", params: { message: "Loading environment variables..." } })); dotenv.config(); // 设置默认的 API_URL if (!process.env.API_URL) { process.env.API_URL = 'https://api.zenstec.com/'; } // 检查必要的环境变量 const requiredEnvVars = ['ACCOUNT', 'APP_SECRET']; const missingEnvVars = requiredEnvVars.filter(varName => !process.env[varName]); if (missingEnvVars.length > 0) { console.log(JSON.stringify({ jsonrpc: "2.0", method: "error", params: { message: `Missing required environment variables: ${missingEnvVars.join(', ')}` } })); process.exit(1); } console.log(JSON.stringify({ jsonrpc: "2.0", method: "log", params: { message: "Environment variables loaded successfully" } })); // 定义服务器版本 const VERSION = "1.2.53"; // 创建工具实例 const touristFlowTool = new TouristFlowTool(); const cumulativeTouristFlowTool = new CumulativeTouristFlowTool(); const dailyTouristFlowTool = new DailyTouristFlowTool(); const provinceOriginTool = new ProvinceOriginTool(); const genderPortraitTool = new GenderPortraitTool(); const agePortraitTool = new AgePortraitTool(); // 创建工具映射 const tools = { TouristFlow: touristFlowTool, CumulativeTouristFlow: cumulativeTouristFlowTool, DailyTouristFlow: dailyTouristFlowTool, ProvinceOrigin: provinceOriginTool, GenderPortrait: genderPortraitTool, AgePortrait: agePortraitTool }; // 创建MCP服务器 const server = new McpServer({ name: "Tourist Flow Query Service", version: VERSION }); // 注册TouristFlow工具 server.tool( "TouristFlow", "获取指定景区的实时客流信息", { scenicId: z.string().describe("Scenic Area ID, e.g., F51018268") }, async ({ scenicId }) => { try { console.log(JSON.stringify({ jsonrpc: "2.0", method: "log", params: { message: `Executing TouristFlow tool with scenicId=${scenicId}` } })); const result = await touristFlowTool.execute({ scenicId }); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; } catch (error) { console.log(JSON.stringify({ jsonrpc: "2.0", method: "error", params: { message: "TouristFlow tool execution error", error: error.message } })); return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true }; } } ); // 注册CumulativeTouristFlow工具 server.tool( "CumulativeTouristFlow", "获取指定景区的当日累计客流信息", { scenicId: z.string().describe("Scenic Area ID, e.g., F51018268") }, async ({ scenicId }) => { try { console.log(JSON.stringify({ jsonrpc: "2.0", method: "log", params: { message: `Executing CumulativeTouristFlow tool with scenicId=${scenicId}` } })); const result = await cumulativeTouristFlowTool.execute({ scenicId }); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; } catch (error) { console.log(JSON.stringify({ jsonrpc: "2.0", method: "error", params: { message: "CumulativeTouristFlow tool execution error", error: error.message } })); return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true }; } } ); // 注册DailyTouristFlow工具 server.tool( "DailyTouristFlow", "获取指定景区的指定日期客流信息", { scenicId: z.string().describe("Scenic Area ID, e.g., F51018268"), date: z.string().describe("Date in YYYYMMDD format, e.g., 20250410") }, async ({ scenicId, date }) => { try { console.log(JSON.stringify({ jsonrpc: "2.0", method: "log", params: { message: `Executing DailyTouristFlow tool with scenicId=${scenicId}, date=${date}` } })); const result = await dailyTouristFlowTool.execute({ scenicId, date }); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; } catch (error) { console.log(JSON.stringify({ jsonrpc: "2.0", method: "error", params: { message: "DailyTouristFlow tool execution error", error: error.message } })); return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true }; } } ); // 注册ProvinceOrigin工具 server.tool( "ProvinceOrigin", "获取指定景区的指定日期客流省份分布信息", { scenicId: z.string().describe("Scenic Area ID, e.g., F51018268"), date: z.string().describe("Date in YYYYMMDD format, e.g., 20250410") }, async ({ scenicId, date }) => { try { console.log(JSON.stringify({ jsonrpc: "2.0", method: "log", params: { message: `Executing ProvinceOrigin tool with scenicId=${scenicId}, date=${date}` } })); const result = await provinceOriginTool.execute({ scenicId, date }); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; } catch (error) { console.log(JSON.stringify({ jsonrpc: "2.0", method: "error", params: { message: "ProvinceOrigin tool execution error", error: error.message } })); return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true }; } } ); // 注册GenderPortrait工具 server.tool( "GenderPortrait", "获取指定景区的指定日期客流性别分布信息", { scenicId: z.string().describe("Scenic Area ID, e.g., F51018268"), date: z.string().describe("Date in YYYYMMDD format, e.g., 20250410") }, async ({ scenicId, date }) => { try { console.log(JSON.stringify({ jsonrpc: "2.0", method: "log", params: { message: `Executing GenderPortrait tool with scenicId=${scenicId}, date=${date}` } })); const result = await genderPortraitTool.execute({ scenicId, date }); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; } catch (error) { console.log(JSON.stringify({ jsonrpc: "2.0", method: "error", params: { message: "GenderPortrait tool execution error", error: error.message } })); return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true }; } } ); // 注册AgePortrait工具 server.tool( "AgePortrait", "获取指定景区的指定日期客流年龄分布信息", { scenicId: z.string().describe("Scenic Area ID, e.g., F51018268"), date: z.string().describe("Date in YYYYMMDD format, e.g., 20250410") }, async ({ scenicId, date }) => { try { console.log(JSON.stringify({ jsonrpc: "2.0", method: "log", params: { message: `Executing AgePortrait tool with scenicId=${scenicId}, date=${date}` } })); const result = await agePortraitTool.execute({ scenicId, date }); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; } catch (error) { console.log(JSON.stringify({ jsonrpc: "2.0", method: "error", params: { message: "AgePortrait tool execution error", error: error.message } })); return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true }; } } ); console.log(JSON.stringify({ jsonrpc: "2.0", method: "log", params: { message: "Tools registration completed" } })); // 处理启动过程中的错误 function handleError(error) { console.log(JSON.stringify({ jsonrpc: "2.0", method: "error", params: { message: "Fatal error", error: error.message } })); process.exit(1); } // 启动MCP服务器的函数 export async function startServer() { try { console.log(JSON.stringify({ jsonrpc: "2.0", method: "log", params: { message: "Starting tourist flow query service..." } })); // 创建传输层 console.log(JSON.stringify({ jsonrpc: "2.0", method: "log", params: { message: "Creating transport layer..." } })); const transport = new StdioServerTransport(); console.log(JSON.stringify({ jsonrpc: "2.0", method: "log", params: { message: "Transport layer created" } })); // 启动服务器 console.log(JSON.stringify({ jsonrpc: "2.0", method: "log", params: { message: "Starting server..." } })); await server.connect(transport); console.log(JSON.stringify({ jsonrpc: "2.0", method: "log", params: { message: "Server started" } })); // 设置API路由 app.post('/tool/:toolName', async (req, res) => { const { toolName } = req.params; const params = req.body; console.log(JSON.stringify({ jsonrpc: "2.0", method: "log", params: { message: "Received tool execution request", toolName, params } })); // 根据工具名称调用对应的处理函数 let result; switch (toolName) { case 'TouristFlow': result = await touristFlowTool.execute(params); break; case 'CumulativeTouristFlow': result = await cumulativeTouristFlowTool.execute(params); break; case 'DailyTouristFlow': result = await dailyTouristFlowTool.execute(params); break; case 'ProvinceOrigin': result = await provinceOriginTool.execute(params); break; case 'GenderPortrait': result = await genderPortraitTool.execute(params); break; case 'AgePortrait': result = await agePortraitTool.execute(params); break; default: throw new Error(`Unknown tool: ${toolName}`); } res.json({ content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }); }); // 添加健康检查端点 app.get('/health', (req, res) => { res.json({ status: 'ok' }); }); // 启动HTTP服务器 // const PORT = process.env.PORT || 3000; // app.listen(PORT, () => { // console.log(JSON.stringify({ // jsonrpc: "2.0", // method: "log", // params: { // message: `HTTP server started on port ${PORT}` // } // })); // }); // // 输出服务启动完成信息 // console.log(JSON.stringify({ // jsonrpc: "2.0", // method: "log", // params: { // message: `Tourist flow query service started, version ${VERSION}`, // availableTools: ['TouristFlow', 'CumulativeTouristFlow', 'DailyTouristFlow', 'ProvinceOrigin', 'GenderPortrait', 'AgePortrait'] // } // })); return server; } catch (error) { handleError(error); } } // 捕获未处理的异常 process.on('uncaughtException', (error) => { console.log(JSON.stringify({ jsonrpc: "2.0", method: "error", params: { message: "Uncaught exception", error: error.message } })); // 不退出进程,保持服务运行 }); // 捕获未处理的Promise拒绝 process.on('unhandledRejection', (reason, promise) => { console.log(JSON.stringify({ jsonrpc: "2.0", method: "error", params: { message: "Unhandled promise rejection", reason: reason.message } })); // 不退出进程,保持服务运行 }); // 如果直接运行则启动服务器 if (import.meta.url === `file://${process.argv[1]}`) { console.log(JSON.stringify({ jsonrpc: "2.0", method: "log", params: { message: "Direct execution detected, starting server..." } })); startServer().catch(handleError); }