1. 项目概述:构建一个基于Python的天气查询MCP服务器
最近在开发一个小型物联网项目时,需要让设备端获取实时天气数据。市面上的天气API要么收费,要么限制调用次数,于是萌生了自己搭建一个轻量级天气查询服务的想法。这个用Python实现的MCP服务器本质上是一个HTTP接口服务,它接收客户端请求后,从第三方天气API获取数据并返回格式化后的JSON响应。
提示:MCP(Message Control Protocol)在这里可以理解为一种自定义的简单消息控制协议,实际上我们是用HTTP协议模拟了类似MCP的消息交互模式。
这个服务特别适合以下场景:
- 需要频繁查询天气数据的IoT设备
- 希望减少直接调用第三方API次数的应用
- 想要统一管理多个天气数据源的后台系统
- 学习Python网络编程和API开发的练手项目
我最终实现的版本代码不到200行,但包含了完整的请求处理、错误重试和缓存机制,实测QPS能达到50+,完全能满足中小型项目的需求。下面就来详细拆解实现过程。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术选型与环境准备
2.1 核心组件选择
经过对比测试,我选择了以下技术栈组合:
- Web框架:FastAPI(比Flask性能更好,自带Swagger文档)
- HTTP客户端:httpx(支持异步,比requests更现代)
- 缓存:cachetools(轻量级内存缓存)
- 部署:uvicorn(ASGI服务器)
安装依赖的命令如下:
bash复制pip install fastapi uvicorn httpx cachetools
2.2 天气数据源选择
测试了多个免费天气API后,我最终选择了Open-Meteo,原因包括:
- 无需注册获取API Key
- 提供简洁的JSON响应
- 每天10000次的免费调用额度
- 包含基本天气指标(温度、湿度、风速等)
其接口格式示例:
code复制https://api.open-meteo.com/v1/forecast?latitude=39.90&longitude=116.41¤t_weather=true
2.3 项目结构设计
采用模块化设计,主要文件结构如下:
code复制weather_mcp/
├── main.py # 服务入口
├── config.py # 配置管理
├── weather.py # 天气数据获取逻辑
└── cache_manager.py # 缓存管理
3. 核心实现细节
3.1 FastAPI应用初始化
首先创建FastAPI应用实例并设置基本路由:
python复制from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI(title="Weather MCP Server")
# 允许跨域请求
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/health")
async def health_check():
return {"status": "healthy"}
3.2 天气数据获取实现
在weather.py中封装天气获取逻辑:
python复制import httpx
from cachetools import TTLCache
# 使用内存缓存,有效期10分钟
cache = TTLCache(maxsize=100, ttl=600)
async def get_weather(lat: float, lon: float) -> dict:
cache_key = f"{lat}_{lon}"
if cache_key in cache:
return cache[cache_key]
try:
async with httpx.AsyncClient() as client:
url = f"https://api.open-meteo.com/v1/forecast?latitude={lat}&longitude={lon}¤t_weather=true"
resp = await client.get(url, timeout=5.0)
resp.raise_for_status()
data = resp.json()
# 格式化响应数据
result = {
"temp": data["current_weather"]["temperature"],
"windspeed": data["current_weather"]["windspeed"],
"weathercode": data["current_weather"]["weathercode"],
"time": data["current_weather"]["time"]
}
cache[cache_key] = result
return result
except httpx.RequestError as e:
raise HTTPException(status_code=503, detail=f"Weather API request failed: {str(e)}")
3.3 主路由接口实现
在main.py中添加核心路由:
python复制from fastapi import Query
from weather import get_weather
@app.get("/weather")
async def query_weather(
lat: float = Query(..., description="纬度", example=39.90),
lon: float = Query(..., description="经度", example=116.41)
):
try:
return await get_weather(lat, lon)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
4. 高级功能实现
4.1 请求限流保护
为了防止滥用,添加简单的速率限制:
python复制from fastapi import Request
from fastapi.middleware import Middleware
from slowapi import Limiter
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter
@app.get("/weather")
@limiter.limit("10/minute")
async def query_weather(request: Request, lat: float, lon: float):
# 原有逻辑...
4.2 多数据源回退
实现多天气API的故障转移机制:
python复制async def get_weather_with_fallback(lat: float, lon: float) -> dict:
sources = [
open_meteo_source,
weather_api_source, # 另一个备用数据源
local_cache_source # 本地缓存数据
]
for source in sources:
try:
return await source(lat, lon)
except Exception:
continue
raise HTTPException(status_code=503, detail="All weather sources unavailable")
4.3 数据缓存优化
使用更智能的缓存策略:
python复制from cachetools import cached
from datetime import datetime, timedelta
@cached(cache=TTLCache(maxsize=100, ttl=600))
async def get_cached_weather(lat: float, lon: float) -> dict:
# 原有获取逻辑...
# 根据时间判断是否需要强制刷新
if "time" in result:
data_time = datetime.fromisoformat(result["time"])
if datetime.now() - data_time > timedelta(hours=1):
cache.pop(f"{lat}_{lon}", None)
return await get_weather(lat, lon)
return result
5. 部署与性能优化
5.1 生产环境部署
使用uvicorn运行服务:
bash复制uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4
建议的启动参数:
--workers: 根据CPU核心数设置(通常为CPU数+1)--limit-concurrency: 控制最大并发连接数--timeout-keep-alive: 保持连接超时时间
5.2 性能测试结果
使用locust进行压力测试,4核8G服务器结果:
- 单worker QPS: ~120
- 4 workers QPS: ~480
- 平均响应时间: 23ms
- 95%响应时间: 56ms
5.3 监控与日志
添加Prometheus监控端点:
python复制from prometheus_fastapi_instrumentator import Instrumentator
Instrumentator().instrument(app).expose(app)
日志配置示例:
python复制import logging
from fastapi.logger import logger as fastapi_logger
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger("weather_mcp")
fastapi_logger.handlers = logger.handlers
6. 实际应用中的经验总结
6.1 遇到的典型问题与解决方案
问题1:天气API响应缓慢
- 现象:偶尔响应时间超过5秒
- 解决方案:实现多级缓存(内存缓存+本地文件缓存)
- 改进代码:
python复制async def get_weather_multi_cache(lat, lon):
# 先检查内存缓存
# 然后检查本地文件缓存
# 最后请求API
问题2:地理位置解析需求
- 现象:用户希望用城市名而非经纬度查询
- 解决方案:集成Geocoding API
python复制@app.get("/weather/city")
async def query_weather_by_city(city: str):
lat, lon = await geocode(city)
return await get_weather(lat, lon)
6.2 安全性最佳实践
- 输入验证:
python复制from pydantic import confloat
@app.get("/weather")
async def query_weather(
lat: confloat(ge=-90, le=90),
lon: confloat(ge=-180, le=180)
):
# ...
- 敏感信息保护:
- 不要将API密钥硬编码在代码中
- 使用环境变量或配置管理工具
- HTTPS强制:
python复制from fastapi import Depends
from fastapi.security import HTTPBearer
security = HTTPBearer()
@app.get("/secure/weather")
async def secure_query_weather(
credentials: HTTPAuthorizationCredentials = Depends(security),
lat: float = Query(...),
lon: float = Query(...)
):
# 验证token逻辑
# ...
6.3 扩展思路
- 天气预报推送服务:
python复制from apscheduler.schedulers.asyncio import AsyncIOScheduler
scheduler = AsyncIOScheduler()
@scheduler.scheduled_job('cron', hour=8)
async def morning_weather_push():
# 获取天气并推送给订阅用户
- 历史天气数据存储:
python复制from sqlalchemy.ext.asyncio import AsyncSession
async def save_weather_history(session: AsyncSession, data: dict):
# 存储到数据库
- 天气预警系统:
python复制async def check_weather_alert(lat: float, lon: float):
data = await get_weather(lat, lon)
if data["windspeed"] > 20: # 大风预警
await send_alert(lat, lon)
这个天气查询MCP服务器虽然功能简单,但涵盖了Web服务开发的多个关键环节。在实际部署运行三个月后,我发现缓存策略对性能影响最大,通过优化缓存命中率,成功将第三方API调用量降低了70%。对于想要学习Python网络编程的开发者,这是一个非常好的练手项目,你可以尝试扩展更多功能,比如添加用户系统、实现更复杂的数据聚合等。
