MCP实战:打造一个实时获取天气信息的小助手

本文将带您了解大模型上下文协议(Model Context Protocol, MCP),并通过一个获取实时天气信息的实战项目,手把手教您如何实现AI模型与外部工具的无缝交互。

什么是Model Context Protocol (MCP)?

Model Context Protocol (MCP) 是一种开放协议,专为大语言模型(如 Claude、ChatGPT等)设计,允许这些模型与外部系统安全地交互。简单来说,MCP 提供了一种标准化的方式,让AI模型能够:

  • • 调用外部工具和API

  • • 访问实时数据和信息

  • • 获取环境上下文

  • • 执行复杂操作

通过MCP,AI模型不再局限于训练数据,而是可以获取实时信息、控制外部系统,并执行各种实用任务。

 

为什么需要MCP?对比传统方法

在AI应用开发中,有几种主流方式让模型与外部世界交互:

特性

传统REST API

Function Calling

MCP

实现方式

直接调用HTTP接口

模型输出JSON格式工具调用

标准化协议

安全性

中等(需手动处理)

中等(解析不稳定)

高(沙箱隔离)

集成难度

高(需自行实现)

中等(需处理解析错误)

低(标准接口)

交互方式

异步、单向

半同步

同步、双向

上下文感知

有限

完整

适用场景

简单集成

单次调用

复杂工具链

传统REST API的局限

传统方式中,开发者需要:

  1. 1. 解析模型输出

  2. 2. 识别API调用意图

  3. 3. 手动构造API请求

  4. 4. 将结果返回给模型

这种方式不仅繁琐,而且容易出错,特别是当需要处理多个API调用或复杂逻辑时。

Function Calling的进步与局限

Function Calling(如OpenAI的函数调用或Anthropic的Tool Use)是一种改进,模型可以直接输出结构化的JSON来表示函数调用意图。但它仍有局限:

  1. 1. 输出格式不稳定,需要额外验证和错误处理

  2. 2. 安全边界模糊,需要开发者自行实现安全措施

  3. 3. 缺乏标准化,不同模型实现差异大

MCP的优势

MCP通过标准化协议解决了上述问题:

  1. 1. 标准接口:提供统一的工具定义和调用方式

  2. 2. 安全隔离:工具在沙箱环境中执行,减少安全风险

  3. 3. 双向通信:模型和工具可以进行实时交互

  4. 4. 环境感知:工具可以访问完整上下文

  5. 5. 简化开发:开发者只需实现工具逻辑,协议处理由MCP框架管理

MCP天气工具实战项目

下面,我们将通过一个实际项目,展示如何使用MCP创建一个天气信息工具,让AI模型能够查询实时天气数据。

项目介绍

这是一个基于MCP的天气工具演示项目,通过和风天气API获取实时天气数据,提供以下功能:

  1. 1. 天气预警查询:获取指定城市的天气灾害预警信息

  2. 2. 天气预报查询:获取指定城市未来几天的天气预报

项目架构

项目分为三个主要部分:

┌─────────────┐     stdio    ┌──────────────┐
│             │◄────────────►│              │
│  MCP 客户端  │              │  MCP 服务器   │
│             │              │              │
└─────────────┘              └──────────────┘
                                   ▲
                                   │
                              调试 │
                                   │
                                   ▼
                            ┌─────────────┐
                            │             │
                            │MCP Inspector│
                            │             │
                            └─────────────┘
  1. 1. MCP服务器:提供天气工具的核心实现

  2. 2. MCP客户端:连接服务器,发送工具调用请求

  3. 3. MCP Inspector:用于调试和测试服务器

环境准备

开始前,我们需要准备以下环境:

  • • Python 3.10.12 或更高版本

  • • NodeJS 22.14.0+ 和 NPM 10.9.2+(用于MCP Inspector)

  • • 和风天气API Key和API Host(注册地址[1])【具体流程参考文末】

实战步骤

第一步:创建MCP服务器

MCP服务器是提供工具功能的核心部分。以下是实现天气服务器的核心代码:

import os
import json
import httpx
import asyncio
from dotenv import load_dotenv
from modelcontextprotocol.server import (
    create_server,
    ServerConfig,
    tools,
    JsonSchema,
)

# 加载环境变量
load_dotenv()
API_KEY = os.getenv("QWEATHER_API_KEY")
API_HOST = os.getenv("QWEATHER_API_HOST", "https://XXX.qweather.com")

# 定义天气预警工具
@tools.tool(
    name="get_weather_warning",
    description="获取指定位置的天气灾害预警",
    parameters=JsonSchema(
        {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "城市ID或经纬度坐标(经度,纬度)\n例如:'101010100'(北京)或 '116.41,39.92'",
                },
            },
            "required": ["location"],
        }
    ),
)
asyncdefget_weather_warning(location: str) -> str:
    """
    获取指定位置的天气灾害预警
    
    参数:
        location: 城市ID或经纬度坐标(经度,纬度)
                例如:'101010100'(北京)或 '116.41,39.92'
        
    返回:
        格式化的预警信息字符串
    """
    asyncwith httpx.AsyncClient() as client:
        response = await client.get(
            f"{API_HOST}/v7/warning/now",
            params={
                "location": location,
                "key": API_KEY,
                "lang": "zh",
            },
        )
        data = response.json()
        
        if data["code"] != "200":
            returnf"获取天气预警失败: {data['code']}"
        
        warnings = data.get("warning", [])
        ifnot warnings:
            return"当前没有天气预警信息"
            
        result = []
        for warning in warnings:
            result.append(
                f"预警ID: {warning['id']}\n"
                f"标题: {warning['title']}\n"
                f"发布时间: {warning['pubTime']}\n"
                f"开始时间: {warning['startTime']}\n"
                f"结束时间: {warning['endTime']}\n"
                f"预警类型: {warning['typeName']}\n"
                f"预警等级: {warning['severityName']} ({warning['level']})\n"
                f"发布单位: {warning['sender']}\n"
                f"状态: {warning['status']}\n"
                f"详细信息: {warning['text']}"
            )
        
        return"\n\n".join(result)

# 定义天气预报工具
@tools.tool(
    name="get_daily_forecast",
    description="获取指定位置的天气预报",
    parameters=JsonSchema(
        {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "城市ID或经纬度坐标(经度,纬度)\n例如:'101010100'(北京)或 '116.41,39.92'",
                },
                "days": {
                    "type": "integer",
                    "description": "预报天数,可选值为 3、7、10、15、30,默认为 3",
                    "enum": [3, 7, 10, 15, 30],
                    "default": 3,
                },
            },
            "required": ["location"],
        }
    ),
)
asyncdefget_daily_forecast(location: str, days: int = 3) -> str:
    """
    获取指定位置的天气预报
    
    参数:
        location: 城市ID或经纬度坐标(经度,纬度)
                例如:'101010100'(北京)或 '116.41,39.92'
        days: 预报天数,可选值为 3、7、10、15、30,默认为 3
        
    返回:
        格式化的天气预报字符串
    """
    # 根据天数选择API版本
    version = "3d"if days == 3else"7d"if days == 7else"10d"if days in [10, 15, 30] else"3d"
    
    asyncwith httpx.AsyncClient() as client:
        response = await client.get(
            f"{API_HOST}/v7/weather/{version}",
            params={
                "location": location,
                "key": API_KEY,
                "lang": "zh",
            },
        )
        data = response.json()
        
        if data["code"] != "200":
            returnf"获取天气预报失败: {data['code']}"
        
        daily = data.get("daily", [])
        ifnot daily:
            return"无法获取天气预报信息"
            
        result = []
        for day in daily[:days]:  # 限制天数
            result.append(
                f"日期: {day['fxDate']}\n"
                f"日出: {day['sunrise']}  日落: {day['sunset']}\n"
                f"最高温度: {day['tempMax']}°C  最低温度: {day['tempMin']}°C\n"
                f"白天天气: {day['textDay']}  夜间天气: {day['textNight']}\n"
                f"白天风向: {day['windDirDay']} {day['windScaleDay']}级 ({day['windSpeedDay']}km/h)\n"
                f"夜间风向: {day['windDirNight']} {day['windScaleNight']}级 ({day['windSpeedNight']}km/h)\n"
                f"相对湿度: {day['humidity']}%\n"
                f"降水量: {day['precip']}mm\n"
                f"紫外线指数: {day['uvIndex']}\n"
                f"能见度: {day['vis']}km"
            )
        
        return"\n\n---\n\n".join(result)

# 主函数
asyncdefmain():
    config = ServerConfig()
    server = create_server(config)
    
    # 注册工具
    server.register_tool(get_weather_warning)
    server.register_tool(get_daily_forecast)
    
    # 启动服务器
    await server.serve()

if __name__ == "__main__":
    asyncio.run(main())

第二步:实现MCP客户端

MCP客户端用于连接服务器并调用工具。以下是客户端的实现:

import asyncio
import json
import os
import signal
import subprocess
import sys
from asyncio import create_subprocess_exec
from asyncio.subprocess import PIPE

from modelcontextprotocol.client import create_client, ClientConfig
from modelcontextprotocol.protocol.tool_schemas import ToolSchema

# 启动服务器进程
asyncdefstart_server_process():
    server_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "server", "weather_server.py")
    returnawait create_subprocess_exec(
        sys.executable, server_path,
        stdin=PIPE, stdout=PIPE, stderr=PIPE
    )

# 主函数
asyncdefmain():
    print("启动 MCP 服务器进程...")
    server_process = await start_server_process()
    
    # 配置客户端
    config = ClientConfig()
    client = create_client(config)
    
    try:
        # 连接到服务器
        await client.connect_process(server_process)
        
        # 获取可用工具
        tools = await client.get_tools()
        print(f"已连接到服务器,可用工具: {len(tools)}")
        
        # 显示工具信息
        for tool in tools:
            print(f"  - {tool.name}: \n{tool.description}\n")
        
        print("使用 'help' 查看帮助,使用 'exit' 退出\n")
        
        # 命令行交互循环
        whileTrue:
            user_input = input("> ").strip()
            
            if user_input.lower() == "exit":
                break
                
            elif user_input.lower() == "help":
                print("\n可用命令:")
                print("  help - 显示此帮助信息")
                print("  list - 列出可用工具")
                print("  call <工具名> <参数JSON> - 调用工具")
                print("  exit - 退出程序")
                print("\n示例:")
                print('  call get_weather_warning {"location": "101010100"}')
                print("  call get_daily_forecast 116.41,39.92")
                print("  call get_daily_forecast 101010100 7")
                print()
                
            elif user_input.lower() == "list":
                for tool in tools:
                    print(f"  - {tool.name}: {tool.description[:50]}...")
                print()
                
            elif user_input.lower().startswith("call "):
                # 解析命令
                parts = user_input[5:].strip().split(" ", 1)
                iflen(parts) < 1:
                    print("错误: 需要指定工具名称")
                    continue
                    
                tool_name = parts[0]
                
                # 查找工具
                tool = next((t for t in tools if t.name == tool_name), None)
                ifnot tool:
                    print(f"错误: 找不到工具 '{tool_name}'")
                    continue
                
                # 解析参数
                args = {}
                iflen(parts) > 1:
                    arg_text = parts[1].strip()
                    
                    # 简单参数处理
                    ifnot arg_text.startswith("{"):
                        # 简单模式: call get_daily_forecast 101010100 7
                        simple_args = arg_text.split(" ")
                        
                        # 检查是否为天气预报工具
                        if tool_name == "get_daily_forecast":
                            iflen(simple_args) >= 1:
                                args["location"] = simple_args[0]
                            iflen(simple_args) >= 2:
                                try:
                                    args["days"] = int(simple_args[1])
                                except ValueError:
                                    print("错误: days 参数必须是整数")
                                    continue
                        elif tool_name == "get_weather_warning":
                            iflen(simple_args) >= 1:
                                args["location"] = simple_args[0]
                        else:
                            print("错误: 简单参数模式仅支持预定义工具")
                            continue
                    else:
                        # JSON模式: call get_weather_warning {"location": "101010100"}
                        try:
                            args = json.loads(arg_text)
                        except json.JSONDecodeError:
                            print("错误: 无效的JSON参数")
                            continue
                
                print("正在调用工具...\n")
                try:
                    # 调用工具
                    result = await client.call_tool(tool_name, args)
                    print("结果:")
                    print(result)
                    print()
                except Exception as e:
                    print(f"错误: {str(e)}")
                    print()
            
            else:
                print("未知命令,使用 'help' 查看帮助")
                print()
    
    finally:
        # 关闭连接和进程
        await client.close()
        if server_process.returncode isNone:
            server_process.terminate()
            try:
                await asyncio.wait_for(server_process.wait(), timeout=5.0)
            except asyncio.TimeoutError:
                server_process.kill()

if __name__ == "__main__":
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        print("\n已退出")

第三步:使用MCP Inspector调试

首先在.env文件配置QWEATHER_API_KEY和 QWEATHER_API_KEY

MCP Inspector是调试MCP服务器的利器,提供可视化界面:

  1. 1. 安装Inspector:

npm install -g @modelcontextprotocol/inspector
  1. 2. 启动Inspector:

mcp dev server/weather_server.py
  1. 3. 在浏览器访问 http://localhost:6274

Inspector界面可以让您直观地查看工具定义、测试调用并查看结果。

  • • 查看可用工具及其描述

    查看可用工具及其描述

  • • 查询北京未来3天天气

    查询北京未来3天天气

  • • 查询北京灾害预警

    查询北京灾害预警

提示:MCP Inspector 提供了更直观的界面来测试和调试 MCP 服务器,特别适合开发和调试复杂工具。

实际效果展示

首先在.env文件配置QWEATHER_API_KEY和 QWEATHER_API_KEY

启动程序python client/mcp_client.py

天气预警查询

> call get_weather_warning {"location": "101010100"}
正在调用工具...

结果:
预警ID: 10123020120230713145500551323468
标题: 杭州市气象台发布高温黄色预警[III级/较重]
发布时间: 2023-07-13T14:55+08:00
开始时间: 2023-07-13T14:55+08:00
结束时间: 2023-07-14T14:55+08:00
预警类型: 高温
预警等级: Moderate (Yellow)
发布单位: 杭州市气象台
状态: active
详细信息: 杭州市气象台2023年07月13日14时55分发布高温黄色预警信号:预计未来24小时内最高气温将达到37℃以上,请注意防暑降温。

天气预报查询

> call get_daily_forecast 101010100 3
正在调用工具...

结果:
日期: 2023-07-13
日出: 04:54  日落: 19:44
最高温度: 32°C  最低温度: 22°C
白天天气: 多云  夜间天气: 阴
白天风向: 东南风 3级 (19km/h)
夜间风向: 东南风 3级 (16km/h)
相对湿度: 75%
降水量: 0mm
紫外线指数: 7
能见度: 25km

---

日期: 2023-07-14
日出: 04:55  日落: 19:43
最高温度: 33°C  最低温度: 23°C
白天天气: 多云  夜间天气: 阴
白天风向: 东南风 3级 (21km/h)
夜间风向: 东风 3级 (15km/h)
相对湿度: 72%
降水量: 0mm
紫外线指数: 8
能见度: 25km

---

日期: 2023-07-15
日出: 04:56  日落: 19:43
最高温度: 34°C  最低温度: 23°C
白天天气: 多云  夜间天气: 多云
白天风向: 东南风 3级 (18km/h)
夜间风向: 东风 3级 (14km/h)
相对湿度: 70%
降水量: 0mm
紫外线指数: 9
能见度: 25km

MCP的进阶应用

MCP不仅限于天气查询,还可以实现:

  1. 1. 文件操作:读写文件、处理上传文件

  2. 2. 数据库交互:查询和修改数据库

  3. 3. 多媒体处理:处理图像、音频、视频

  4. 4. 复杂工作流:多工具链式调用

MCP开发最佳实践

  1. 1. 工具设计

    • • 单一职责:每个工具只做一件事

    • • 明确参数:详细描述每个参数的用途

    • • 健壮错误处理:优雅处理各类异常情况

  2. 2. 安全考虑

    • • 输入验证:使用JSON Schema验证输入

    • • 权限控制:限制工具访问范围

    • • 资源限制:防止资源滥用

  3. 3. 调试技巧

    • • 使用MCP Inspector可视化调试

    • • 日志记录:添加详细日志

    • • 参数测试:测试边界条件和异常输入

结语

MCP为AI模型与外部系统的交互提供了标准化、安全、高效的解决方案。通过本文的天气工具实战项目,您已经掌握了MCP的基本应用。随着大模型应用的普及,MCP将在AI工具链开发中扮演越来越重要的角色。

希望这篇入门指南能帮助您开始MCP之旅,构建更强大、更安全的AI应用。欢迎在评论区分享您的想法和实践经验!

附录

和风天气 API 注册与使用

要使用本项目,需要先注册和风天气开发者账号并获取 API Key:

  1. 1. 注册和风天气开发者账号

    • • 访问 和风天气开发服务[2]

    • • 点击"注册",按照提示完成账号注册

  2. 2. 创建项目并获取 API Key

    • • 登录开发者控制台

    • • 点击"项目管理" -> "创建项目"

    • • 填写项目名称、创建凭据

    • • 创建成功后,在项目详情页可以获取 API Key

      和风天气API Key

  3. 3. 开发者的API Host

    • • 登录开发者控制台

    • • 点击"头像" -> "设置",或直接访问https://console.qweather.com/setting?lang=zh

    • • 查看API Host

      和风天气API Host

  4. 4. API 使用说明

    • • 免费版API有调用次数限制,详情请参考和风天气定价页面[3]

    • • 支持通过城市ID或经纬度坐标查询天气信息

    • • 城市ID可通过和风天气城市查询API[4]获取

参考资源

  • • MCP官方文档:https://modelcontextprotocol.io/

  • • MCP快速入门:https://modelcontextprotocol.io/quickstart/server

  • • 项目源码:https://github.com/FlyAIBox/mcp-in-action/tree/qweather_0.1/mcp_demo

  • • 和风天气API:https://dev.qweather.com/

引用链接

[1] 注册地址: https://dev.qweather.com/
[2] 和风天气开发服务: https://dev.qweather.com/
[3] 和风天气定价页面: https://dev.qweather.com/docs/pricing/
[4] 和风天气城市查询API: https://dev.qweather.com/docs/api/geoapi/

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值