Python MCP 工具开发实战:创建天气查询工具
引言
在上一篇文章中,我们了解了 MCP(Model Context Protocol)的核心概念和架构。现在,让我们通过一个实际案例——创建天气查询工具,来深入学习如何开发自己的 MCP 工具。
选择天气查询作为示例的原因:
- ✅ 实用性强:天气信息是日常高频需求
- ✅ 简单易懂:API 调用逻辑清晰,便于理解 MCP 核心概念
- ✅ 功能完整:可以展示工具定义、参数处理、错误处理等完整流程
通过这个教程,你将学会:
- 理解 MCP 工具(Tools)的核心概念
- 使用 Python 和 FastMCP 创建 MCP 服务器
- 定义和实现工具函数
- 配置客户端连接
- 测试和调试 MCP 工具
环境准备
步骤 1:检查 Python 版本
MCP 需要 Python 3.11 及以上版本。首先检查你的 Python 版本:
python --version
# 或
python3 --version如果版本低于 3.11,请先升级 Python。
步骤 2:安装必要的库
我们需要安装两个库:
fastmcp:MCP 的简化框架,让开发更简单requests:用于调用天气 API
pip install fastmcp requests步骤 3:获取天气 API 密钥(可选)
本教程会提供两种实现方式:
- 使用免费 API(无需密钥,适合学习)
- 使用 OpenWeatherMap(需要注册,功能更强大)
如果选择 OpenWeatherMap,可以到 openweathermap.org 免费注册获取 API Key。
核心概念:理解 MCP 工具(Tools)
在开始编码之前,让我们先理解 MCP 的核心概念——工具(Tools)。
什么是 MCP 工具?
工具(Tools) 是 MCP 的核心概念之一,它代表一个可被 AI 模型调用的函数。
想象一下:
- 普通函数:只能被你的代码调用
- MCP 工具:可以被 AI 模型(如 Claude)调用,AI 会根据用户的需求自动选择合适的工具
工具的三要素
每个 MCP 工具都包含三个关键要素:
- 名称(Name):工具的唯一标识,如
get_weather - 描述(Description):告诉 AI 这个工具是做什么的,AI 会根据描述决定是否使用它
- 参数定义(Parameters):工具需要哪些输入参数,每个参数的类型和含义
工具如何工作?
用户:"北京今天天气怎么样?"
↓
AI 模型分析需求
↓
发现需要调用 get_weather 工具
↓
调用工具:get_weather(city="北京")
↓
工具返回结果:"北京今天晴天,温度 25°C"
↓
AI 将结果返回给用户演示步骤 1:创建最简单的 MCP 服务器
让我们从最简单的代码开始,逐步构建完整的天气工具。
创建文件
创建一个新文件 weather_server.py:
from mcp.server.fastmcp import FastMCP
# 创建 MCP 服务器实例
mcp = FastMCP("WeatherServer")
# 运行服务器
if __name__ == "__main__":
mcp.run()代码解析:
from mcp.server.fastmcp import FastMCP:导入 FastMCP 框架FastMCP("WeatherServer"):创建一个名为 "WeatherServer" 的 MCP 服务器实例mcp.run():启动服务器,开始监听来自客户端的请求
核心要点:
FastMCP是 MCP 服务器的容器,所有工具都注册在这个容器中- 服务器名称用于标识,可以是任意字符串
测试运行
运行这个文件:
python weather_server.py如果看到没有任何输出(或者只有一些日志信息),说明服务器已经启动成功!服务器会通过标准输入输出(stdio)与客户端通信。
演示步骤 2:定义第一个工具函数
现在让我们添加第一个工具函数——获取天气。
基础版本
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("WeatherServer")
@mcp.tool()
def get_weather(city: str) -> str:
"""获取指定城市的天气信息"""
return f"{city}的天气信息:晴天,25°C"
if __name__ == "__main__":
mcp.run()代码解析:
@mcp.tool()装饰器:- 这是 MCP 的魔法装饰器
- 它告诉 FastMCP:"这个函数是一个工具,可以被 AI 调用"
- 装饰器会自动提取函数的签名、参数类型和文档字符串
函数签名
def get_weather(city: str) -> str::city: str:参数名和类型注解,告诉 MCP 这个工具需要一个字符串类型的城市名-> str:返回类型注解,告诉 MCP 这个工具返回字符串
文档字符串
"""获取指定城市的天气信息""":- 这是工具的描述,AI 会根据这个描述决定是否使用这个工具
- 描述要清晰准确,帮助 AI 理解工具的用途
核心要点:
- 类型注解很重要:MCP 依赖类型注解来理解参数和返回值
- 文档字符串是工具的描述:AI 通过它理解工具的用途
- 装饰器是关键:
@mcp.tool()将普通函数转换为 MCP 工具
演示步骤 3:完善工具参数
让我们添加更多功能,比如支持温度单位选择。
添加可选参数
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("WeatherServer")
@mcp.tool()
def get_weather(
city: str,
unit: str = "celsius"
) -> str:
"""获取指定城市的当前天气信息
Args:
city: 城市名称,例如 '北京' 或 'Beijing'
unit: 温度单位,'celsius'(摄氏度)或 'fahrenheit'(华氏度),默认为 'celsius'
Returns:
包含天气信息的字符串
"""
if unit == "fahrenheit":
temp = "77°F"
else:
temp = "25°C"
return f"{city}的天气信息:晴天,{temp}"
if __name__ == "__main__":
mcp.run()代码解析:
可选参数
unit: str = "celsius":= "celsius"表示这是可选参数,默认值为 "celsius"- MCP 会自动识别可选参数,AI 调用时可以不提供这个参数
详细的文档字符串:
Args:部分详细说明每个参数的含义和示例Returns:部分说明返回值的内容- 这些信息帮助 AI 更好地理解和使用工具
核心要点:
- 默认参数 = 可选参数:有默认值的参数在 MCP 中是可选的
- 文档字符串要详细:详细的文档帮助 AI 正确使用工具
- 参数类型要明确:使用类型注解明确参数类型
演示步骤 4:集成真实的天气 API
现在让我们连接真实的天气 API,获取实际的天气数据。
方案一:使用免费 API(无需密钥)
我们使用一个免费的天气 API,无需注册即可使用:
import requests
from mcp.server.fastmcp import FastMCP
import os
mcp = FastMCP("WeatherServer")
@mcp.tool()
def get_weather(
city: str,
unit: str = "celsius"
) -> str:
"""获取指定城市的当前天气信息
Args:
city: 城市名称,例如 '北京' 或 'Beijing'
unit: 温度单位,'celsius'(摄氏度)或 'fahrenheit'(华氏度),默认为 'celsius'
Returns:
包含天气信息的字符串,如果查询失败则返回错误信息
"""
try:
# 使用免费的天气 API(示例,实际使用时请替换为真实 API)
# 这里使用 wttr.in 作为示例(无需 API 密钥)
url = f"https://wttr.in/{city}?format=j1"
response = requests.get(url, timeout=5)
if response.status_code != 200:
return f"无法获取 {city} 的天气信息,请检查城市名称是否正确"
data = response.json()
current = data['current_condition'][0]
temp_c = current['temp_C']
temp_f = current['temp_F']
condition = current['weatherDesc'][0]['value']
humidity = current['humidity']
if unit == "fahrenheit":
temp = f"{temp_f}°F"
else:
temp = f"{temp_c}°C"
return f"{city}当前天气:{condition},温度 {temp},湿度 {humidity}%"
except requests.exceptions.RequestException as e:
return f"获取天气信息时发生错误:{str(e)}"
except (KeyError, IndexError) as e:
return f"解析天气数据时发生错误,请稍后重试"
if __name__ == "__main__":
mcp.run()方案二:使用 OpenWeatherMap API(需要 API 密钥)
如果你有 OpenWeatherMap 的 API 密钥,可以使用这个版本:
import requests
from mcp.server.fastmcp import FastMCP
import os
mcp = FastMCP("WeatherServer")
# 从环境变量获取 API 密钥
API_KEY = os.getenv("WEATHER_API_KEY", "")
@mcp.tool()
def get_weather(
city: str,
unit: str = "celsius"
) -> str:
"""获取指定城市的当前天气信息
Args:
city: 城市名称,例如 '北京' 或 'Beijing'
unit: 温度单位,'celsius'(摄氏度)或 'fahrenheit'(华氏度),默认为 'celsius'
Returns:
包含天气信息的字符串,如果查询失败则返回错误信息
"""
if not API_KEY:
return "错误:未配置天气 API 密钥,请设置环境变量 WEATHER_API_KEY"
try:
# OpenWeatherMap API
url = "https://api.openweathermap.org/data/2.5/weather"
params = {
"q": city,
"appid": API_KEY,
"units": "metric" if unit == "celsius" else "imperial"
}
response = requests.get(url, params=params, timeout=5)
if response.status_code == 404:
return f"未找到城市 '{city}',请检查城市名称是否正确"
elif response.status_code != 200:
return f"获取天气信息失败,状态码:{response.status_code}"
data = response.json()
temp = data['main']['temp']
condition = data['weather'][0]['description']
humidity = data['main']['humidity']
temp_unit = "°C" if unit == "celsius" else "°F"
return f"{city}当前天气:{condition},温度 {temp}{temp_unit},湿度 {humidity}%"
except requests.exceptions.RequestException as e:
return f"网络请求错误:{str(e)}"
except (KeyError, IndexError) as e:
return f"解析天气数据时发生错误:{str(e)}"
if __name__ == "__main__":
mcp.run()代码解析:
错误处理:
try-except块捕获可能的异常- 网络错误、API 错误、数据解析错误都有相应的处理
- 返回友好的错误信息
环境变量:
os.getenv("WEATHER_API_KEY", "")从环境变量读取 API 密钥- 这样可以将敏感信息(API 密钥)与代码分离
API 调用:
- 使用
requests库发送 HTTP 请求 - 设置超时时间避免长时间等待
- 解析 JSON 响应数据
- 使用
核心要点:
- 错误处理很重要:API 调用可能失败,要有完善的错误处理
- 使用环境变量管理密钥:不要将 API 密钥硬编码在代码中
- 返回友好的错误信息:帮助用户理解问题所在
演示步骤 5:运行服务器
运行服务器
保存代码后,运行服务器:
python weather_server.py服务器启动后,会通过标准输入输出(stdio)与客户端通信。你不会看到太多输出,这是正常的。
验证服务器运行
如果服务器正常运行,你应该:
- ✅ 没有报错
- ✅ 进程在运行(没有立即退出)
- ✅ 可以通过 Ctrl+C 停止服务器
配置客户端连接
现在我们需要配置 MCP 客户端(如 Claude Desktop)来连接我们的服务器。
理解 MCP 架构
在配置之前,让我们回顾一下 MCP 的三层架构:
┌─────────────┐
│ MCP Host │ ← Claude Desktop(客户端应用)
└──────┬──────┘
│
┌──────▼──────┐
│ MCP Client │ ← 协议客户端(自动处理)
└──────┬──────┘
│
┌──────▼──────┐
│ MCP Server │ ← 我们的 weather_server.py
└─────────────┘- Host:使用 MCP 的应用(如 Claude Desktop)
- Client:协议客户端,负责与服务器通信(自动处理)
- Server:我们创建的
weather_server.py
Claude Desktop 配置
找到 Claude Desktop 的配置文件:
macOS:
~/Library/Application Support/Claude/claude_desktop_config.jsonWindows:
%APPDATA%\Claude\claude_desktop_config.jsonLinux:
~/.config/Claude/claude_desktop_config.json添加服务器配置
编辑配置文件,添加我们的天气服务器:
{
"mcpServers": {
"weather-server": {
"command": "python",
"args": ["/绝对路径/to/weather_server.py"],
"env": {
"WEATHER_API_KEY": "your-api-key-here"
}
}
}
}配置说明:
"weather-server":服务器名称,可以自定义"command": "python":运行服务器的命令"args":传递给命令的参数,这里是 Python 脚本的路径- ⚠️ 重要:必须使用绝对路径
"env":环境变量,用于传递 API 密钥等配置
路径示例:
- macOS/Linux:
"/Users/username/projects/weather_server.py" - Windows:
"C:\\Users\\username\\projects\\weather_server.py"
重启 Claude Desktop
配置完成后,重启 Claude Desktop 使配置生效。
重启后,你应该能在 Claude Desktop 中看到天气工具可用。尝试问 Claude:"北京今天天气怎么样?",Claude 应该会调用我们的 get_weather 工具。
测试和验证
测试工具功能
在 Claude Desktop 中测试:
基本查询:
用户:"北京今天天气怎么样?"应该返回北京的天气信息。
指定温度单位:
用户:"上海天气,用华氏度"应该返回华氏度的温度。
错误处理:
用户:"查询一个不存在的城市天气"应该返回友好的错误信息。
常见问题排查
问题 1:服务器无法启动
症状:运行 python weather_server.py 时报错
可能原因:
- Python 版本过低(需要 3.11+)
- 缺少依赖库(
fastmcp或requests)
解决方案:
# 检查 Python 版本
python --version
# 安装依赖
pip install fastmcp requests问题 2:客户端无法连接服务器
症状:Claude Desktop 中看不到工具
可能原因:
- 配置文件路径错误
- Python 脚本路径不是绝对路径
- 配置文件 JSON 格式错误
解决方案:
- 检查配置文件路径是否正确
- 确保使用绝对路径
- 验证 JSON 格式是否正确(可以使用 JSON 验证工具)
问题 3:工具调用失败
症状:Claude 调用了工具,但返回错误
可能原因:
- API 密钥未配置或错误
- 网络连接问题
- API 服务不可用
解决方案:
- 检查环境变量是否正确设置
- 测试网络连接
- 验证 API 密钥是否有效
调试技巧
添加日志输出:
pythonimport logging logging.basicConfig(level=logging.DEBUG)测试 API 调用: 在工具函数外直接测试 API:
python# 测试代码 response = requests.get("https://api.example.com/weather") print(response.json())检查工具注册: 确保
@mcp.tool()装饰器正确应用
扩展功能
现在你已经掌握了 MCP 工具的基础开发,让我们看看如何扩展功能。
添加更多工具
我们可以添加更多相关的工具函数:
@mcp.tool()
def get_weather_forecast(city: str, days: int = 3) -> str:
"""获取指定城市的天气预报
Args:
city: 城市名称
days: 预报天数,默认 3 天
Returns:
天气预报信息
"""
# 实现天气预报逻辑
return f"{city}未来{days}天天气预报:..."使用 Resources(资源)
Resources 是 MCP 的另一个核心概念,用于提供可访问的数据源:
@mcp.resource("city-list")
def get_city_list() -> list:
"""获取支持的城市列表"""
return ["北京", "上海", "广州", "深圳", ...]使用 Prompts(提示模板)
Prompts 用于定义预定义的提示模板:
@mcp.prompt()
def weather_query_template(city: str) -> str:
"""天气查询提示模板"""
return f"请查询{city}的天气信息,包括温度、湿度、天气状况。"完整代码示例
以下是使用免费 API 的完整代码:
import requests
from mcp.server.fastmcp import FastMCP
import os
# 创建 MCP 服务器实例
mcp = FastMCP("WeatherServer")
@mcp.tool()
def get_weather(
city: str,
unit: str = "celsius"
) -> str:
"""获取指定城市的当前天气信息
Args:
city: 城市名称,例如 '北京' 或 'Beijing'
unit: 温度单位,'celsius'(摄氏度)或 'fahrenheit'(华氏度),默认为 'celsius'
Returns:
包含天气信息的字符串,如果查询失败则返回错误信息
"""
try:
# 使用 wttr.in 免费 API
url = f"https://wttr.in/{city}?format=j1"
response = requests.get(url, timeout=5)
if response.status_code != 200:
return f"无法获取 {city} 的天气信息,请检查城市名称是否正确"
data = response.json()
current = data['current_condition'][0]
temp_c = current['temp_C']
temp_f = current['temp_F']
condition = current['weatherDesc'][0]['value']
humidity = current['humidity']
wind_speed = current['windspeedKmph']
if unit == "fahrenheit":
temp = f"{temp_f}°F"
else:
temp = f"{temp_c}°C"
return (
f"{city}当前天气:\n"
f"- 天气状况:{condition}\n"
f"- 温度:{temp}\n"
f"- 湿度:{humidity}%\n"
f"- 风速:{wind_speed} km/h"
)
except requests.exceptions.RequestException as e:
return f"获取天气信息时发生网络错误:{str(e)}"
except (KeyError, IndexError) as e:
return f"解析天气数据时发生错误,请稍后重试"
if __name__ == "__main__":
mcp.run()总结
通过这个教程,我们学习了:
核心概念回顾
MCP 工具(Tools):
- 可被 AI 调用的函数
- 包含名称、描述、参数定义三要素
- 使用
@mcp.tool()装饰器定义
FastMCP 框架:
- 简化 MCP 服务器开发
- 自动处理协议通信
- 通过装饰器注册工具
工具开发流程:
- 创建服务器实例
- 定义工具函数
- 添加类型注解和文档
- 实现业务逻辑
- 配置客户端连接
关键要点
- ✅ 类型注解很重要:MCP 依赖类型注解理解参数
- ✅ 文档字符串是工具的描述:帮助 AI 理解工具用途
- ✅ 错误处理不可少:API 调用可能失败,要有完善的错误处理
- ✅ 使用环境变量管理密钥:不要硬编码敏感信息
- ✅ 配置文件使用绝对路径:确保客户端能正确找到服务器
下一步学习方向
深入学习 MCP 概念:
- Resources(资源):提供数据源访问
- Prompts(提示):定义提示模板
扩展工具功能:
- 添加更多工具函数
- 实现复杂业务逻辑
- 集成更多外部服务
优化和部署:
- 添加日志和监控
- 性能优化
- 生产环境部署
参考资源:
💡 提示:MCP 工具开发的核心是理解"工具"的概念——将你的函数暴露给 AI,让 AI 能够根据用户需求自动调用。掌握了这个核心概念,你就能开发出各种强大的 MCP 工具。
