Skip to main content
Glama

Weather MCP Server

by ctermiii
index.js12.9 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"; /** * 通过城市名称获取地理坐标 */ async function geocodeCity(cityName) { try { const url = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(cityName)}&count=1&language=zh&format=json`; const response = await fetch(url); const data = await response.json(); if (!data.results || data.results.length === 0) { throw new Error(`无法找到城市: ${cityName}`); } const result = data.results[0]; return { name: result.name, country: result.country, latitude: result.latitude, longitude: result.longitude, timezone: result.timezone || 'auto' }; } catch (error) { throw new Error(`地理编码失败: ${error.message}`); } } /** * 获取天气预报 * @param {number} latitude - 纬度 * @param {number} longitude - 经度 * @param {string} timezone - 时区 * @param {number} days - 预报天数(1-16天) */ async function getWeatherForecast(latitude, longitude, timezone = 'auto', days = 7) { try { // 限制天数范围在 1-16 之间 const forecastDays = Math.max(1, Math.min(16, days)); // 构建天气 API URL const weatherParams = new URLSearchParams({ latitude: latitude.toString(), longitude: longitude.toString(), timezone: timezone, // 每日天气数据 daily: [ 'temperature_2m_max', // 最高温度 'temperature_2m_min', // 最低温度 'weathercode', // 天气代码 'precipitation_sum', // 降水量 'rain_sum', // 降雨量 'snowfall_sum', // 降雪量 'windspeed_10m_max', // 最大风速 'windgusts_10m_max', // 最大阵风 'winddirection_10m_dominant', // 主导风向 'sunrise', // 日出时间 'sunset', // 日落时间 'uv_index_max' // 紫外线指数 ].join(','), // 当前天气数据 current: [ 'temperature_2m', 'relative_humidity_2m', 'apparent_temperature', // 体感温度 'weathercode', 'windspeed_10m', 'winddirection_10m', 'uv_index' // 当前紫外线指数 ].join(','), forecast_days: forecastDays.toString() }); const weatherUrl = `https://api.open-meteo.com/v1/forecast?${weatherParams}`; const weatherResponse = await fetch(weatherUrl); const weatherData = await weatherResponse.json(); if (weatherData.error) { throw new Error(`天气API错误: ${weatherData.reason || '未知错误'}`); } // 构建空气质量 API URL const airParams = new URLSearchParams({ latitude: latitude.toString(), longitude: longitude.toString(), current: ['pm2_5', 'pm10'].join(',') }); const airUrl = `https://air-quality-api.open-meteo.com/v1/air-quality?${airParams}`; let airData = null; try { const airResponse = await fetch(airUrl); airData = await airResponse.json(); if (airData.error) { console.error('空气质量数据获取失败,将显示为无数据'); airData = null; } } catch (error) { console.error('空气质量API调用失败:', error.message); } return formatWeatherData(weatherData, airData, forecastDays); } catch (error) { throw new Error(`获取天气数据失败: ${error.message}`); } } /** * 天气代码转换为中文描述 */ function 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] || '未知'; } /** * 获取紫外线等级描述 */ function getUVLevel(uvIndex) { if (uvIndex === null || uvIndex === undefined) return '无数据'; if (uvIndex < 3) return '低'; if (uvIndex < 6) return '中等'; if (uvIndex < 8) return '高'; if (uvIndex < 11) return '很高'; return '极高'; } /** * 获取空气质量等级 */ function getAirQualityLevel(pm25) { if (pm25 === null || pm25 === undefined) return '无数据'; if (pm25 <= 12) return '优'; if (pm25 <= 35) return '良'; if (pm25 <= 55) return '轻度污染'; if (pm25 <= 150) return '中度污染'; if (pm25 <= 250) return '重度污染'; return '严重污染'; } /** * 风向角度转换为方位 */ function getWindDirection(degree) { const directions = ['北', '东北', '东', '东南', '南', '西南', '西', '西北']; const index = Math.round(degree / 45) % 8; return directions[index]; } /** * 计算舒适度指数 */ function getComfortIndex(temp, humidity, apparentTemp) { let comfort = '舒适'; if (apparentTemp < 0) { comfort = '非常寒冷'; } else if (apparentTemp < 10) { comfort = '寒冷'; } else if (apparentTemp < 16) { comfort = '凉爽'; } else if (apparentTemp < 24) { comfort = '舒适'; } else if (apparentTemp < 28) { comfort = '温暖'; } else if (apparentTemp < 32) { comfort = '炎热'; } else { comfort = '酷热'; } if (humidity > 80) { comfort += ' (湿度高)'; } else if (humidity < 30) { comfort += ' (干燥)'; } return comfort; } /** * 格式化天气数据 * @param {object} weatherData - 天气API返回的原始数据 * @param {object} airData - 空气质量API返回的数据 * @param {number} forecastDays - 预报天数 */ function formatWeatherData(weatherData, airData, forecastDays) { // 验证数据结构 if (!weatherData || !weatherData.daily) { throw new Error('API返回数据格式错误'); } const current = weatherData.current; const daily = weatherData.daily; const airCurrent = airData?.current; const result = {}; // 当前天气 (可选,某些情况下可能不可用) if (current && current.time) { result.当前天气 = { 时间: current.time, 温度: current.temperature_2m !== undefined ? `${current.temperature_2m}°C` : '无数据', 体感温度: current.apparent_temperature !== undefined ? `${current.apparent_temperature}°C` : '无数据', 湿度: current.relative_humidity_2m !== undefined ? `${current.relative_humidity_2m}%` : '无数据', 天气状况: current.weathercode !== undefined ? getWeatherDescription(current.weathercode) : '无数据', 风速: current.windspeed_10m !== undefined ? `${current.windspeed_10m} km/h` : '无数据', 风向: current.winddirection_10m !== undefined ? getWindDirection(current.winddirection_10m) : '无数据', 舒适度: (current.temperature_2m !== undefined && current.relative_humidity_2m !== undefined && current.apparent_temperature !== undefined) ? getComfortIndex(current.temperature_2m, current.relative_humidity_2m, current.apparent_temperature) : '无数据', 紫外线指数: current.uv_index !== null && current.uv_index !== undefined ? `${current.uv_index} (${getUVLevel(current.uv_index)})` : '无数据', PM2_5: airCurrent?.pm2_5 !== null && airCurrent?.pm2_5 !== undefined ? `${airCurrent.pm2_5.toFixed(1)} μg/m³ (${getAirQualityLevel(airCurrent.pm2_5)})` : '无数据', PM10: airCurrent?.pm10 !== null && airCurrent?.pm10 !== undefined ? `${airCurrent.pm10.toFixed(1)} μg/m³` : '无数据' }; } // N天预报 if (daily.time && daily.time.length > 0) { const forecast = daily.time.map((date, index) => { const maxTemp = daily.temperature_2m_max?.[index]; const minTemp = daily.temperature_2m_min?.[index]; const avgTemp = (maxTemp !== undefined && minTemp !== undefined) ? (maxTemp + minTemp) / 2 : null; const weatherCode = daily.weathercode?.[index]; const precipitation = daily.precipitation_sum?.[index] || 0; const rain = daily.rain_sum?.[index] || 0; const snow = daily.snowfall_sum?.[index] || 0; const uvIndex = daily.uv_index_max?.[index]; let weatherDetail = weatherCode !== undefined ? getWeatherDescription(weatherCode) : '无数据'; if (rain > 0) weatherDetail += ` (降雨 ${rain}mm)`; if (snow > 0) weatherDetail += ` (降雪 ${snow}cm)`; return { 日期: date, 最高温度: maxTemp !== undefined ? `${maxTemp}°C` : '无数据', 最低温度: minTemp !== undefined ? `${minTemp}°C` : '无数据', 天气状况: weatherDetail, 降水量: `${precipitation}mm`, 降雨量: `${rain}mm`, 降雪量: `${snow}cm`, 最大风速: daily.windspeed_10m_max?.[index] !== undefined ? `${daily.windspeed_10m_max[index]} km/h` : '无数据', 最大阵风: daily.windgusts_10m_max?.[index] !== undefined ? `${daily.windgusts_10m_max[index]} km/h` : '无数据', 主导风向: daily.winddirection_10m_dominant?.[index] !== undefined ? getWindDirection(daily.winddirection_10m_dominant[index]) : '无数据', 日出: daily.sunrise?.[index] || '无数据', 日落: daily.sunset?.[index] || '无数据', 紫外线指数: uvIndex !== null && uvIndex !== undefined ? `${uvIndex} (${getUVLevel(uvIndex)})` : '无数据', 舒适度: avgTemp !== null ? getComfortIndex(avgTemp, 60, avgTemp) : '无数据' }; }); result[`${forecastDays}天预报`] = forecast; } result.时区 = weatherData.timezone || 'UTC'; return result; } /** * 创建MCP服务器 */ const server = new Server( { name: "weather-mcp-server", version: "1.0.0", }, { capabilities: { tools: {}, }, } ); /** * 注册工具列表处理器 */ server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "get_weather", description: "获取指定城市的天气预报信息。支持1-16天的天气预报,包括温度、风力、舒适度、降雨降雪、紫外线指数、PM2.5等详细信息。", inputSchema: { type: "object", properties: { city: { type: "string", description: "城市名称,可以是中文或英文,例如:北京、Shanghai、New York等" }, days: { type: "number", description: "预报天数,范围1-16天,默认7天", default: 7, minimum: 1, maximum: 16 } }, required: ["city"] } } ] }; }); /** * 注册工具调用处理器 */ server.setRequestHandler(CallToolRequestSchema, async (request) => { if (request.params.name === "get_weather") { const cityName = request.params.arguments.city; const days = request.params.arguments.days || 7; try { // 1. 获取城市坐标 const location = await geocodeCity(cityName); // 2. 获取天气数据 const weather = await getWeatherForecast( location.latitude, location.longitude, location.timezone, days ); // 3. 构建返回结果 const result = { 城市信息: { 名称: location.name, 国家: location.country, 坐标: `${location.latitude}, ${location.longitude}`, 时区: location.timezone }, ...weather }; return { content: [ { type: "text", text: JSON.stringify(result, null, 2) } ] }; } catch (error) { return { content: [ { type: "text", text: `错误: ${error.message}` } ], isError: true }; } } throw new Error(`未知工具: ${request.params.name}`); }); /** * 启动服务器 */ async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Weather MCP Server 已启动"); } main().catch((error) => { console.error("服务器启动失败:", error); process.exit(1); });

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/ctermiii/weather-mcp-metro'

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