MCP服务器(Model Context Protocol Server)是一种轻量级程序,旨在将大型语言模型(LLM)与外部数据源和工具无缝集成。它通过标准化的协议,使得LLM能够访问实时数据并执行更复杂的任务。
核心定义:MCP服务器作为连接AI与外部世界的标准化桥梁,类似于USB-C接口的"统一协议",使AI能够轻松对接各种业务接口,提供智能化功能。
主要功能:
资源(Resources):读取数据,如查询SQLite课程数据库、读取本地CSV文件等。
工具(Tools):执行函数,如微信消息实时抓取与总结、网页内容转Markdown等。
提示(Prompts):预置任务模板,如生成Git提交信息模板、自动编写代码注释等。
架构优势:MCP服务器基于JSON-RPC构建,提供了一个有状态会话协议,专注于客户端和服务器之间的上下文交换和采样协调。
传输层:MCP使用JSON-RPC 2.0作为其数据传输格式,传输层负责将MCP协议消息转换为JSON-RPC格式进行传输,并将接收到的JSON-RPC消息转换回MCP协议消息。
内置传输类型:
标准输入/输出(stdio):适用于本地集成和命令行工具,客户端将MCP服务器启动为子进程,服务器通过标准输入接收消息,通过标准输出写入响应。
服务器发送事件(SSE):支持服务器到客户端的流式传输,客户端通过HTTP POST请求向服务器发送消息,服务器通过SSE端点向客户端发送消息。
消息格式:包括请求、响应和通知三种类型。请求消息包含唯一标识符、方法名称和可选参数;响应消息包含结果或错误信息;通知消息用于服务器向客户端发送消息,无需唯一标识符。
AI模型服务:如大语言模型(GPT系列)、图像生成模型(Stable Diffusion)、语音识别/合成模型等。
工具和功能服务:如代码分析工具、文本处理工具、数据转换工具等。
集成服务:如API网关、模型编排服务、资源调度服务等
2.1 服务器依赖安装
uv add mcp httpx
2.2 服务器代码
天气查询服务器的代码如下,我的项目与新能源发电相关,所以返回的信息也与新能源发电相关联,具体代码如下,每一行都给了解释和说明,供大家参考。
导入必要的模块
import json # 导入 JSON 处理模块
import os # 导入 os 模块,用于环境变量和系统操作
import time # 导入 time 模块,用于时间相关操作
import logging # 导入日志模块,用于记录程序运行状态
import asyncio # 导入异步 IO 模块
from datetime import datetime, timedelta # 导入日期时间处理模块
from typing import Any, Optional, Dict # 导入类型提示模块
import httpx # 导入异步 HTTP 客户端模块
from mcp.server.fastmcp import FastMCP # 导入 MCP 服务器模块
# 配置日志系统
logging.basicConfig( # 配置日志基本设置
level=logging.INFO, # 设置日志级别为 INFO
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' # 设置日志格式
)
logger = logging.getLogger(__name__) # 创建日志记录器
# 初始化 MCP 服务器
mcp = FastMCP(title="Weather Server") # 创建 MCP 服务器实例
# OpenWeather API 配置
OPENWEATHER_API_BASE = "https://api.openweathermap.org/data/2.5/weather" # OpenWeather API 的基础 URL
API_KEY = os.getenv("OPENWEATHER_API_KEY", "YOUR_API_KEY") # 从环境变量获取 API 密钥,如果没有则使用默认值
USER_AGENT = "weather-app/1.0" # 设置用户代理标识
MAX_RETRIES = 3 # 设置最大重试次数
RETRY_DELAY = 1 # 设置重试延迟时间(秒)
CACHE_DURATION = 1800 # 设置缓存持续时间(30分钟)
# 简单的内存缓存
weather_cache: Dict[str, Dict[str, Any]] = {} # 创建天气数据缓存字典
async def fetch_weather(city: str) -> Optional[Dict[str, Any]]: # 定义异步获取天气数据的函数
"""
从 OpenWeather API 获取天气信息,包含重试机制和缓存。
Args:
city: 城市名称(英文)
Returns:
天气数据字典或 None(如果发生错误)
"""
# 检查缓存
cache_key = city.lower() # 将城市名转换为小写作为缓存键
if cache_key in weather_cache: # 检查缓存中是否存在数据
cache_data = weather_cache[cache_key] # 获取缓存数据
if datetime.now().timestamp() - cache_data['timestamp'] < CACHE_DURATION: # 检查缓存是否过期
logger.info(f"返回缓存数据: {city}") # 记录日志
return cache_data['data'] # 返回缓存的数据
params = { # 设置 API 请求参数
"q": city, # 城市名称
"appid": API_KEY, # API 密钥
"units": "metric", # 使用公制单位
"lang": "zh_cn" # 使用中文返回结果
}
headers = {"User-Agent": USER_AGENT} # 设置请求头
for attempt in range(MAX_RETRIES): # 重试循环
try:
async with httpx.AsyncClient() as client: # 创建异步 HTTP 客户端
response = await client.get( # 发送 GET 请求
OPENWEATHER_API_BASE, # API URL
params=params, # 请求参数
headers=headers, # 请求头
timeout=30.0 # 超时时间
)
response.raise_for_status() # 检查响应状态
data = response.json() # 解析 JSON 响应
# 更新缓存
weather_cache[cache_key] = { # 将数据存入缓存
'data': data, # 天气数据
'timestamp': datetime.now().timestamp() # 缓存时间戳
}
logger.info(f"成功获取天气数据: {city}") # 记录成功日志
return data # 返回天气数据
except httpx.HTTPStatusError as e: # 捕获 HTTP 错误
logger.error(f"HTTP错误 ({attempt + 1}/{MAX_RETRIES}): {e.response.status_code}") # 记录错误日志
if attempt < MAX_RETRIES - 1: # 如果还有重试机会
await asyncio.sleep(RETRY_DELAY * (attempt + 1)) # 等待一段时间后重试
else:
return {"error": f"HTTP错误: {e.response.status_code}"} # 返回错误信息
except Exception as e: # 捕获其他异常
logger.error(f"请求失败 ({attempt + 1}/{MAX_RETRIES}): {str(e)}") # 记录错误日志
if attempt < MAX_RETRIES - 1: # 如果还有重试机会
await asyncio.sleep(RETRY_DELAY * (attempt + 1)) # 等待一段时间后重试
else:
return {"error": f"请求失败: {str(e)}"} # 返回错误信息
return None # 如果所有重试都失败,返回 None
def format_weather(data: Dict[str, Any] | str) -> str: # 定义格式化天气数据的函数
"""
将天气数据格式化为易读文本,重点关注对新能源发电有影响的天气信息。
Args:
data: 天气数据(字典或JSON字符串)
Returns:
格式化后的天气信息
"""
if isinstance(data, str): # 如果输入是字符串
try:
data = json.loads(data) # 尝试解析 JSON 字符串
except json.JSONDecodeError as e: # 捕获 JSON 解析错误
logger.error(f"JSON解析错误: {e}") # 记录错误日志
return f"⚠️ 数据格式错误: {e}" # 返回错误信息
if "error" in data: # 检查是否包含错误信息
return f"⚠️ {data['error']}" # 返回错误信息
try:
city = data.get("name", "未知") # 获取城市名称
country = data.get("sys", {}).get("country", "未知") # 获取国家代码
temp = data.get("main", {}).get("temp", "N/A") # 获取温度
humidity = data.get("main", {}).get("humidity", "N/A") # 获取湿度
# 风能相关数据
wind_speed = data.get("wind", {}).get("speed", "N/A") # 获取风速
wind_deg = data.get("wind", {}).get("deg", "N/A") # 获取风向角度
wind_direction = get_wind_direction(wind_deg) # 转换风向角度为方向描述
# 太阳能相关数据
clouds = data.get("clouds", {}).get("all", "N/A") # 获取云量百分比
weather_list = data.get("weather", [{}]) # 获取天气描述列表
description = weather_list[0].get("description", "未知") # 获取天气描述
# 获取紫外线指数(如果API提供)
uvi = data.get("uvi", "N/A") # 获取紫外线指数
# 获取降雨量(如果API提供)
rain_1h = data.get("rain", {}).get("1h", "N/A") # 获取1小时降雨量
# 评估风能发电条件
wind_condition = "良好" if isinstance(wind_speed, (int, float)) and wind_speed >= 3.0 else "不足"
wind_power = "适合" if wind_condition == "良好" else "不适合"
# 评估太阳能发电条件
solar_condition = "良好" if isinstance(clouds, (int, float)) and clouds <= 30 else "不足"
solar_power = "适合" if solar_condition == "良好" else "不适合"
return ( # 返回格式化后的天气信息
f" {city}, {country}\n" # 城市和国家
f" 温度: {temp}°C\n" # 温度
f" 湿度: {humidity}%\n" # 湿度
f" 天气: {description}\n" # 天气描述
f"☁️ 云量: {clouds}% (太阳能发电条件: {solar_condition}, {solar_power}光伏发电)\n" # 云量和太阳能发电评估
f"☀️ 紫外线指数: {uvi} (影响太阳能板发电效率)\n" # 紫外线指数
f" 1小时降雨量: {rain_1h}mm (可能影响设备运行)\n" # 降雨量
f" 风速: {wind_speed} m/s ({wind_direction}) (风能发电条件: {wind_condition}, {wind_power}风力发电)\n" # 风速和风向
f"\n新能源发电评估:\n" # 新能源发电总体评估
f"1️⃣ 风能发电: {wind_power} (风速需≥3.0m/s)\n" # 风能发电评估
f"2️⃣ 太阳能发电: {solar_power} (云量需≤30%)\n" # 太阳能发电评估
f"3️⃣ 综合建议: {'建议开启新能源发电' if wind_condition == '良好' or solar_condition == '良好' else '建议使用传统能源'}" # 综合建议
)
except Exception as e: # 捕获所有异常
logger.error(f"数据格式化错误: {e}") # 记录错误日志
return f"⚠️ 数据格式化错误: {str(e)}" # 返回错误信息
def get_wind_direction(degrees: float | str) -> str: # 定义获取风向描述的函数
"""
将风向角度转换为方向描述。
Args:
degrees: 风向角度(0-360)
Returns:
风向描述(如:东北风)
"""
if isinstance(degrees, str) or degrees == "N/A": # 检查输入是否有效
return "未知" # 返回未知
directions = ["北", "东北", "东", "东南", "南", "西南", "西", "西北"] # 定义八个方向
index = round(degrees / 45) % 8 # 计算方向索引
return f"{directions[index]}风" # 返回风向描述
@mcp.tool() # 注册为 MCP 工具,修正装饰器语法
async def query_weather(city: str) -> str: # 定义查询天气的异步函数
"""
查询指定城市的天气信息。
Args:
city: 城市名称(英文)
Returns:
格式化后的天气信息
"""
logger.info(f"收到天气查询请求: {city}") # 记录请求日志
data = await fetch_weather(city) # 获取天气数据
if data is None: # 如果获取数据失败
return "⚠️ 获取天气数据失败" # 返回错误信息
return format_weather(data) # 返回格式化后的天气信息
if __name__ == "__main__": # 如果直接运行此文件
logger.info("启动天气查询服务器...") # 记录启动日志
mcp.run() # 启动 MCP 服务器