手把手教你开发一个 AI 可用的天气查询 MCP 服务

前言

最近 AI 生态里MCP(Model Context Protocol) 火出圈了!它能让 AI 大模型无缝调用外部工具、API、本地资源,相当于给 AI 装上了「手脚」。

今天我就带大家从零开发一个基于美国国家气象局 NWS API 的天气 MCP 服务,支持查询州级天气预警、经纬度精准天气预报,代码可直接运行、可对接 Claude / 其他支持 MCP 的 AI 模型。

全程 Node.js + TypeScript,新手也能轻松上手!

一、先搞懂:什么是 MCP?

MCP 全称 Model Context Protocol ,由 Anthropic 开源,是一套标准化协议,作用只有一个:让大模型安全、规范地调用外部服务 / 工具

简单理解:

  • 你写一个 MCP 服务 = 给 AI 注册一套「API 工具」
  • 大模型触发 = 自动调用你写的方法
  • 返回结果 = 模型直接理解并自然语言回复你

我们今天要做的就是:天气查询 MCP 服务

二、项目准备

1. 初始化项目

bash 复制代码
mkdir mcp-weather-server 
cd mcp-weather-server 
npm init -y
  1. 安装依赖
bash 复制代码
npm install @modelcontextprotocol/sdk zod 
npm install -D typescript @types/node ts-node
  1. tsconfig.json 基础配置
json 复制代码
{ "compilerOptions": { "target": "ES2022", "module": "ESNext", "moduleResolution": "node", "esModuleInterop": true, "skipLibCheck": true, "strict": true, "outDir": "./dist" }, "include": ["src/**/*"] }

三、完整代码实现

新建 src/index.ts,直接复制以下完整代码:

ts 复制代码
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; // NWS 公共API配置(无需KEY,免费使用) const NWS_API_BASE = "https://api.weather.gov"; const USER_AGENT = "weather-app/1.0"; // 创建MCP服务实例 const server = new McpServer({ name: "weather", version: "1.0.0", }); // 请求工具函数 async function makeNWSRequest<T>(url: string): Promise<T | null> { const headers = { "User-Agent": USER_AGENT, Accept: "application/geo+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("API请求失败:", error); return null; } } // 类型定义 interface AlertFeature { properties: { event?: string; areaDesc?: string; severity?: string; status?: string; headline?: string; }; } interface ForecastPeriod { name?: string; temperature?: number; temperatureUnit?: string; windSpeed?: string; windDirection?: string; shortForecast?: string; } interface AlertsResponse { features: AlertFeature[] } interface PointsResponse { properties: { forecast?: string } } interface ForecastResponse { properties: { periods: ForecastPeriod[] } } // 格式化预警信息 function formatAlert(feature: AlertFeature): string { const p = feature.properties; return [ `事件: ${p.event || "未知"}`, `地区: ${p.areaDesc || "未知"}`, `等级: ${p.severity || "未知"}`, `状态: ${p.status || "未知"}`, `概要: ${p.headline || "无"}`, "---", ].join("\n"); } // ============================================== // 工具1:获取美国某州的天气预警 // ============================================== server.registerTool( "get_alerts", { description: "获取指定州的天气预警信息(美国)", inputSchema: z.object({ state: z.string().length(2).describe("州代码,如 CA、NY"), }), }, async ({ state }) => { const code = state.toUpperCase(); const data = await makeNWSRequest<AlertsResponse>(`${NWS_API_BASE}/alerts?area=${code}`); if (!data) return { content: [{ type: "text", text: "获取预警失败" }] }; if (!data.features.length) return { content: [{ type: "text", text: `${code}暂无预警` }] }; const text = `【${code} 天气预警】\n\n${data.features.map(formatAlert).join("\n")}`; return { content: [{ type: "text", text }] }; } ); // ============================================== // 工具2:根据经纬度获取天气预报 // ============================================== server.registerTool( "get_forecast", { description: "根据经纬度获取天气预报(仅支持美国)", inputSchema: z.object({ latitude: z.number().min(-90).max(90).describe("纬度"), longitude: z.number().min(-180).max(180).describe("经度"), }), }, async ({ latitude, longitude }) => { // 第一步:获取网格点 const pointData = await makeNWSRequest<PointsResponse>( `${NWS_API_BASE}/points/${latitude.toFixed(4)},${longitude.toFixed(4)}` ); if (!pointData || !pointData.properties.forecast) { return { content: [{ type: "text", text: "不支持该地区(仅美国境内)" }] }; } // 第二步:获取预报 const forecastData = await makeNWSRequest<ForecastResponse>(pointData.properties.forecast); if (!forecastData || !forecastData.properties.periods.length) { return { content: [{ type: "text", text: "暂无预报数据" }] }; } const forecastText = forecastData.properties.periods .map(p => [ `${p.name}:`, `温度:${p.temperature}°${p.temperatureUnit}`, `风速:${p.windSpeed} ${p.windDirection}`, p.shortForecast, "---", ].join("\n")) .join("\n"); return { content: [{ type: "text", text: `【${latitude},${longitude} 天气预报】\n\n${forecastText}` }] }; } ); // 启动服务 async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Weather MCP 服务已启动"); } main().catch(err => { console.error("服务启动失败", err); process.exit(1); });
相关推荐
printfall1 小时前
openclaw.mjs
后端
神奇小汤圆1 小时前
2022 年 Java 后端面试题,吃透 20 套专题技术栈
后端
i建模1 小时前
npm使用大全
前端·npm·node.js
KerwinChou_CN2 小时前
大模型 RAG 中 RRF(Reciprocal Rank Fusion倒数排序融合)是什么
人工智能·后端·python
神奇小汤圆2 小时前
类字节码:揭开Java虚拟机运行机制的神秘面纱
后端
Java面试题总结2 小时前
Go运行时系统解析: runtime包深度指南
开发语言·后端·golang
Soofjan2 小时前
Sync.Map 详解以及源码
后端
小箌2 小时前
JavaWeb & SpringBoot 总结
java·spring boot·后端