openweather-mcp
Version:
A comprehensive OpenWeatherMap API integration server based on Model Context Protocol (MCP), providing AI assistants with full weather data access capabilities including smart city name query support.
338 lines (325 loc) • 13.5 kB
JavaScript
/**
* OpenWeatherMap MCP 服务器入口
*/
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
import { OpenWeatherMapService } from './services/openweather.js';
import { CurrentWeatherTool } from './tools/current-weather.js';
import { ForecastTool } from './tools/forecast.js';
import { AirPollutionTool } from './tools/air-pollution.js';
import { WeatherMapTool } from './tools/weather-maps.js';
import { WeatherAlertsTool } from './tools/weather-alerts.js';
import { HistoricalWeatherTool } from './tools/historical-weather.js';
// 导入类型定义(暂时注释掉未使用的)
// import { ... } from './types/mcp.js';
class OpenWeatherMCPServer {
server;
weatherService;
currentWeatherTool;
forecastTool;
airPollutionTool;
weatherMapTool;
weatherAlertsTool;
historicalWeatherTool;
constructor() {
// 检查 API 密钥
const apiKey = process.env['OPENWEATHER_API_KEY'];
if (!apiKey) {
console.error('错误: 请设置 OPENWEATHER_API_KEY 环境变量');
console.error('获取API密钥: https://openweathermap.org/api');
process.exit(1);
}
// 初始化服务
this.weatherService = new OpenWeatherMapService({
apiKey,
units: 'metric',
lang: 'zh_cn',
});
// 初始化工具
this.currentWeatherTool = new CurrentWeatherTool(this.weatherService);
this.forecastTool = new ForecastTool(this.weatherService);
this.airPollutionTool = new AirPollutionTool(this.weatherService);
this.weatherMapTool = new WeatherMapTool(this.weatherService);
this.weatherAlertsTool = new WeatherAlertsTool(this.weatherService);
this.historicalWeatherTool = new HistoricalWeatherTool(this.weatherService);
// 创建 MCP 服务器
this.server = new McpServer({
name: 'openweather-mcp',
version: '1.0.0',
});
this.setupTools();
this.setupResources();
}
/**
* 设置工具
*/
setupTools() {
// 当前天气查询工具
this.server.registerTool('get_current_weather', {
title: '获取当前天气',
description: CurrentWeatherTool.getDescription(),
inputSchema: {
city: z.string().optional().describe('城市名称'),
lat: z.number().optional().describe('纬度'),
lon: z.number().optional().describe('经度'),
zip: z.string().optional().describe('邮政编码'),
country: z.string().optional().describe('国家代码'),
units: z.string().optional().describe('单位制'),
lang: z.string().optional().describe('语言')
}
}, async (input) => {
return await this.currentWeatherTool.execute(input);
});
// 天气预报工具
this.server.registerTool('get_weather_forecast', {
title: '获取天气预报',
description: ForecastTool.getDescription(),
inputSchema: {
city: z.string().optional().describe('城市名称'),
lat: z.number().optional().describe('纬度'),
lon: z.number().optional().describe('经度'),
zip: z.string().optional().describe('邮政编码'),
country: z.string().optional().describe('国家代码'),
cnt: z.number().optional().describe('预报时间点数量'),
units: z.string().optional().describe('单位制'),
lang: z.string().optional().describe('语言')
}
}, async (input) => {
return await this.forecastTool.execute(input);
});
// 空气质量工具
this.server.registerTool('get_air_quality', {
title: '获取空气质量',
description: AirPollutionTool.getDescription(),
inputSchema: {
lat: z.number().describe('纬度'),
lon: z.number().describe('经度')
}
}, async (input) => {
return await this.airPollutionTool.execute(input);
});
// 空气质量预报工具
this.server.registerTool('get_air_quality_forecast', {
title: '获取空气质量预报',
description: '获取指定坐标的空气质量预报数据',
inputSchema: {
lat: z.number().describe('纬度'),
lon: z.number().describe('经度')
}
}, async (input) => {
return await this.airPollutionTool.getForecast(input.lat, input.lon);
});
// 天气地图工具
this.server.registerTool('get_weather_map', {
title: '获取天气地图',
description: WeatherMapTool.getDescription(),
inputSchema: {
layer: z.string().describe('地图图层类型'),
z: z.number().describe('缩放级别'),
x: z.number().describe('瓦片X坐标'),
y: z.number().describe('瓦片Y坐标')
}
}, async (input) => {
return await this.weatherMapTool.execute(input);
});
// 区域天气地图工具
this.server.registerTool('get_region_weather_map', {
title: '获取区域天气地图',
description: '获取指定区域中心的天气地图',
inputSchema: {
layer: z.string().describe('地图图层类型'),
lat: z.number().describe('区域中心纬度'),
lon: z.number().describe('区域中心经度'),
zoom: z.number().optional().describe('缩放级别,默认为5')
}
}, async (input) => {
return await this.weatherMapTool.getRegionMap(input.layer, input.lat, input.lon, input.zoom || 5);
});
// 天气警报工具
this.server.registerTool('get_weather_alerts', {
title: '获取天气警报',
description: WeatherAlertsTool.getDescription(),
inputSchema: {
lat: z.number().describe('纬度'),
lon: z.number().describe('经度')
}
}, async (input) => {
return await this.weatherAlertsTool.execute(input);
});
// 历史天气工具
this.server.registerTool('get_historical_weather', {
title: '获取历史天气',
description: HistoricalWeatherTool.getDescription(),
inputSchema: {
lat: z.number().describe('纬度'),
lon: z.number().describe('经度'),
dt: z.number().describe('Unix时间戳'),
units: z.string().optional().describe('单位制'),
lang: z.string().optional().describe('语言')
}
}, async (input) => {
return await this.historicalWeatherTool.execute(input);
});
// 地理编码工具
this.server.registerTool('geocoding', {
title: '地理编码',
description: '根据地名获取坐标信息',
inputSchema: {
q: z.string().describe('要查询的地名'),
limit: z.number().optional().describe('返回结果数量限制')
}
}, async (input) => {
try {
const results = await this.weatherService.geocoding(input.q, input.limit || 5);
let response = `🌍 **地理编码结果**\n\n`;
response += `🔍 **查询**: ${input.q}\n`;
response += `📊 **结果数量**: ${results.length}\n\n`;
for (let i = 0; i < results.length; i++) {
const result = results[i];
if (result) {
response += `${i + 1}. **${result.name}**\n`;
response += ` 📍 坐标: ${result.lat.toFixed(4)}, ${result.lon.toFixed(4)}\n`;
response += ` 🏳️ 国家: ${result.country}\n`;
if (result.state) {
response += ` 🏛️ 州/省: ${result.state}\n`;
}
response += '\n';
}
}
return {
content: [{ type: 'text', text: response }],
};
}
catch (error) {
return {
content: [
{
type: 'text',
text: `地理编码查询失败: ${error instanceof Error ? error.message : '未知错误'}`,
},
],
isError: true,
};
}
});
// 反向地理编码工具
this.server.registerTool('reverse_geocoding', {
title: '反向地理编码',
description: '根据坐标获取地名信息',
inputSchema: {
lat: z.number().describe('纬度'),
lon: z.number().describe('经度'),
limit: z.number().optional().describe('返回结果数量限制')
}
}, async (input) => {
try {
const results = await this.weatherService.reverseGeocoding(input.lat, input.lon, input.limit || 5);
let response = `🌍 **反向地理编码结果**\n\n`;
response += `📍 **查询坐标**: ${input.lat.toFixed(4)}, ${input.lon.toFixed(4)}\n`;
response += `📊 **结果数量**: ${results.length}\n\n`;
for (let i = 0; i < results.length; i++) {
const result = results[i];
if (result) {
response += `${i + 1}. **${result.name}**\n`;
response += ` 🏳️ 国家: ${result.country}\n`;
if (result.state) {
response += ` 🏛️ 州/省: ${result.state}\n`;
}
response += '\n';
}
}
return {
content: [{ type: 'text', text: response }],
};
}
catch (error) {
return {
content: [
{
type: 'text',
text: `反向地理编码查询失败: ${error instanceof Error ? error.message : '未知错误'}`,
},
],
isError: true,
};
}
});
}
/**
* 设置资源
*/
setupResources() {
// 可以在这里添加静态资源,如API文档、使用指南等
this.server.registerResource('weather_api_guide', 'weather://guide', {
title: 'OpenWeatherMap API 使用指南',
description: '详细的API使用说明和示例',
mimeType: 'text/markdown',
}, async () => {
const guide = `# OpenWeatherMap MCP 服务器使用指南
## 可用工具
### 1. 当前天气查询 (get_current_weather)
获取指定位置的实时天气数据。
### 2. 天气预报 (get_weather_forecast)
获取5天天气预报,每3小时一个数据点。
### 3. 空气质量 (get_air_quality)
获取空气质量指数和污染物浓度。
### 4. 天气地图 (get_weather_map)
获取各种天气要素的地图瓦片。
### 5. 天气警报 (get_weather_alerts)
获取政府发布的天气预警信息。
### 6. 历史天气 (get_historical_weather)
查询历史天气数据。
### 7. 地理编码 (geocoding)
地名转坐标。
### 8. 反向地理编码 (reverse_geocoding)
坐标转地名。
## 环境变量配置
请确保设置了以下环境变量:
- OPENWEATHER_API_KEY: OpenWeatherMap API 密钥
## 获取API密钥
访问 https://openweathermap.org/api 注册并获取免费API密钥。
`;
return {
contents: [
{
uri: 'weather://guide',
text: guide,
mimeType: 'text/markdown',
},
],
};
});
}
/**
* 启动服务器
*/
async start() {
try {
// 验证API密钥
console.error('正在验证 OpenWeatherMap API 密钥...');
const isValid = await this.weatherService.validateApiKey();
if (!isValid) {
console.error('错误: API 密钥无效或网络连接失败');
process.exit(1);
}
console.error('API 密钥验证成功');
// 启动服务器
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('OpenWeatherMap MCP 服务器已启动');
}
catch (error) {
console.error('启动服务器时发生错误:', error);
process.exit(1);
}
}
}
// 启动服务器
const server = new OpenWeatherMCPServer();
server.start().catch((error) => {
console.error('服务器启动失败:', error);
process.exit(1);
});
//# sourceMappingURL=index.js.map