
MCP(Model Context Protocol)从入门到实战:构建你的第一个 AI 工具服务器
手把手教你理解 MCP 协议的原理、架构,并用 Python 从零搭建一个支持实时数据查询的 MCP 服务器
原创。读完本文,你将理解 MCP 协议的核心概念和架构,并能用 Python 动手搭建一个可运行的 MCP 服务器,让 AI 模型通过标准协议与外部工具通信。
为什么需要 MCP?
2024 年底,Anthropic 开源了 Model Context Protocol(MCP),一个旨在统一 AI 模型与外部数据源交互方式的开放标准。在 MCP 出现之前,让 LLM 调用外部工具是一件相当繁琐的事情——每个工具都要写一套自定义的集成代码,形成一个 "N × M" 的问题:N 个模型 × M 个工具,每个组合都要单独适配。
MCP 的出现改变了这一切。你可以把它理解成 AI 世界的 USB-C 接口:只要工具支持 MCP 协议,任何兼容的 AI 应用都能直接使用它,无需反复造轮子。
—— 广告 ——
MCP 的核心架构
MCP 采用经典的 客户端-服务器 架构,包含四个关键组件:
| 组件 | 角色 |
|---|---|
| MCP Host | 承载 LLM 的 AI 应用(如 Claude Desktop、AI IDE 插件) |
| MCP Client | 位于 Host 内部,负责与 MCP Server 通信的协议客户端 |
| MCP Server | 对外提供数据或能力的服务,封装了具体的工具实现 |
| Transport Layer | 通信层,支持 stdio(本地进程)和 SSE(远程流式)两种方式 |
通信流程
当用户向 AI 应用提出一个需要访问外部数据的问题时,整个交互流程是这样的:
- 用户提问 → LLM 意识到自己需要外部数据
- 工具发现 → MCP Client 搜索已注册的 MCP Server,找到匹配的工具
- 工具调用 → Client 通过 JSON-RPC 2.0 协议向 Server 发起请求
- 外部执行 → Server 执行实际操作(查数据库、调 API、发邮件等)
- 结果返回 → Server 将结构化结果返回给 LLM
- 最终回复 → LLM 综合上下文给出人类可读的回答
一个具体的例子:用户说"帮我查一下最新的销售报表,然后邮件发给我经理"。LLM 通过 MCP 先后调用了
database_query和email_sender两个工具,整个过程对用户透明。
MCP vs RAG:不是替代,而是互补
很多人刚接触 MCP 时会混淆它和 RAG(检索增强生成)。它们的核心区别在于:
| 维度 | MCP | RAG |
|---|---|---|
| 目标 | 标准化 AI 与外部工具的 双向交互 | 从知识库 被动检索 信息来增强生成 |
| 交互方式 | 主动执行任务(写数据库、发消息) | 只读检索,不改变外部状态 |
| 输出 | 工具调用结果 + 文本 | 仅增强文本生成 |
| 标准化 | 统一协议标准 | 技术框架,无统一协议 |
| 典型场景 | AI 智能助手订票、更新 CRM、运行代码 | 客服问答、文档摘要、知识库查询 |
简单来说:RAG 让 AI 会查资料,MCP 让 AI 会干活。两者可以组合使用——RAG 提供静态知识,MCP 提供动态能力。
准备工作:搭建开发环境
开始动手之前,你需要准备以下环境:
# Python 3.10+
python3 --version
# 安装 MCP Python SDK
pip install mcp
# 如果需要调用 LLM 做测试,准备一个 API Key(OpenAI / Anthropic / 任意兼容接口)MCP 的 Python SDK 是官方维护的,提供了开箱即用的服务器和客户端脚手架。最新的稳定版本对应 MCP Specification 2025-11-25。
微软在 GitHub 上开源了 mcp-for-beginners 课程(16.7k Stars),包含 .NET、Java、TypeScript、Rust、Python 等多种语言的入门示例,是学习 MCP 的绝佳补充资源。
实战:用 Python 构建一个天气查询 MCP 服务器
理论讲完了,我们来点实在的。下面这个例子将创建一个 MCP 服务器,提供两个工具:获取指定城市的当前天气,以及获取未来三天的天气预报。
步骤 1:创建项目结构
mkdir mcp-weather-server
cd mcp-weather-server
python3 -m venv venv
source venv/bin/activate
pip install mcp httpx步骤 2:编写 MCP 服务器
创建一个 weather_server.py 文件:
from mcp.server import Server, NotificationOptions
from mcp.server.models import InitializationOptions
import mcp.server.stdio
import mcp.types as types
import httpx
import json
from typing import Any
# 创建 MCP 服务器实例
server = Server("weather-server")
# 定义一个模拟的天气数据源(实际项目中可替换为真实 API)
WEATHER_API_BASE = "https://api.open-meteo.com/v1"
@server.list_tools()
async def handle_list_tools() -> list[types.Tool]:
"""
声明服务器提供的工具列表。
MCP Client 会在连接时调用此方法进行工具发现。
"""
return [
types.Tool(
name="get_current_weather",
description="获取指定城市的实时天气信息",
inputSchema={
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,支持中文(如:北京、上海、深圳)",
},
"units": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "温度单位",
"default": "celsius",
},
},
"required": ["city"],
},
),
types.Tool(
name="get_forecast",
description="获取指定城市未来三天的天气预报",
inputSchema={
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名称"},
"days": {
"type": "integer",
"description": "预报天数(1-3)",
"minimum": 1,
"maximum": 3,
"default": 3,
},
},
"required": ["city"],
},
),
]
@server.call_tool()
async def handle_call_tool(
name: str, arguments: dict[str, Any] | None
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
"""
处理工具调用请求。当 LLM 决定使用某个工具时,
MCP Client 会调用此方法并将结果返回给 LLM。
"""
if not arguments:
raise ValueError("Missing arguments")
if name == "get_current_weather":
city = arguments["city"]
units = arguments.get("units", "celsius")
# 这里使用 Open-Meteo 免费 API(无需 API Key)
# 实际项目中可以替换为任意天气服务
async with httpx.AsyncClient() as client:
# 先用地理编码查坐标
geo_resp = await client.get(
"https://geocoding-api.open-meteo.com/v1/search",
params={"name": city, "count": 1, "language": "zh"},
)
geo_data = geo_resp.json()
if not geo_data.get("results"):
return [types.TextContent(
type="text",
text=f"未找到城市:{city}",
)]
lat = geo_data["results"][0]["latitude"]
lon = geo_data["results"][0]["longitude"]
city_name = geo_data["results"][0]["name"]
# 查询天气
temp_unit = "celsius" if units == "celsius" else "fahrenheit"
weather_resp = await client.get(
f"{WEATHER_API_BASE}/forecast",
params={
"latitude": lat,
"longitude": lon,
"current_weather": True,
"temperature_unit": temp_unit,
"timezone": "auto",
},
)
weather = weather_resp.json()["current_weather"]
result = (
f"📍 {city_name} 当前天气
"
f"🌡️ 温度:{weather['temperature']}°{'C' if units == 'celsius' else 'F'}
"
f"💨 风速:{weather['windspeed']} km/h
"
f"🧭 风向:{weather['winddirection']}°
"
f"🕐 更新时间:{weather['time']}"
)
return [types.TextContent(type="text", text=result)]
elif name == "get_forecast":
city = arguments["city"]
days = arguments.get("days", 3)
async with httpx.AsyncClient() as client:
geo_resp = await client.get(
"https://geocoding-api.open-meteo.com/v1/search",
params={"name": city, "count": 1, "language": "zh"},
)
geo_data = geo_resp.json()
if not geo_data.get("results"):
return [types.TextContent(
type="text",
text=f"未找到城市:{city}",
)]
lat = geo_data["results"][0]["latitude"]
lon = geo_data["results"][0]["longitude"]
city_name = geo_data["results"][0]["name"]
forecast_resp = await client.get(
f"{WEATHER_API_BASE}/forecast",
params={
"latitude": lat,
"longitude": lon,
"daily": ["temperature_2m_max", "temperature_2m_min", "weathercode"],
"forecast_days": days,
"timezone": "auto",
},
)
daily = forecast_resp.json()["daily"]
lines = [f"📍 {city_name} 未来 {days} 天预报"]
for i in range(len(daily["time"])):
lines.append(
f"
📅 {daily['time'][i]}: "
f"最高 {daily['temperature_2m_max'][i]}°C / "
f"最低 {daily['temperature_2m_min'][i]}°C"
)
return [types.TextContent(type="text", text="\n".join(lines))]
else:
raise ValueError(f"未知工具:{name}")
async def main():
"""启动 MCP 服务器"""
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
await server.run(
read_stream,
write_stream,
InitializationOptions(
server_name="weather-server",
server_version="1.0.0",
capabilities=server.get_capabilities(
notification_options=NotificationOptions(),
experimental_capabilities={},
),
),
)
if __name__ == "__main__":
import asyncio
asyncio.run(main())步骤 3:运行服务器
MCP 服务器可以通过 stdio 模式运行,这意味着它通过标准输入输出来通信:
python3 weather_server.py此时服务器会等待来自 MCP Client 的 JSON-RPC 消息。你可以在 VS Code 的 MCP 配置中指向这个脚本,也可以在 Claude Desktop 的 mcp_servers.json 中注册:
{
"mcpServers": {
"weather-server": {
"command": "python3",
"args": ["/path/to/weather_server.py"]
}
}
}步骤 4:编写测试客户端(可选)
如果你想在不依赖 LLM 的情况下测试服务器,可以写一个简单的 MCP Client:
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def test():
server_params = StdioServerParameters(
command="python3",
args=["weather_server.py"],
)
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
# 列出可用工具
tools = await session.list_tools()
print("可用工具:")
for tool in tools.tools:
print(f" - {tool.name}: {tool.description}")
# 调用天气查询
result = await session.call_tool(
"get_current_weather",
{"city": "北京"}
)
print("\n查询结果:")
print(result.content[0].text)
asyncio.run(test())MCP 的安全最佳实践
在用 MCP 连接外部工具时,安全是不可忽视的。以下是几条核心原则:
- 用户知情与授权 — 任何对外部系统的写操作(发邮件、改数据)都必须先获得用户明确同意
- 数据隐私 — 在暴露数据给 MCP Server 之前,确保用户理解哪些数据将被访问
- 工具安全 — 不信任来路不明的 MCP Server 描述。执行工具调用前检查权限
- 输出清洗 — LLM 的输出可能包含注入代码,务必对输出做 XSS 防护
- 监控审计 — 记录所有 MCP 交互日志,便于追溯异常行为
总结与下一步
本文从零开始介绍了 MCP 协议的核心概念、架构设计,并通过一个完整的天气查询服务器示例,演示了如何用 Python 构建 MCP Server。
MCP 正在成为 AI 工具生态的关键基础设施。无论是 Anthropic 生态内的 Claude,还是通过开源 SDK 构建的各类 Agent 框架,都在积极支持这一标准。如果你正在开发 AI 应用,现在就是学习 MCP 最好的时机。
延伸资源:
© 2026 四月
原文链接:https://www.aprilzz.com/tutorials/mcp-beginners-guide
相关文章
React 太重了?用 Python + HTMX 快速构建内部工具的实战教程
本文通过一个完整的实战项目,带你从零开始用 FastAPI + Jinja2 + HTMX 构建一个内部管理面板,无需构建工具、无需 API 层、无需前端状态管理
Bash4LLM⁺ 使用教程:用纯 Bash 脚本优雅调用 LLM API
Bash4LLM⁺ 是一个纯 Bash 编写的 LLM API 包装器,无需 Python/Node.js,单脚本即可调用 Groq 等提供商的 Chat Completions API
MCP 协议入门实战:为 AI 助手搭建自定义工具扩展
模型上下文协议(MCP)正在成为 AI 助手工具扩展的标准接口,从零配置一个文件系统服务器到 GitHub 集成,十分钟上手