基于 MCP 协议构建可插拔的 AI 工具调用系统:以天气查询为例

本文将带你从零实现一个基于 Model Context Protocol (MCP) 的轻量级 AI 工具调用系统,展示如何让大模型安全、标准化地访问本地或远程数据源,并通过百炼(DashScope)的 Qwen 大模型完成自然语言交互。

什么是 MCP?

MCP(Model Context Protocol)是一种新兴的标准化协议,用于连接大语言模型(LLM)与外部工具服务。其核心思想是:

  • 将真实世界的功能(如查询天气、读取数据库)封装为“工具”(Tool)
  • 通过统一协议(如 stdio)暴露给 LLM 客户端
  • LLM 仅需理解工具的元信息(名称、参数、描述),无需关心底层实现

MCP 架构包含三个关键角色:

角色 说明
MCP 主机(Host) 如 Claude Desktop、IDE 插件等,发起对工具的请求
MCP 客户端(Client) 负责与 LLM 和 MCP 服务器通信,协调工具调用流程
MCP 服务器(Server) 轻量级程序,封装具体业务逻辑(如调用 API),通过 stdio 与客户端交互

优势:工具服务与大模型解耦,更换模型(Claude → Qwen)或工具(天气 → 股票)无需修改对方代码。

实战:构建一个天气查询工具

我们将实现一个简单的天气查询系统,用户输入“旧金山明天会下雨吗?”,系统自动调用天气 API 并返回自然语言回答。

(服务器和客户端均在本地实现)

第一步:开发 MCP 服务器(weather.py

环境准备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 为我们的项目创建一个新目录
uv init weather
cd weather

# 创建虚拟环境并激活它
uv venv

#由于windows的安全机制,阻止未经签名的脚本运行
#可以先临时绕过策略
# Windows 用户需先执行:
#Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
#再

.venv\Scripts\activate

# 安装依赖
uv add "mcp[cli]" httpx

MCP 服务器的核心是使用 FastMCP 框架,将函数注册为工具:

代码看:p111223/my-mcp-example-resource: 基于MCP协议的AI工具调用系统 - 天气查询示例中的weather.py

该服务器通过 标准输入输出(stdio) 与客户端通信,启动后等待 MCP 协议消息。

💡 重点:weather.py 完全不依赖任何大模型,它只是一个“工具提供者”。

注意:由于调用的是美国国家气象局官方 API(api.weather.gov),该服务主要提供美国本土、领地和近海的天气预报数据。

第二步:开发 MCP 客户端(client.py

客户端负责:

  1. 启动 MCP 服务器子进程(如 python weather.py
  2. 获取可用工具列表
  3. 将工具元信息传给大模型
  4. 执行模型指示的工具调用
  5. 将结果反馈给模型生成最终回答

我们使用 阿里云百炼(DashScope)的 Qwen 模型,因其兼容 OpenAI API,便于集成。

环境准备

1
2
3
4
5
6
7
uv init mcp-client
cd mcp-client
uv venv
# Windows 用户需先执行:
# Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
.venv\Scripts\activate
uv add mcp openai python-dotenv

创建 .env 文件:

1
2
3
4
DASHSCOPE_API_KEY=your_api_key_here

# 把.env添加到.gitignore中(powershell可以执行)
echo ".env" >> .gitignore

客户端代码

代码看p111223/my-mcp-example-resource: 基于MCP协议的AI工具调用系统 - 天气查询示例client.py

🔍 关键细节:MCP 返回的 result 可能是复杂对象,需安全提取文本内容(见完整代码中的 _extract_content 逻辑)。

第三步:运行系统

1
2
# 启动客户端并连接 weather.py
uv run python client.py [.../weather.py(服务器路径)]

结果示例:

核心机制解析

整个系统的工作流如下:

  1. 用户输入自然语言 → 客户端接收
  2. 客户端向 MCP 服务器请求工具列表
  3. 将工具元信息(JSON Schema)传给大模型
  4. 大模型决定是否调用工具,并生成参数
  5. 客户端通过 MCP 协议调用服务器
  6. 服务器执行真实逻辑(如 HTTP 请求)并返回结果
  7. 客户端将结果喂回大模型,生成最终自然语言回答

之前在HK2025那里碰到mcp调用的题,这里测一些

发现,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
{  "jsonrpc": "2.0",  "id": 2,  "method": "tools/list",  "params": {}}
——用之前的payload直接传


返回
{
"jsonrpc": "2.0",
"id": 2,
"result": [
{
"name": "get_alerts",
"description": "Get weather alerts for a US state.",
"parameters": {
"type": "object",
"properties": {
"state": {
"title": "State",
"type": "string"
}
},
"required": ["state"]
}
},
{
"name": "get_forecast",
"description": "Get weather forecast for a location.",
"parameters": {
"type": "object",
"properties": {
"latitude": {
"title": "Latitude",
"type": "number"
},
"longitude": {
"title": "Longitude",
"type": "number"
}
},
"required": ["latitude", "longitude"]
}
}
]
}

image-20260122170529128

——直接返回了我代码中的参数信息了。

1
{  "jsonrpc": "2.0",  "id": 5,  "method": "prompts/list",  "params": {}}

image-20260122170732376

由于我没有prompts/list的方法,所以,就没有返回内容了

1
2
{  "jsonrpc": "2.0",  "id": 10,  "method": "resources/templates/list",  "params": {}}
——也不行
1
2
{  "jsonrpc": "2.0",  "id": 10,  "method": "tools/call",  "params": { "name": "get_forecast", "arguments": { "latitude": 37.7, "longitude": -122.4 } }
——可以,call调用

——这里是利用了mcp协议:

这些是MCP 协议中标准的“列出所有可用工具”请求
任何符合 MCP 规范的服务端在收到这个请求后,必须返回它所支持的所有工具的元数据(metadata)

MCP 使用 JSON-RPC 2.0 作为底层通信协议

格式:

1
2
3
4
5
6
{
"jsonrpc": "2.0", // 必须是 "2.0"
"id": <number|string>,// 唯一标识符,用于匹配请求与响应(不能为 null)
"method": "<string>", // 要调用的方法名(如 "tools/list"、"tools/call")
"params": { ... } // 参数对象(可以是 {}、数组或对象,依方法而定)
}
方法名 用途 params 示例
initialize 初始化连接 { "clientCapabilities": {} }
tools/list 获取可用工具列表 {}
tools/call 调用某个工具 { "name": "get_forecast", "arguments": { "latitude": 37.7, "longitude": -122.4 } }

在写复杂工具时,不要暴露太多信息,且不要暴露到公网上

文章参考客户端开发 – MCP 中文站(Model Context Protocol 中文)