Skip to main content
Glama

Caiyun Weather MCP Server

by marcusbai
index.ts20.4 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js'; import { CaiyunWeatherService } from './caiyun-service.js'; import { GeocodeService } from './geocode-service.js'; // 获取API密钥 // 1. 从环境变量获取 // 2. 从命令行参数获取 // 3. 如果都没有,提供明确的错误信息 let caiyunApiKey = process.env.CAIYUN_API_KEY; const AMAP_API_KEY = process.env.AMAP_API_KEY; // 检查命令行参数 if (!caiyunApiKey) { // 查找命令行参数中是否有API密钥 const apiKeyArg = process.argv.find(arg => arg.startsWith('--api-key=')); if (apiKeyArg) { caiyunApiKey = apiKeyArg.split('=')[1]; } } if (!caiyunApiKey) { console.error('错误: 未提供彩云天气API密钥'); console.error('请使用以下方式之一提供API密钥:'); console.error('1. 设置环境变量: CAIYUN_API_KEY=您的密钥 node dist/index.js'); console.error('2. 使用命令行参数: node dist/index.js --api-key=您的密钥'); console.error('3. 在MCP设置文件中配置环境变量'); process.exit(1); } class CaiyunWeatherMcpServer { private server: Server; private weatherService: CaiyunWeatherService; private geocodeService: GeocodeService; constructor() { this.server = new Server( { name: 'caiyun-weather-mcp-server', version: '1.0.0', }, { capabilities: { tools: {}, }, } ); this.weatherService = new CaiyunWeatherService(caiyunApiKey as string); this.geocodeService = new GeocodeService(AMAP_API_KEY || undefined); this.setupToolHandlers(); // 错误处理 this.server.onerror = (error) => console.error('[MCP Error]', error); process.on('SIGINT', async () => { await this.server.close(); process.exit(0); }); } private setupToolHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ { name: 'get_weather_by_location', description: '根据经纬度获取天气信息', inputSchema: { type: 'object', properties: { longitude: { type: 'number', description: '经度', }, latitude: { type: 'number', description: '纬度', }, daily_steps: { type: 'number', description: '每日预报天数 (1-15)', minimum: 1, maximum: 15, default: 5, }, hourly_steps: { type: 'number', description: '小时预报数量 (1-360)', minimum: 1, maximum: 360, default: 24, }, language: { type: 'string', enum: ['zh_CN', 'en_US'], description: '语言', default: 'zh_CN', }, unit: { type: 'string', enum: ['metric', 'imperial'], description: '单位制 (metric: 公制, imperial: 英制)', default: 'metric', }, }, required: ['longitude', 'latitude'], }, }, { name: 'get_weather_by_address', description: '根据地址获取天气信息', inputSchema: { type: 'object', properties: { address: { type: 'string', description: '地址,如"北京市海淀区"', }, daily_steps: { type: 'number', description: '每日预报天数 (1-15)', minimum: 1, maximum: 15, default: 5, }, hourly_steps: { type: 'number', description: '小时预报数量 (1-360)', minimum: 1, maximum: 360, default: 24, }, language: { type: 'string', enum: ['zh_CN', 'en_US'], description: '语言', default: 'zh_CN', }, unit: { type: 'string', enum: ['metric', 'imperial'], description: '单位制 (metric: 公制, imperial: 英制)', default: 'metric', }, }, required: ['address'], }, }, { name: 'get_realtime_weather', description: '获取实时天气数据', inputSchema: { type: 'object', properties: { longitude: { type: 'number', description: '经度', }, latitude: { type: 'number', description: '纬度', }, language: { type: 'string', enum: ['zh_CN', 'en_US'], description: '语言', default: 'zh_CN', }, unit: { type: 'string', enum: ['metric', 'imperial'], description: '单位制 (metric: 公制, imperial: 英制)', default: 'metric', }, }, required: ['longitude', 'latitude'], }, }, { name: 'get_minutely_forecast', description: '获取分钟级降水预报', inputSchema: { type: 'object', properties: { longitude: { type: 'number', description: '经度', }, latitude: { type: 'number', description: '纬度', }, language: { type: 'string', enum: ['zh_CN', 'en_US'], description: '语言', default: 'zh_CN', }, unit: { type: 'string', enum: ['metric', 'imperial'], description: '单位制 (metric: 公制, imperial: 英制)', default: 'metric', }, }, required: ['longitude', 'latitude'], }, }, { name: 'get_hourly_forecast', description: '获取小时级天气预报', inputSchema: { type: 'object', properties: { longitude: { type: 'number', description: '经度', }, latitude: { type: 'number', description: '纬度', }, hourly_steps: { type: 'number', description: '小时预报数量 (1-360)', minimum: 1, maximum: 360, default: 24, }, language: { type: 'string', enum: ['zh_CN', 'en_US'], description: '语言', default: 'zh_CN', }, unit: { type: 'string', enum: ['metric', 'imperial'], description: '单位制 (metric: 公制, imperial: 英制)', default: 'metric', }, }, required: ['longitude', 'latitude'], }, }, { name: 'get_daily_forecast', description: '获取天级天气预报', inputSchema: { type: 'object', properties: { longitude: { type: 'number', description: '经度', }, latitude: { type: 'number', description: '纬度', }, daily_steps: { type: 'number', description: '每日预报天数 (1-15)', minimum: 1, maximum: 15, default: 5, }, language: { type: 'string', enum: ['zh_CN', 'en_US'], description: '语言', default: 'zh_CN', }, unit: { type: 'string', enum: ['metric', 'imperial'], description: '单位制 (metric: 公制, imperial: 英制)', default: 'metric', }, }, required: ['longitude', 'latitude'], }, }, { name: 'get_weather_alert', description: '获取天气预警信息', inputSchema: { type: 'object', properties: { longitude: { type: 'number', description: '经度', }, latitude: { type: 'number', description: '纬度', }, language: { type: 'string', enum: ['zh_CN', 'en_US'], description: '语言', default: 'zh_CN', }, unit: { type: 'string', enum: ['metric', 'imperial'], description: '单位制 (metric: 公制, imperial: 英制)', default: 'metric', }, }, required: ['longitude', 'latitude'], }, }, { name: 'get_air_quality_trend', description: '获取空气质量趋势信息', inputSchema: { type: 'object', properties: { longitude: { type: 'number', description: '经度', }, latitude: { type: 'number', description: '纬度', }, language: { type: 'string', enum: ['zh_CN', 'en_US'], description: '语言', default: 'zh_CN', }, unit: { type: 'string', enum: ['metric', 'imperial'], description: '单位制 (metric: 公制, imperial: 英制)', default: 'metric', }, }, required: ['longitude', 'latitude'], }, }, { name: 'get_detailed_life_index', description: '获取详细生活指数信息', inputSchema: { type: 'object', properties: { longitude: { type: 'number', description: '经度', }, latitude: { type: 'number', description: '纬度', }, language: { type: 'string', enum: ['zh_CN', 'en_US'], description: '语言', default: 'zh_CN', }, unit: { type: 'string', enum: ['metric', 'imperial'], description: '单位制 (metric: 公制, imperial: 英制)', default: 'metric', }, }, required: ['longitude', 'latitude'], }, }, ], })); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { try { const { name, arguments: args = {} } = request.params; // 创建特定语言和单位的天气服务实例 const language = (args.language || 'zh_CN') as 'zh_CN' | 'en_US'; const unit = (args.unit || 'metric') as 'metric' | 'imperial'; const weatherService = new CaiyunWeatherService(caiyunApiKey as string, language, unit); switch (name) { case 'get_weather_by_location': { if (!this.isValidLocationArgs(args)) { throw new McpError( ErrorCode.InvalidParams, '无效的位置参数' ); } const { longitude, latitude, daily_steps = 5, hourly_steps = 24 } = args; const weatherData = await weatherService.getWeather( longitude, latitude, daily_steps, hourly_steps ); return { content: [ { type: 'text', text: JSON.stringify(weatherService.formatWeatherData(weatherData), null, 2), }, ], }; } case 'get_weather_by_address': { if (!this.isValidAddressArgs(args)) { throw new McpError( ErrorCode.InvalidParams, '无效的地址参数' ); } const { address, daily_steps = 5, hourly_steps = 24 } = args; // 将地址转换为经纬度 const [longitude, latitude] = await this.geocodeService.geocode(address); const weatherData = await weatherService.getWeather( longitude, latitude, daily_steps, hourly_steps ); return { content: [ { type: 'text', text: JSON.stringify(weatherService.formatWeatherData(weatherData), null, 2), }, ], }; } case 'get_realtime_weather': { if (!this.isValidLocationArgs(args)) { throw new McpError( ErrorCode.InvalidParams, '无效的位置参数' ); } const { longitude, latitude } = args; const weatherData = await weatherService.getRealtime(longitude, latitude); return { content: [ { type: 'text', text: JSON.stringify(weatherService.formatRealtimeData(weatherData), null, 2), }, ], }; } case 'get_minutely_forecast': { if (!this.isValidLocationArgs(args)) { throw new McpError( ErrorCode.InvalidParams, '无效的位置参数' ); } const { longitude, latitude } = args; const weatherData = await weatherService.getMinutely(longitude, latitude); return { content: [ { type: 'text', text: JSON.stringify(weatherService.formatMinutelyData(weatherData), null, 2), }, ], }; } case 'get_hourly_forecast': { if (!this.isValidLocationArgs(args)) { throw new McpError( ErrorCode.InvalidParams, '无效的位置参数' ); } const { longitude, latitude, hourly_steps = 24 } = args; const weatherData = await weatherService.getHourly(longitude, latitude, hourly_steps); return { content: [ { type: 'text', text: JSON.stringify(weatherService.formatHourlyData(weatherData), null, 2), }, ], }; } case 'get_daily_forecast': { if (!this.isValidLocationArgs(args)) { throw new McpError( ErrorCode.InvalidParams, '无效的位置参数' ); } const { longitude, latitude, daily_steps = 5 } = args; const weatherData = await weatherService.getDaily(longitude, latitude, daily_steps); return { content: [ { type: 'text', text: JSON.stringify(weatherService.formatDailyData(weatherData), null, 2), }, ], }; } case 'get_weather_alert': { if (!this.isValidLocationArgs(args)) { throw new McpError( ErrorCode.InvalidParams, '无效的位置参数' ); } const { longitude, latitude } = args; const weatherData = await weatherService.getAlert(longitude, latitude); return { content: [ { type: 'text', text: JSON.stringify(weatherService.formatAlertData(weatherData), null, 2), }, ], }; } case 'get_air_quality_trend': { if (!this.isValidLocationArgs(args)) { throw new McpError( ErrorCode.InvalidParams, '无效的位置参数' ); } const { longitude, latitude } = args; const weatherData = await weatherService.getAirQualityTrend(longitude, latitude); return { content: [ { type: 'text', text: JSON.stringify(weatherService.formatAirQualityTrendData(weatherData), null, 2), }, ], }; } case 'get_detailed_life_index': { if (!this.isValidLocationArgs(args)) { throw new McpError( ErrorCode.InvalidParams, '无效的位置参数' ); } const { longitude, latitude } = args; const weatherData = await weatherService.getDetailedLifeIndex(longitude, latitude); return { content: [ { type: 'text', text: JSON.stringify(weatherService.formatDetailedLifeIndexData(weatherData), null, 2), }, ], }; } default: throw new McpError( ErrorCode.MethodNotFound, `未知工具: ${name}` ); } } catch (error) { if (error instanceof McpError) { throw error; } return { content: [ { type: 'text', text: `错误: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } }); } private isValidLocationArgs(args: any): args is { longitude: number; latitude: number; daily_steps?: number; hourly_steps?: number } { return ( typeof args === 'object' && args !== null && typeof args.longitude === 'number' && typeof args.latitude === 'number' && (args.daily_steps === undefined || typeof args.daily_steps === 'number') && (args.hourly_steps === undefined || typeof args.hourly_steps === 'number') ); } private isValidAddressArgs(args: any): args is { address: string; daily_steps?: number; hourly_steps?: number } { return ( typeof args === 'object' && args !== null && typeof args.address === 'string' && (args.daily_steps === undefined || typeof args.daily_steps === 'number') && (args.hourly_steps === undefined || typeof args.hourly_steps === 'number') ); } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('Caiyun Weather MCP server running on stdio'); } } const server = new CaiyunWeatherMcpServer(); server.run().catch(console.error);

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/marcusbai/caiyun-weather-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server