UNPKG

weather-mcp-server-mariox

Version:

MCP server for querying weather by current IP location using Open-Meteo free API

340 lines (302 loc) 11.1 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import axios from 'axios'; class WeatherMCPServer { constructor() { this.server = new Server( { name: 'weather-mcp-server', version: '1.0.0', }, { capabilities: { tools: {}, }, } ); this.setupToolHandlers(); } setupToolHandlers() { // 列出可用工具 this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'get_current_ip_weather', description: '获取当前IP地址的天气信息(使用Open-Meteo免费API)', inputSchema: { type: 'object', properties: {}, required: [], }, }, { name: 'get_weather_by_coordinates', description: '根据经纬度获取天气信息', inputSchema: { type: 'object', properties: { latitude: { type: 'number', description: '纬度', }, longitude: { type: 'number', description: '经度', }, }, required: ['latitude', 'longitude'], }, }, { name: 'get_current_ip_info', description: '获取当前IP地址信息', inputSchema: { type: 'object', properties: {}, required: [], }, }, { name: 'get_weather_forecast', description: '获取天气预报(未来7天)', inputSchema: { type: 'object', properties: { latitude: { type: 'number', description: '纬度', }, longitude: { type: 'number', description: '经度', }, days: { type: 'integer', description: '预报天数(1-7天)', minimum: 1, maximum: 7, }, }, required: ['latitude', 'longitude'], }, }, ], }; }); // 处理工具调用 this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case 'get_current_ip_weather': return await this.getCurrentIpWeather(); case 'get_weather_by_coordinates': return await this.getWeatherByCoordinates(args.latitude, args.longitude); case 'get_current_ip_info': return await this.getCurrentIpInfo(); case 'get_weather_forecast': return await this.getWeatherForecast(args.latitude, args.longitude, args.days || 3); default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { return { content: [ { type: 'text', text: `Error: ${error.message}`, }, ], }; } }); } // 获取当前IP信息 async getCurrentIpInfo() { try { const response = await axios.get('http://ip-api.com/json/', { timeout: 5000, }); const data = response.data; if (data.status === 'fail') { throw new Error('无法获取IP信息'); } const ipInfo = { ip: data.query, country: data.country, countryCode: data.countryCode, region: data.region, regionName: data.regionName, city: data.city, lat: data.lat, lon: data.lon, timezone: data.timezone, isp: data.isp, }; return { content: [ { type: 'text', text: `🌍 **IP地址信息**\n\n🌐 IP地址: ${ipInfo.ip}\n🏳️ 国家: ${ipInfo.country} (${ipInfo.countryCode})\n🏙️ 地区: ${ipInfo.regionName}\n🏘️ 城市: ${ipInfo.city}\n📊 坐标: ${ipInfo.lat}, ${ipInfo.lon}\n🕐 时区: ${ipInfo.timezone}\n📡 ISP: ${ipInfo.isp}`, }, ], }; } catch (error) { throw new Error(`获取IP信息失败: ${error.message}`); } } // 天气代码转换为中文描述 getWeatherDescription(code) { const weatherCodes = { 0: '晴朗', 1: '基本晴朗', 2: '部分多云', 3: '阴天', 45: '雾', 48: '雾凇', 51: '小雨', 53: '中雨', 55: '大雨', 56: '冻雨(轻)', 57: '冻雨(重)', 61: '小雨', 63: '中雨', 65: '大雨', 66: '冻雨', 67: '冻雨(重)', 71: '小雪', 73: '中雪', 75: '大雪', 77: '雪粒', 80: '阵雨(轻)', 81: '阵雨(中)', 82: '阵雨(重)', 85: '阵雪(轻)', 86: '阵雪(重)', 95: '雷雨', 96: '雷雨伴冰雹(轻)', 99: '雷雨伴冰雹(重)', }; return weatherCodes[code] || `天气代码: ${code}`; } // 根据经纬度获取天气信息(使用Open-Meteo) async getWeatherByCoordinates(lat, lon) { try { const response = await axios.get( `https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}&current_weather=true&hourly=temperature_2m,relative_humidity_2m,wind_speed_10m,pressure_msl&timezone=auto`, { timeout: 10000 } ); const data = response.data; const weather = data.current_weather; const hourly = data.hourly; // 获取当前小时的湿度和气压 const currentHour = new Date().getHours(); const humidity = hourly.relative_humidity_2m[currentHour] || 'N/A'; const pressure = hourly.pressure_msl[currentHour] || 'N/A'; const result = { temperature: `${weather.temperature}°C`, weather_description: this.getWeatherDescription(weather.weathercode), wind_speed: `${weather.windspeed} km/h`, wind_direction: `${weather.winddirection}°`, humidity: humidity !== 'N/A' ? `${humidity}%` : 'N/A', pressure: pressure !== 'N/A' ? `${pressure} hPa` : 'N/A', coordinates: `${lat}, ${lon}`, data_source: 'Open-Meteo (免费API)', time: weather.time, }; return { content: [ { type: 'text', text: `🌤️ **天气信息**\n\n📍 坐标: ${result.coordinates}\n🌡️ 温度: ${result.temperature}\n☁️ 天气: ${result.weather_description}\n💨 风速: ${result.wind_speed}\n🧭 风向: ${result.wind_direction}\n💧 湿度: ${result.humidity}\n📊 气压: ${result.pressure}\n🕐 时间: ${result.time}\n📡 数据源: ${result.data_source}`, }, ], }; } catch (error) { throw new Error(`获取天气信息失败: ${error.message}`); } } // 获取天气预报 async getWeatherForecast(lat, lon, days = 3) { try { const response = await axios.get( `https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}&daily=temperature_2m_max,temperature_2m_min,weathercode,precipitation_sum&timezone=auto&forecast_days=${days}`, { timeout: 10000 } ); const data = response.data; const daily = data.daily; let forecastText = `🌤️ **${days}天天气预报**\n\n📍 坐标: ${lat}, ${lon}\n\n`; for (let i = 0; i < days; i++) { const date = daily.time[i]; const maxTemp = daily.temperature_2m_max[i]; const minTemp = daily.temperature_2m_min[i]; const weatherCode = daily.weathercode[i]; const precipitation = daily.precipitation_sum[i]; const dayName = i === 0 ? '今天' : i === 1 ? '明天' : `第${i+1}天`; forecastText += `📅 **${dayName}** (${date})\n`; forecastText += `🌡️ 温度: ${minTemp}°C - ${maxTemp}°C\n`; forecastText += `☁️ 天气: ${this.getWeatherDescription(weatherCode)}\n`; forecastText += `🌧️ 降水量: ${precipitation} mm\n\n`; } forecastText += `📡 数据源: Open-Meteo (免费API)`; return { content: [ { type: 'text', text: forecastText, }, ], }; } catch (error) { throw new Error(`获取天气预报失败: ${error.message}`); } } // 获取当前IP的天气信息 async getCurrentIpWeather() { try { // 1. 获取IP信息 const ipResponse = await axios.get('http://ip-api.com/json/', { timeout: 5000, }); const ipData = ipResponse.data; if (ipData.status === 'fail') { throw new Error('无法获取IP位置信息'); } // 2. 根据经纬度获取天气信息 const weatherResponse = await axios.get( `https://api.open-meteo.com/v1/forecast?latitude=${ipData.lat}&longitude=${ipData.lon}&current_weather=true&hourly=temperature_2m,relative_humidity_2m,wind_speed_10m,pressure_msl&timezone=auto`, { timeout: 10000 } ); const weatherData = weatherResponse.data; const weather = weatherData.current_weather; const hourly = weatherData.hourly; // 获取当前小时的湿度和气压 const currentHour = new Date().getHours(); const humidity = hourly.relative_humidity_2m[currentHour] || 'N/A'; const pressure = hourly.pressure_msl[currentHour] || 'N/A'; return { content: [ { type: 'text', text: `🌍 **当前IP位置天气信息**\n\n📍 **位置信息:**\n🌐 IP地址: ${ipData.query}\n🏳️ 国家: ${ipData.country}\n🏙️ 地区: ${ipData.regionName}\n🏘️ 城市: ${ipData.city}\n📊 坐标: ${ipData.lat}, ${ipData.lon}\n\n🌤️ **天气信息:**\n🌡️ 温度: ${weather.temperature}°C\n☁️ 天气: ${this.getWeatherDescription(weather.weathercode)}\n💨 风速: ${weather.windspeed} km/h\n🧭 风向: ${weather.winddirection}°\n💧 湿度: ${humidity !== 'N/A' ? humidity + '%' : 'N/A'}\n📊 气压: ${pressure !== 'N/A' ? pressure + ' hPa' : 'N/A'}\n🕐 时间: ${weather.time}\n\n📡 数据源: Open-Meteo (免费API)`, }, ], }; } catch (error) { throw new Error(`获取当前IP天气信息失败: ${error.message}`); } } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('Weather MCP Server 已启动'); } } const server = new WeatherMCPServer(); server.run().catch(console.error);