index.js•12.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);
});