🎯 目标读者 & 阅读收益
本文面向有 Python 基础、接触过 LangChain 工具调用、但从未了解过 MCP 的开发者。你不需要懂网络协议,也不需要深入理解 JSON-RPC,只需要知道"函数可以被调用"就够了。
读完本文,你将能够:清楚解释 MCP 是什么、它解决了什么问题;看懂并运行配套的01_mcp_concepts.py;理解工具(Tools)、资源(Resources)、提示(Prompts)三大组件的区别;判断自己的项目场景是否适合引入 MCP。
前期回顾
- 第01章:从零开始调用 LLM-入门 Qwen 大模型 API
- 第02章:Prompt 工程 ------ 用语言精准指挥 AI
- 第03章:LCEL 链式调用------让 AI 任务像流水线一样运转
- 第04章:AI Tools 工具调用 ------ 让 AI 伸出"手"与世界交互
- 第05章:AI Agent 智能体 ------ 让 AI 自主决策,循环解决问题
- 第06章(上):AI RAG 检索增强生成 --- 从零到生产
- 第06章(下):AI RAG 向量库选型与生产实践
- 第07章(上):LangGraph 工作流 ------ 为什么需要它,以及如何入门
- 第07章(下):LangGraph 工作流进阶 ------ 检查点、人工介入与多 Agent 协作
1. 🔌 什么是 MCP?(一分钟读懂)
USB 类比
你有没有买过一个新键盘,直接插上电脑就能用,不需要安装任何驱动?这是因为键盘和电脑之间遵守了同一个标准:USB 接口协议。
MCP 对 AI 世界做的,正是 USB 对外设世界做的事情------统一接口标准。
css
没有 MCP(插头形状各异,每次都要重新适配):
ChatGPT ─── 自定义格式 ──→ 天气工具
Claude ─── 另一套格式 ──→ 天气工具 ← 同一个工具,写了两套对接代码!
Cursor ─── 又一套格式 ──→ 天气工具
有了 MCP(统一 USB 接口,谁都能插):
ChatGPT ─┐
Claude ─┼─── MCP 标准 ──→ 天气工具(只需实现一次)
Cursor ─┘
一句话定义
MCP(Model Context Protocol,模型上下文协议) 是 Anthropic 于 2024 年 11 月开源的标准协议,定义了 AI 模型与外部工具、数据源之间的通信接口。
- 发布时间:2024 年 11 月
- 开源方:Anthropic(Claude 的开发公司)
- 核心价值:标准化 AI 与工具/数据的交互接口
2. 🤔 为什么需要 MCP?(解决什么问题)
2.1 没有 MCP 之前的痛点
设想你有 N 个 AI 应用 (Claude、GPT、自研 Agent......)和 M 个工具(天气查询、数据库、文件读写......)。
要让每个 AI 应用都能调用每个工具,你需要写 N × M 个适配器:
AI 应用数量(N) × 工具数量(M) = 适配工作量
例如:
3 个 AI 平台 × 5 个工具 = 15 个适配器
5 个 AI 平台 × 8 个工具 = 40 个适配器 ← 随着规模扩大,指数级增长
传统方式集成一个工具长这样(每个 AI 框架都不一样):
python
# 为 OpenAI 写的工具适配器
def openai_get_weather(location: str):
return {
"role": "tool",
"content": fetch_weather(location),
"tool_call_id": "..." # OpenAI 特有字段
}
# 为 Claude 写的工具适配器(格式完全不同)
def claude_get_weather(location: str):
return {
"type": "tool_result",
"tool_use_id": "...", # Claude 特有字段
"content": fetch_weather(location)
}
# 为 LangChain 写的工具适配器(又是另一套)
@tool
def langchain_get_weather(location: str) -> str:
"""获取天气信息"""
return fetch_weather(location)
同一个 fetch_weather 函数,为三个框架写了三套包装代码。更痛苦的是,每次框架升级,三套都要改。
2.2 有了 MCP 之后
MCP 将复杂度从 N × M 降低为 N + M:
工具开发者:只需实现一次 MCP 服务器(成本:M 次)
AI 应用:只需支持 MCP 客户端(成本:N 次)
总成本:N + M,而不是 N × M
一次实现,到处复用:
python
# 实现一次 MCP 工具服务器
class WeatherMCPServer:
def get_weather(self, location: str) -> str:
return fetch_weather(location)
# Claude Desktop、Cursor、你自己的 Agent------都能直接使用这个服务器
# 不需要修改任何工具代码
2.3 如果你不用 MCP 会怎样?
| 场景 | 不用 MCP 的后果 |
|---|---|
| 新增一个 AI 平台 | 所有工具都要重写适配代码 |
| 升级工具接口 | 每个 AI 平台的适配代码都要同步修改 |
| 跨团队共享工具 | 接收方需要学习你的自定义格式 |
| 开源你的工具 | 用户只能在你支持的框架里使用 |
3. 🏗️ MCP 的核心架构
3.1 三角关系:主机 / 客户端 / 服务器
MCP 生态由三个角色组成:
arduino
┌─────────────────────────────────────────────────┐
│ 主机(Host) │
│ 例如:Claude Desktop、Cursor、 │
│ 你自己写的 AI 应用 │
│ │
│ ┌─────────────────┐ │
│ │ 客户端(Client)│ ←── 内嵌在主机程序里 │
│ │ 负责与服务器 │ │
│ │ 建立连接和通信 │ │
│ └────────┬────────┘ │
└────────────┼────────────────────────────────────┘
│ JSON-RPC 2.0 协议
│(通过 stdio 或 HTTP)
▼
┌─────────────────────────────────────────────────┐
│ 服务器(Server) │
│ 例如:天气工具服务器、 │
│ 数据库查询服务器、 │
│ 文件系统服务器 │
└─────────────────────────────────────────────────┘
三个角色的职责:
| 角色 | 是谁 | 干什么 |
|---|---|---|
| 主机(Host) | AI 应用程序(Claude Desktop、你的 Agent) | 管理整个会话,决定何时调用工具 |
| 客户端(Client) | 内嵌在主机里的 MCP 通信模块 | 与服务器建立连接、发送请求、接收结果 |
| 服务器(Server) | 独立运行的工具/数据提供程序 | 暴露工具、资源、提示,执行具体操作 |
💡 记忆方法:主机是"老板",客户端是"秘书",服务器是"外包团队"。老板通过秘书给外包团队下单,外包团队执行后把结果交回来。
3.2 通信机制:JSON-RPC 2.0
MCP 使用 JSON-RPC 2.0 作为通信协议。你不需要深入了解协议细节,只需认识三种核心消息:
① 初始化握手(initialize)
json
{
"jsonrpc": "2.0",
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"clientInfo": { "name": "claude-desktop", "version": "1.0" }
},
"id": 1
}
② 列出可用工具(tools/list)
json
{
"jsonrpc": "2.0",
"method": "tools/list",
"id": 2
}
③ 调用工具(tools/call)
json
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "get_current_time",
"arguments": { "format": "full" }
},
"id": 3
}
规律很简单 :每条消息都有 jsonrpc(固定值 "2.0")、method(操作名称)、id(请求编号,用于匹配响应)。工具调用时额外带 params 指定工具名和参数。
4. 🧩 MCP 的三大组件
4.1 工具(Tools)------ 让 AI 执行操作
定义:工具是 AI 可以调用的函数。每个工具有:
name:唯一名称(AI 通过名称调用)description:文字描述(LLM 靠这个理解工具用途)inputSchema:参数的 JSON Schema(规定参数类型和格式)
来看配套代码里的两个工具示例:
python
# 工具1:获取当前时间(无必填参数)
MCPToolSchema(
name="get_current_time",
description="获取当前日期和时间",
input_schema={
"type": "object",
"properties": {
"format": {
"type": "string",
"description": "时间格式:'full'(完整)或 'date'(仅日期)或 'time'(仅时间)",
"default": "full",
}
},
# 注意:没有 "required" 字段,意味着 format 是可选参数
},
)
# 工具2:计算天数差(有必填参数)
MCPToolSchema(
name="calculate_days",
description="计算从指定日期到今天的天数",
input_schema={
"type": "object",
"properties": {
"date": {
"type": "string",
"description": "起始日期,格式 YYYY-MM-DD",
}
},
"required": ["date"], # date 是必填参数
},
)
工具适合做:查询天气、操作数据库、调用 API、执行代码、发送邮件------一切有副作用或需要实时数据的操作。
4.2 资源(Resources)------ 让 AI 读取数据
定义 :资源是通过 URI 标识的只读数据。适合暴露文档、配置文件、数据库记录等静态或半静态内容。
python
# 资源示例:服务器状态信息
MCPResourceSchema(
uri="server://status", # URI 格式,唯一标识这份数据
name="服务器状态",
description="MCP 服务器的当前状态信息",
mime_type="text/plain", # 内容类型
)
资源 vs 工具的区别:
- 资源:读取数据,没有副作用(类似 GET 请求)
- 工具:执行操作,可能改变状态(类似 POST/PUT 请求)
4.3 提示(Prompts)------ 预定义对话模板
定义:提示是可重用的提示词模板,带有可填充的参数槽位。
python
# 提示示例:代码审查模板
MCPPromptSchema(
name="code_review",
description="对代码进行安全和质量审查",
arguments=[
{"name": "language", "description": "编程语言", "required": True},
{"name": "code", "description": "要审查的代码", "required": True},
]
)
提示适合做:封装复杂的系统提示、标准化团队 Prompt 规范、让非技术人员也能使用高质量 Prompt。
5. 🛠️ 动手实战:读懂第一个 MCP 服务器
让我们逐步拆解 01_mcp_concepts.py,理解一个完整的 MCP 服务器是怎么搭起来的。
5.1 数据结构定义
python
from dataclasses import dataclass, field
from datetime import datetime
from typing import Any, Optional
@dataclass
class MCPToolSchema:
"""MCP 工具的 Schema 定义"""
name: str # 工具唯一名称
description: str # 工具描述(LLM 用来理解工具用途)
input_schema: dict # JSON Schema 定义输入参数
def to_dict(self) -> dict:
"""序列化为字典(用于 JSON-RPC 传输)"""
return {
"name": self.name,
"description": self.description,
"inputSchema": self.input_schema,
}
@dataclass
class MCPToolResult:
"""MCP 工具调用的返回结果"""
content: list[dict] # 内容列表(支持文本、图片等)
is_error: bool = False # 是否发生错误
@classmethod
def text(cls, text: str) -> "MCPToolResult":
"""创建文本类型的结果"""
return cls(content=[{"type": "text", "text": text}])
@classmethod
def error(cls, message: str) -> "MCPToolResult":
"""创建错误类型的结果"""
return cls(content=[{"type": "text", "text": message}], is_error=True)
📌 注意 :
MCPToolResult用content列表而不是单一字符串,是因为 MCP 支持返回多种内容类型(文本、图片、文件等)。这是协议的扩展性设计。
5.2 创建服务器并注册工具
python
class SimpleMCPServer:
"""模拟 MCP 服务器"""
def __init__(self, name: str, version: str = "1.0.0"):
self.name = name
self.version = version
self._tools: dict[str, tuple[MCPToolSchema, callable]] = {}
self._resources: dict[str, tuple[MCPResourceSchema, callable]] = {}
def register_tool(self, schema: MCPToolSchema, handler: callable):
"""注册工具:Schema 描述工具,handler 执行具体逻辑"""
self._tools[schema.name] = (schema, handler)
def handle_call_tool(self, tool_name: str, arguments: dict) -> MCPToolResult:
"""处理工具调用:查找工具 → 执行 handler → 返回结果"""
if tool_name not in self._tools:
return MCPToolResult.error(f"工具不存在:{tool_name}")
schema, handler = self._tools[tool_name]
try:
result = handler(**arguments)
return MCPToolResult.text(str(result))
except Exception as e:
return MCPToolResult.error(f"工具执行错误:{str(e)}")
关键设计 :register_tool 将 Schema(描述)和 handler(实现)绑定在一起。Schema 告诉客户端"这个工具能做什么、需要什么参数",handler 实际执行操作。
5.3 注册具体工具
python
def create_time_tools_server() -> SimpleMCPServer:
server = SimpleMCPServer("time-tools-server", "1.0.0")
# 工具1:获取当前时间
server.register_tool(
MCPToolSchema(
name="get_current_time",
description="获取当前日期和时间",
input_schema={
"type": "object",
"properties": {
"format": {
"type": "string",
"description": "时间格式:'full'(完整)或 'date'(仅日期)或 'time'(仅时间)",
"default": "full",
}
},
},
),
handler=lambda format="full": (
datetime.now().strftime("%Y-%m-%d %H:%M:%S") if format == "full"
else datetime.now().strftime("%Y-%m-%d") if format == "date"
else datetime.now().strftime("%H:%M:%S")
),
)
# 工具2:计算天数差
server.register_tool(
MCPToolSchema(
name="calculate_days",
description="计算从指定日期到今天的天数",
input_schema={
"type": "object",
"properties": {
"date": {
"type": "string",
"description": "起始日期,格式 YYYY-MM-DD",
}
},
"required": ["date"],
},
),
handler=lambda date: (
f"从 {date} 到今天共 "
f"{(datetime.now() - datetime.strptime(date, '%Y-%m-%d')).days} 天"
),
)
# 资源:服务器状态
server.register_resource(
MCPResourceSchema(
uri="server://status",
name="服务器状态",
description="MCP 服务器的当前状态信息",
mime_type="text/plain",
),
handler=lambda: f"服务器:{server.name} v{server.version},运行中,时间:{datetime.now()}",
)
return server
5.4 客户端连接并调用
python
class SimpleMCPClient:
"""模拟 MCP 客户端"""
def __init__(self, server: SimpleMCPServer):
self.server = server
def initialize(self):
"""初始化连接,获取服务器能力"""
response = self.server.handle_initialize()
print(f"协议版本:{response['protocolVersion']}")
return response
def list_tools(self) -> list:
"""列出所有可用工具"""
response = self.server.handle_list_tools()
return response["tools"]
def call_tool(self, tool_name: str, arguments: dict = None) -> str:
"""调用指定工具"""
result = self.server.handle_call_tool(tool_name, arguments or {})
if result.is_error:
return None
return result.content[0]["text"]
def read_resource(self, uri: str) -> str:
"""读取指定资源"""
response = self.server.handle_read_resource(uri)
return response["contents"][0]["text"]
5.5 运行方式和预期输出
bash
# 在项目根目录运行
python lessons/08_mcp/01_mcp_concepts.py
预期输出:
css
============================================================
MCP(模型上下文协议)概念演示
============================================================
=== MCP 协议消息格式(JSON-RPC 2.0)===
请求 [initialize]:
{
"jsonrpc": "2.0",
"method": "initialize",
...
}
=== MCP 服务器-客户端交互演示 ===
[服务端] 启动 MCP 服务器...
[服务器] 注册工具:get_current_time
[服务器] 注册工具:calculate_days
[客户端] 连接到 MCP 服务器:time-tools-server
[客户端] 初始化成功
协议版本:2024-11-05
服务器:time-tools-server v1.0.0
[客户端] 发现 2 个工具:
- get_current_time: 获取当前日期和时间
- calculate_days: 计算从指定日期到今天的天数
[客户端] 调用工具:get_current_time({'format': 'full'})
✅ 结果:2024-11-15 14:30:22
[客户端] 调用工具:calculate_days({'date': '2024-01-01'})
✅ 结果:从 2024-01-01 到今天共 319 天
6. 📐 工具 Schema 的核心知识
工具 Schema 是 MCP 的"说明书"------LLM 完全依靠它来理解工具、决定何时调用、传什么参数。写好 Schema 至关重要。
6.1 必填 vs 可选参数
python
input_schema={
"type": "object",
"properties": {
"date": {
"type": "string",
"description": "日期,格式 YYYY-MM-DD",
},
"format": {
"type": "string",
"description": "输出格式",
"default": "full", # 有默认值
}
},
"required": ["date"], # 只有 date 是必填,format 是可选
}
规则 :required 列表里的参数必须提供,不在列表里的参数可选(建议设置 default)。
6.2 参数类型对照表
| JSON Schema 类型 | Python 对应 | 示例值 | 适用场景 |
|---|---|---|---|
string |
str |
"hello" |
文本、日期字符串、枚举值 |
integer |
int |
42 |
整数、页码、数量 |
number |
float |
3.14 |
浮点数、价格、坐标 |
boolean |
bool |
true |
开关、是否选项 |
array |
list |
["a", "b"] |
批量处理、标签列表 |
object |
dict |
{"key": "val"} |
复合参数、嵌套结构 |
6.3 枚举类型(限定取值范围)
当参数只能取特定值时,用 enum 明确告诉 LLM:
python
"format": {
"type": "string",
"description": "时间格式",
"enum": ["full", "date", "time"], # 只允许这三个值
"default": "full"
}
LLM 看到 enum 后,会严格从枚举值里选择,不会乱填参数。
6.4 描述文本为什么至关重要
LLM 不会读代码,只会读描述 。工具是否被正确调用,80% 取决于 description 写得好不好。
python
# ❌ 差的描述(LLM 看不懂该传什么)
"date": {
"type": "string",
"description": "日期"
}
# ✅ 好的描述(LLM 一目了然)
"date": {
"type": "string",
"description": "起始日期,格式 YYYY-MM-DD,例如 2024-01-01"
}
写好描述的三个原则:
- 说清格式 :字符串参数一定要说明格式(
YYYY-MM-DD、HH:MM:SS等) - 给出示例 :
例如 2024-01-01比单纯描述更有效 - 说明含义 :
从该日期计算到今天比开始日期更清晰
7. 🗺️ 什么场景该用 MCP?
用决策树帮你判断:
css
你的需求是什么?
│
├── 需要 AI 调用外部工具(查天气、操作数据库、发邮件......)?
│ │
│ ├── 这个工具会被多个 AI 应用复用?
│ │ └── ✅ 用 MCP Tools(一次实现,多处使用)
│ │
│ └── 这个工具只在一个项目里用?
│ └── ⚡ 可以直接用 LangChain @tool(更简单快速)
│
├── 需要 AI 读取外部数据(文档、配置、数据库记录......)?
│ │
│ ├── 数据有明确的 URI 标识(文件路径、数据库 ID......)?
│ │ └── ✅ 用 MCP Resources(天然适合只读数据)
│ │
│ └── 数据是实时查询结果(搜索、聚合......)?
│ └── ⚡ 用工具(Tool)更合适,结果作为工具返回值
│
└── 需要标准化提示词模板?
│
├── 要跨团队/跨平台共享 Prompt?
│ └── ✅ 用 MCP Prompts
│
└── 只在自己项目里用?
└── ⚡ 直接定义字符串模板即可
典型使用场景
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 给 Claude Desktop 添加自定义工具 | MCP Server | 官方支持,直接集成 |
| 公司内部 AI 平台对接多种数据源 | MCP Server | 一次实现,多平台复用 |
| 快速给 LangChain Agent 加一个工具 | @tool 装饰器 |
更快,不需要单独部署服务 |
| 开源一个通用 AI 工具给社区使用 | MCP Server | 最大兼容性 |
| 给特定 Agent 加上下文数据 | MCP Resources | 语义清晰,符合只读语义 |
8. 📝 总结 & 下一步
本章核心要点
| 概念 | 一句话记忆 |
|---|---|
| MCP 是什么 | AI 工具的 USB 标准接口,2024 年 11 月 Anthropic 开源 |
| 解决什么问题 | N×M 适配工作 → N+M,一次实现,到处复用 |
| 三个角色 | 主机(老板)→ 客户端(秘书)→ 服务器(外包团队) |
| 通信协议 | JSON-RPC 2.0,三种核心消息:initialize / tools/list / tools/call |
| 三大组件 | Tools(执行操作)/ Resources(读取数据)/ Prompts(提示模板) |
| Schema 重点 | description 写清楚,LLM 才能正确调用工具 |
📌 下一章预告: 掌握基础概念后,下一章将进入实战:
- 进阶篇 :使用真实的
mcpSDK 搭建生产级服务器- 集成篇:将 MCP 服务器接入 LangChain Agent 和 Claude Desktop
- 部署篇:通过 HTTP 而非 stdio 暴露 MCP 服务,实现远程调用
作者:阿聪谈架构公众号:阿聪谈架构 (分享后端架构 / AI / Java 技术文章)
相关代码关注公众号:【阿聪谈架构】 回复:AI专栏代码