yszl-mcp
Version:
Scenic Area Tourist Flow Query Server with official MCP SDK
494 lines (459 loc) • 13.2 kB
JavaScript
/**
* 景区客流查询服务 - 使用官方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);
}