Skip to content

Python MCP 工具开发实战:创建天气查询工具

引言

在上一篇文章中,我们了解了 MCP(Model Context Protocol)的核心概念和架构。现在,让我们通过一个实际案例——创建天气查询工具,来深入学习如何开发自己的 MCP 工具。

选择天气查询作为示例的原因:

  • 实用性强:天气信息是日常高频需求
  • 简单易懂:API 调用逻辑清晰,便于理解 MCP 核心概念
  • 功能完整:可以展示工具定义、参数处理、错误处理等完整流程

通过这个教程,你将学会:

  1. 理解 MCP 工具(Tools)的核心概念
  2. 使用 Python 和 FastMCP 创建 MCP 服务器
  3. 定义和实现工具函数
  4. 配置客户端连接
  5. 测试和调试 MCP 工具

环境准备

步骤 1:检查 Python 版本

MCP 需要 Python 3.11 及以上版本。首先检查你的 Python 版本:

bash
python --version
# 或
python3 --version

如果版本低于 3.11,请先升级 Python。

步骤 2:安装必要的库

我们需要安装两个库:

  • fastmcp:MCP 的简化框架,让开发更简单
  • requests:用于调用天气 API
bash
pip install fastmcp requests

步骤 3:获取天气 API 密钥(可选)

本教程会提供两种实现方式:

  1. 使用免费 API(无需密钥,适合学习)
  2. 使用 OpenWeatherMap(需要注册,功能更强大)

如果选择 OpenWeatherMap,可以到 openweathermap.org 免费注册获取 API Key。


核心概念:理解 MCP 工具(Tools)

在开始编码之前,让我们先理解 MCP 的核心概念——工具(Tools)

什么是 MCP 工具?

工具(Tools) 是 MCP 的核心概念之一,它代表一个可被 AI 模型调用的函数

想象一下:

  • 普通函数:只能被你的代码调用
  • MCP 工具:可以被 AI 模型(如 Claude)调用,AI 会根据用户的需求自动选择合适的工具

工具的三要素

每个 MCP 工具都包含三个关键要素:

  1. 名称(Name):工具的唯一标识,如 get_weather
  2. 描述(Description):告诉 AI 这个工具是做什么的,AI 会根据描述决定是否使用它
  3. 参数定义(Parameters):工具需要哪些输入参数,每个参数的类型和含义

工具如何工作?

用户:"北京今天天气怎么样?"

AI 模型分析需求

发现需要调用 get_weather 工具

调用工具:get_weather(city="北京")

工具返回结果:"北京今天晴天,温度 25°C"

AI 将结果返回给用户

演示步骤 1:创建最简单的 MCP 服务器

让我们从最简单的代码开始,逐步构建完整的天气工具。

创建文件

创建一个新文件 weather_server.py

python
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 服务器的容器,所有工具都注册在这个容器中
  • 服务器名称用于标识,可以是任意字符串

测试运行

运行这个文件:

bash
python weather_server.py

如果看到没有任何输出(或者只有一些日志信息),说明服务器已经启动成功!服务器会通过标准输入输出(stdio)与客户端通信。


演示步骤 2:定义第一个工具函数

现在让我们添加第一个工具函数——获取天气。

基础版本

python
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()

代码解析:

  1. @mcp.tool() 装饰器

    • 这是 MCP 的魔法装饰器
    • 它告诉 FastMCP:"这个函数是一个工具,可以被 AI 调用"
    • 装饰器会自动提取函数的签名、参数类型和文档字符串
  2. 函数签名 def get_weather(city: str) -> str:

    • city: str:参数名和类型注解,告诉 MCP 这个工具需要一个字符串类型的城市名
    • -> str:返回类型注解,告诉 MCP 这个工具返回字符串
  3. 文档字符串 """获取指定城市的天气信息"""

    • 这是工具的描述,AI 会根据这个描述决定是否使用这个工具
    • 描述要清晰准确,帮助 AI 理解工具的用途

核心要点:

  • 类型注解很重要:MCP 依赖类型注解来理解参数和返回值
  • 文档字符串是工具的描述:AI 通过它理解工具的用途
  • 装饰器是关键@mcp.tool() 将普通函数转换为 MCP 工具

演示步骤 3:完善工具参数

让我们添加更多功能,比如支持温度单位选择。

添加可选参数

python
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()

代码解析:

  1. 可选参数 unit: str = "celsius"

    • = "celsius" 表示这是可选参数,默认值为 "celsius"
    • MCP 会自动识别可选参数,AI 调用时可以不提供这个参数
  2. 详细的文档字符串

    • Args: 部分详细说明每个参数的含义和示例
    • Returns: 部分说明返回值的内容
    • 这些信息帮助 AI 更好地理解和使用工具

核心要点:

  • 默认参数 = 可选参数:有默认值的参数在 MCP 中是可选的
  • 文档字符串要详细:详细的文档帮助 AI 正确使用工具
  • 参数类型要明确:使用类型注解明确参数类型

演示步骤 4:集成真实的天气 API

现在让我们连接真实的天气 API,获取实际的天气数据。

方案一:使用免费 API(无需密钥)

我们使用一个免费的天气 API,无需注册即可使用:

python
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 密钥,可以使用这个版本:

python
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()

代码解析:

  1. 错误处理

    • try-except 块捕获可能的异常
    • 网络错误、API 错误、数据解析错误都有相应的处理
    • 返回友好的错误信息
  2. 环境变量

    • os.getenv("WEATHER_API_KEY", "") 从环境变量读取 API 密钥
    • 这样可以将敏感信息(API 密钥)与代码分离
  3. API 调用

    • 使用 requests 库发送 HTTP 请求
    • 设置超时时间避免长时间等待
    • 解析 JSON 响应数据

核心要点:

  • 错误处理很重要:API 调用可能失败,要有完善的错误处理
  • 使用环境变量管理密钥:不要将 API 密钥硬编码在代码中
  • 返回友好的错误信息:帮助用户理解问题所在

演示步骤 5:运行服务器

运行服务器

保存代码后,运行服务器:

bash
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.json

Windows:

%APPDATA%\Claude\claude_desktop_config.json

Linux:

~/.config/Claude/claude_desktop_config.json

添加服务器配置

编辑配置文件,添加我们的天气服务器:

json
{
  "mcpServers": {
    "weather-server": {
      "command": "python",
      "args": ["/绝对路径/to/weather_server.py"],
      "env": {
        "WEATHER_API_KEY": "your-api-key-here"
      }
    }
  }
}

配置说明:

  1. "weather-server":服务器名称,可以自定义
  2. "command": "python":运行服务器的命令
  3. "args":传递给命令的参数,这里是 Python 脚本的路径
    • ⚠️ 重要:必须使用绝对路径
  4. "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. 基本查询

    用户:"北京今天天气怎么样?"

    应该返回北京的天气信息。

  2. 指定温度单位

    用户:"上海天气,用华氏度"

    应该返回华氏度的温度。

  3. 错误处理

    用户:"查询一个不存在的城市天气"

    应该返回友好的错误信息。

常见问题排查

问题 1:服务器无法启动

症状:运行 python weather_server.py 时报错

可能原因

  • Python 版本过低(需要 3.11+)
  • 缺少依赖库(fastmcprequests

解决方案

bash
# 检查 Python 版本
python --version

# 安装依赖
pip install fastmcp requests

问题 2:客户端无法连接服务器

症状:Claude Desktop 中看不到工具

可能原因

  • 配置文件路径错误
  • Python 脚本路径不是绝对路径
  • 配置文件 JSON 格式错误

解决方案

  • 检查配置文件路径是否正确
  • 确保使用绝对路径
  • 验证 JSON 格式是否正确(可以使用 JSON 验证工具)

问题 3:工具调用失败

症状:Claude 调用了工具,但返回错误

可能原因

  • API 密钥未配置或错误
  • 网络连接问题
  • API 服务不可用

解决方案

  • 检查环境变量是否正确设置
  • 测试网络连接
  • 验证 API 密钥是否有效

调试技巧

  1. 添加日志输出

    python
    import logging
    logging.basicConfig(level=logging.DEBUG)
  2. 测试 API 调用: 在工具函数外直接测试 API:

    python
    # 测试代码
    response = requests.get("https://api.example.com/weather")
    print(response.json())
  3. 检查工具注册: 确保 @mcp.tool() 装饰器正确应用


扩展功能

现在你已经掌握了 MCP 工具的基础开发,让我们看看如何扩展功能。

添加更多工具

我们可以添加更多相关的工具函数:

python
@mcp.tool()
def get_weather_forecast(city: str, days: int = 3) -> str:
    """获取指定城市的天气预报
    
    Args:
        city: 城市名称
        days: 预报天数,默认 3 天
    
    Returns:
        天气预报信息
    """
    # 实现天气预报逻辑
    return f"{city}未来{days}天天气预报:..."

使用 Resources(资源)

Resources 是 MCP 的另一个核心概念,用于提供可访问的数据源:

python
@mcp.resource("city-list")
def get_city_list() -> list:
    """获取支持的城市列表"""
    return ["北京", "上海", "广州", "深圳", ...]

使用 Prompts(提示模板)

Prompts 用于定义预定义的提示模板:

python
@mcp.prompt()
def weather_query_template(city: str) -> str:
    """天气查询提示模板"""
    return f"请查询{city}的天气信息,包括温度、湿度、天气状况。"

完整代码示例

以下是使用免费 API 的完整代码:

python
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()

总结

通过这个教程,我们学习了:

核心概念回顾

  1. MCP 工具(Tools)

    • 可被 AI 调用的函数
    • 包含名称、描述、参数定义三要素
    • 使用 @mcp.tool() 装饰器定义
  2. FastMCP 框架

    • 简化 MCP 服务器开发
    • 自动处理协议通信
    • 通过装饰器注册工具
  3. 工具开发流程

    • 创建服务器实例
    • 定义工具函数
    • 添加类型注解和文档
    • 实现业务逻辑
    • 配置客户端连接

关键要点

  • 类型注解很重要:MCP 依赖类型注解理解参数
  • 文档字符串是工具的描述:帮助 AI 理解工具用途
  • 错误处理不可少:API 调用可能失败,要有完善的错误处理
  • 使用环境变量管理密钥:不要硬编码敏感信息
  • 配置文件使用绝对路径:确保客户端能正确找到服务器

下一步学习方向

  1. 深入学习 MCP 概念

    • Resources(资源):提供数据源访问
    • Prompts(提示):定义提示模板
  2. 扩展工具功能

    • 添加更多工具函数
    • 实现复杂业务逻辑
    • 集成更多外部服务
  3. 优化和部署

    • 添加日志和监控
    • 性能优化
    • 生产环境部署
  4. 参考资源


💡 提示:MCP 工具开发的核心是理解"工具"的概念——将你的函数暴露给 AI,让 AI 能够根据用户需求自动调用。掌握了这个核心概念,你就能开发出各种强大的 MCP 工具。