在本教程中,我们将构建一个简易的 MCP 天气服务器(参考MCP官方文档),并将其与一个主机(客户端),即 Cursor 客户端相连接。
当前,许多大语言模型(LLM)尚不具备获取天气预报及恶劣天气警报的能力。我们将借助 MCP 来解决这一问题!
我们会构建一个服务器,该服务器将暴露两个工具:get-alerts
和 get-forecast
。随后,我们会把此服务器连接到一个 MCP 客户端。
众多编程语言都支持开发 MCP 服务器,例如 Python、JavaScript、Java、Kotlin、C# 等。在此,我们选用 Python 进行演示。
modelcontextprotocol.io/introductio... mcp 英文版官方文档
mcp-docs.cn/introductio... mcp 中文翻译官方文档
配套视频
www.bilibili.com/video/BV1JD...
搭建Python开发环境
首先,让我们安装 uv
,uv
(Rust 版)需要 Python 3.8+ 环境:
通过 pip
安装(推荐):
bash
pip install uv
# 检查是否安装成功
uv --version
以MacOS/Linux为例:
arduino
curl -LsSf https://astral.sh/uv/install.sh | sh
以Windows为例:
arduino
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
之后请务必重启你的终端,以确保 uv
命令被识别。
创建项目
现在,让我们创建并设置我们的项目:
以MacOS/Linux为例:
bash
# 为我们的项目创建一个新 directory
uv init weather
cd weather
# 创建 virtual environment 并激活它
uv venv
source .venv/bin/activate
# 安装 dependencies
uv add "mcp[cli]" httpx
# 创建我们的 server file
touch weather.py
以Windows为例:
csharp
# 为我们的项目创建一个新 directory
uv init weather
cd weather
# 创建 virtual environment 并激活它
uv venv
.venv\Scripts\activate
# 安装 dependencies
uv add mcp[cli] httpx
# 创建我们的 server file
new-item weather.py
现在让我们深入构建你的服务器。
服务器代码编写
将如下代码添加到你的 weather.py
文件的顶部:
ini
from typing import Any
import httpx
from mcp.server.fastmcp import FastMCP
# 初始化 FastMCP server
mcp = FastMCP("weather")
# Constants
# NWS(美国国家气象局) API 的基础 URL
NWS_API_BASE = "https://api.weather.gov"
# 用于标识我们的应用程序的 User-Agent 字符串
USER_AGENT = "weather-app/1.0"
让我们详细解释FastMCP类的功能和用法。FastMCP是一个用于构建工具服务器的框架,具有以下主要特点:
- 初始化
ini
mcp = FastMCP("weather")
FastMCP实例化时需要一个名称参数,这里使用"weather"作为服务名称。这个名称用于标识该工具服务器。
- 工具装饰器
FastMCP提供了@mcp.tool()
装饰器,用于将函数注册为工具。例如:
python
@mcp.tool()
async def get_alerts(state: str) -> str:
# 函数实现
这个装饰器将普通函数转换为可以通过MCP服务器调用的工具。
- 异步支持
FastMCP完全支持异步操作,可以处理异步函数(使用async/await
语法)。这在处理I/O密集型操作(如API请求)时特别有用。
- 运行服务器
ini
mcp.run(transport='stdio')
FastMCP提供了run()
方法来启动服务器。可以指定不同的传输方式,这里使用'stdio'(标准输入输出)作为传输方式。
- 依赖管理
从pyproject.toml
文件可以看出,FastMCP是通过mcp[cli]
包提供的,版本要求≥1.6.0:
ini
dependencies = [
"mcp[cli]>=1.6.0",
]
在这个天气应用中,FastMCP被用来:
-
创建一个天气服务工具服务器
-
提供两个主要工具函数:
get_alerts
: 获取特定州的天气警报get_forecast
: 获取特定位置的天气预报
-
处理异步API请求
-
格式化和返回天气数据
查询天气和格式化函数
接下来这2个函数,用于查询和格式化来自 National Weather Service API 的数据:
python
async def make_nws_request(url: str) -> dict[str, Any] | None:
"""向 NWS API 发送请求,并进行适当的错误处理。
Args:
url: NWS API 的完整请求 URL
Returns:
dict: 成功时返回解析后的 JSON 响应
None: 请求失败时返回 None
"""
# 设置请求头
headers = {
"User-Agent": USER_AGENT, # 标识我们的应用
"Accept": "application/geo+json" # 指定期望的响应格式
}
# 创建异步 HTTP 客户端
async with httpx.AsyncClient() as client:
try:
# 发送 GET 请求,设置30秒超时
response = await client.get(url, headers=headers, timeout=30.0)
# 检查响应状态码,非2xx状态码会抛出异常
response.raise_for_status()
# 解析并返回 JSON 响应
return response.json()
except Exception:
# 捕获所有异常(网络错误、超时、JSON解析错误等)
return None
def format_alert(feature: dict) -> str:
"""将警报 feature 格式化为可读的字符串。"""
props = feature["properties"]
return f"""
事件: {props.get('event', 'Unknown')}
区域: {props.get('areaDesc', 'Unknown')}
严重性: {props.get('severity', 'Unknown')}
描述: {props.get('description', 'No description available')}
指示: {props.get('instruction', 'No specific instructions provided')}
"""
编写2个工具函数
Tool 函数负责实际执行每个 tool 的逻辑。让我们添加它:
python
@mcp.tool()
async def get_alerts(state: str) -> str:
"""获取美国州的天气警报。
Args:
state: 两个字母的美国州代码(例如 CA、NY)
"""
# 构建获取指定州警报的API URL
url = f"{NWS_API_BASE}/alerts/active/area/{state}"
# 发送请求获取警报数据
data = await make_nws_request(url)
# 检查响应是否有效且包含features字段
if not data or "features" not in data:
return "无法获取警报或未找到警报。"
# 检查是否有活跃的警报
if not data["features"]:
return "该州没有活跃的警报。"
# 格式化所有警报信息
alerts = [format_alert(feature) for feature in data["features"]]
# 用分隔符连接所有警报并返回
return "\n---\n".join(alerts)
@mcp.tool()
async def get_forecast(latitude: float, longitude: float) -> str:
"""获取某个位置的天气预报。
Args:
latitude: 位置的纬度
longitude: 位置的经度
"""
# 首先获取预报网格 endpoint
# NWS API 需要先通过经纬度获取预报网格点信息
points_url = f"{NWS_API_BASE}/points/{latitude},{longitude}"
points_data = await make_nws_request(points_url)
# 如果无法获取网格点数据,返回错误信息
if not points_data:
return "无法获取此位置的预报数据。"
# 从网格点响应中提取预报URL
# NWS API 的响应中包含了获取具体预报数据的URL
forecast_url = points_data["properties"]["forecast"]
forecast_data = await make_nws_request(forecast_url)
# 如果无法获取预报数据,返回错误信息
if not forecast_data:
return "无法获取详细预报。"
# 从预报数据中提取时段信息并格式化
# periods包含了不同时段(如"今天","今晚","明天"等)的预报
periods = forecast_data["properties"]["periods"]
forecasts = []
# 只显示未来5个时段的预报,避免信息过多
for period in periods[:5]:
# 为每个时段构建格式化的预报字符串
# 包含时段名称、温度、风向风速和详细预报
forecast = f"""
{period['name']}:
温度: {period['temperature']}°{period['temperatureUnit']}
风: {period['windSpeed']} {period['windDirection']}
预报: {period['detailedForecast']}
"""
forecasts.append(forecast)
# 用分隔符连接所有预报,返回完整的预报文本
return "\n---\n".join(forecasts)
运行服务器
最后,让我们初始化并运行 server:
ini
if __name__ == "__main__":
# 检查是否直接运行此文件
# 初始化 MCP 服务器并使用标准输入输出(stdio)作为传输方式运行
# stdio 传输方式允许通过标准输入输出流与其他程序进行通信
mcp.run(transport='stdio')
Server 已经完成!运行 uv run weather.py
以确认一切正常。
现在让我们从现有的 MCP host(Cursor) 测试你的 server。
Cursor 连接你的Server
在Cursor中填写MCP的配置信息:
json
{
"mcpServers": {
"weather": {
"command": "uv",
"args": [
"--directory",
"/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather",
"run",
"weather.py"
]
}
}
}
这告诉 Cursor:
- 有一个名为 "weather" 的 MCP server。
- 通过运行
uv --directory /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather run weather.py
来启动它。
底层执行流程
当你通过客户端(Cursor)提出问题时:
- 客户端(client)将你的问题发送给 LLM;
- LLM 分析可用的 tools 并决定使用哪些 tool;
- 客户端(client) 通过 MCP server 执行选择的 tool;
- 结果被发回给 LLM;
- LLM 制定自然语言响应;
- 响应显示给你;