本文将带您了解大模型上下文协议(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. 解析模型输出
-
2. 识别API调用意图
-
3. 手动构造API请求
-
4. 将结果返回给模型
这种方式不仅繁琐,而且容易出错,特别是当需要处理多个API调用或复杂逻辑时。
Function Calling的进步与局限
Function Calling(如OpenAI的函数调用或Anthropic的Tool Use)是一种改进,模型可以直接输出结构化的JSON来表示函数调用意图。但它仍有局限:
-
1. 输出格式不稳定,需要额外验证和错误处理
-
2. 安全边界模糊,需要开发者自行实现安全措施
-
3. 缺乏标准化,不同模型实现差异大
MCP的优势
MCP通过标准化协议解决了上述问题:
-
1. 标准接口:提供统一的工具定义和调用方式
-
2. 安全隔离:工具在沙箱环境中执行,减少安全风险
-
3. 双向通信:模型和工具可以进行实时交互
-
4. 环境感知:工具可以访问完整上下文
-
5. 简化开发:开发者只需实现工具逻辑,协议处理由MCP框架管理
MCP天气工具实战项目
下面,我们将通过一个实际项目,展示如何使用MCP创建一个天气信息工具,让AI模型能够查询实时天气数据。
项目介绍
这是一个基于MCP的天气工具演示项目,通过和风天气API获取实时天气数据,提供以下功能:
-
1. 天气预警查询:获取指定城市的天气灾害预警信息
-
2. 天气预报查询:获取指定城市未来几天的天气预报
项目架构
项目分为三个主要部分:
┌─────────────┐ stdio ┌──────────────┐
│ │◄────────────►│ │
│ MCP 客户端 │ │ MCP 服务器 │
│ │ │ │
└─────────────┘ └──────────────┘
▲
│
调试 │
│
▼
┌─────────────┐
│ │
│MCP Inspector│
│ │
└─────────────┘
-
1. MCP服务器:提供天气工具的核心实现
-
2. MCP客户端:连接服务器,发送工具调用请求
-
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. 安装Inspector:
npm install -g @modelcontextprotocol/inspector
-
2. 启动Inspector:
mcp dev server/weather_server.py
-
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. 文件操作:读写文件、处理上传文件
-
2. 数据库交互:查询和修改数据库
-
3. 多媒体处理:处理图像、音频、视频
-
4. 复杂工作流:多工具链式调用
MCP开发最佳实践
-
1. 工具设计:
-
• 单一职责:每个工具只做一件事
-
• 明确参数:详细描述每个参数的用途
-
• 健壮错误处理:优雅处理各类异常情况
-
-
2. 安全考虑:
-
• 输入验证:使用JSON Schema验证输入
-
• 权限控制:限制工具访问范围
-
• 资源限制:防止资源滥用
-
-
3. 调试技巧:
-
• 使用MCP Inspector可视化调试
-
• 日志记录:添加详细日志
-
• 参数测试:测试边界条件和异常输入
-
结语
MCP为AI模型与外部系统的交互提供了标准化、安全、高效的解决方案。通过本文的天气工具实战项目,您已经掌握了MCP的基本应用。随着大模型应用的普及,MCP将在AI工具链开发中扮演越来越重要的角色。
希望这篇入门指南能帮助您开始MCP之旅,构建更强大、更安全的AI应用。欢迎在评论区分享您的想法和实践经验!
附录
和风天气 API 注册与使用
要使用本项目,需要先注册和风天气开发者账号并获取 API Key:
-
1. 注册和风天气开发者账号:
-
• 访问 和风天气开发服务[2]
-
• 点击"注册",按照提示完成账号注册
-
-
2. 创建项目并获取 API Key:
-
• 登录开发者控制台
-
• 点击"项目管理" -> "创建项目"
-
• 填写项目名称、创建凭据
- • 创建成功后,在项目详情页可以获取 API Key
和风天气API Key
-
-
3. 开发者的API Host:
-
• 登录开发者控制台
-
• 点击"头像" -> "设置",或直接访问https://console.qweather.com/setting?lang=zh
- • 查看API Host
和风天气API Host
-
-
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/