HeFeng Weather MCP Server
by shanggqm
- src
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 { z } from "zod";
const HEFENG_API_BASE = "https://devapi.qweather.com/v7";
let HEFENG_API_KEY = ""; // 默认API密钥
const apiKeyArg = process.argv.find(arg => arg.startsWith('--apiKey='));
if (apiKeyArg) {
const apiKey = apiKeyArg.split('=')[1];
if (apiKey) {
console.log(`使用命令行参数中的API密钥: ${apiKey}`);
HEFENG_API_KEY = apiKey;
}
}
// Define Zod schemas for validation
const WeatherArgumentsSchema = z.object({
location: z.string(), // Location name or coordinates
days: z.enum(['now', '24h', '72h', '168h', '3d', '7d', '10d', '15d', '30d']).default('now'), // 预报天数
});
// Create server instance
const server = new Server(
{
name: "weather-zhcn",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
}
);
// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "get-weather",
description: "获取中国国内的天气预报",
inputSchema: {
type: "object",
properties: {
location: {
type: "string",
description: "逗号分隔的经纬度信息 (e.g., 116.40,39.90)",
},
days: {
type: "string",
enum: ["now", "24h", "72h", "168h", "3d", "7d", "10d", "15d", "30d"],
description: "预报天数,now为实时天气,24h为24小时预报,72h为72小时预报,168h为168小时预报,3d为3天预报,以此类推",
default: "now"
}
},
required: ["location"],
},
},
],
};
});
// Helper function for making HeFeng API requests
async function makeHeFengRequest<T>(url: string): Promise<T | null> {
const headers = {
Accept: "application/json",
};
try {
const response = await fetch(url, { headers });
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return (await response.json()) as T;
} catch (error) {
console.error("Error making HeFeng request:", error);
return null;
}
}
interface HeFengWeatherNowResponse {
now: {
obsTime: string;
temp: string;
feelsLike: string;
text: string;
windDir: string;
windScale: string;
};
}
interface HeFengWeatherDailyResponse {
daily: Array<{
fxDate: string;
tempMax: string;
tempMin: string;
textDay: string;
textNight: string;
windDirDay: string;
windScaleDay: string;
windDirNight: string;
windScaleNight: string;
}>;
}
interface HeFengWeatherHourlyResponse {
hourly: Array<{
fxTime: string;
temp: string;
text: string;
windDir: string;
windScale: string;
humidity: string;
}>;
}
// Handle tool execution
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
if (name === "get-weather") {
const { location, days } = WeatherArgumentsSchema.parse(args);
if (days === 'now') {
// Get current weather data
const weatherUrl = `${HEFENG_API_BASE}/weather/now?location=${location}&key=${HEFENG_API_KEY}`;
const weatherData = await makeHeFengRequest<HeFengWeatherNowResponse>(weatherUrl);
if (!weatherData || !weatherData.now) {
return {
content: [{ type: "text", text: `无法获取 ${location} 的天气数据` }],
};
}
const { now } = weatherData;
const weatherText = `地点: ${location}\n` +
`观测时间: ${now.obsTime}\n` +
`天气: ${now.text}\n` +
`温度: ${now.temp}°C\n` +
`体感温度: ${now.feelsLike}°C\n` +
`风向: ${now.windDir}\n` +
`风力: ${now.windScale}级`;
return { content: [{ type: "text", text: weatherText }] };
} else if (['24h', '72h', '168h'].includes(days)) {
// Get hourly forecast data
const weatherUrl = `${HEFENG_API_BASE}/weather/${days}?location=${location}&key=${HEFENG_API_KEY}`;
const weatherData = await makeHeFengRequest<HeFengWeatherHourlyResponse>(weatherUrl);
if (!weatherData || !weatherData.hourly) {
return {
content: [{ type: "text", text: `无法获取 ${location} 的逐小时天气预报数据` }],
};
}
const hoursText = weatherData.hourly.map(hour => {
return `时间: ${hour.fxTime}\n` +
`天气: ${hour.text}\n` +
`温度: ${hour.temp}°C\n` +
`湿度: ${hour.humidity}%\n` +
`风向: ${hour.windDir} ${hour.windScale}级\n` +
`------------------------`;
}).join('\n');
return {
content: [{
type: "text",
text: `地点: ${location}\n${days}小时预报:\n${hoursText}`
}],
};
} else {
// Get daily forecast weather data
const daysNum = parseInt(days);
const weatherUrl = `${HEFENG_API_BASE}/weather/${days}?location=${location}&key=${HEFENG_API_KEY}`;
const weatherData = await makeHeFengRequest<HeFengWeatherDailyResponse>(weatherUrl);
if (!weatherData || !weatherData.daily) {
return {
content: [{ type: "text", text: `无法获取 ${location} 的天气预报数据` }],
};
}
const forecastText = weatherData.daily.map(day => {
return `日期: ${day.fxDate}\n` +
`白天天气: ${day.textDay}\n` +
`夜间天气: ${day.textNight}\n` +
`最高温度: ${day.tempMax}°C\n` +
`最低温度: ${day.tempMin}°C\n` +
`白天风向: ${day.windDirDay} ${day.windScaleDay}级\n` +
`夜间风向: ${day.windDirNight} ${day.windScaleNight}级\n` +
`------------------------`;
}).join('\n');
return {
content: [{
type: "text",
text: `地点: ${location}\n${daysNum}天预报:\n${forecastText}`
}],
};
}
} else {
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
if (error instanceof z.ZodError) {
throw new Error(
`Invalid arguments: ${error.errors
.map((e) => `${e.path.join(".")}: ${e.message}`)
.join(", ")}`
);
}
throw error;
}
});
// Start the server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Weather-zhcn MCP Server running on stdio");
}
main().catch((error) => {
console.error("Fatal error in main():", error);
process.exit(1);
});