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
JavaScript
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}¤t_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}¤t_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);