MCP 协议 - AI 工具的"USB 接口" / Model Context Protocol for AI Tool Integration
📅 创建时间:2026-07-28 🏷️ 标签:#MCP #ModelContextProtocol #ToolIntegration #Agent 📚 前置知识:[[01-function-calling]] [[00-agent-overview]]
📋 本章目标
- 理解 MCP 要解决的核心问题:N×M 集成困境
- 掌握 MCP 的数据流:Client/Server 架构、Transport 层、JSON-RPC 消息
- 理解 MCP 的四大核心概念:Resources、Tools、Prompts、Sampling
- 能够从零构建一个简单的 MCP Server
- 理解 MCP 与传统 Function Calling 的本质区别
- 了解 MCP 的生态现状:哪些应用已经支持 MCP
第0部分:MCP 解决什么问题?
0.1 一个所有人都遇到过的困境
假设你正在构建一个 AI 助手应用。用户问:"帮我查一下今天 GitHub 上 Python 项目的 trending,然后总结到 Notion 里。"
你的第一反应:这不难,我有 Function Calling。
┌───────────────────────────────────────────────────────────┐
│ 实现这个需求需要什么? │
├───────────────────────────────────────────────────────────┤
│ │
│ 1. 学习 GitHub API → OAuth、REST、rate limit │
│ 2. 学习 Notion API → token、blocks 格式、分页 │
│ 3. 在代码里分别集成 → 两个完全不同的 SDK/认证方式 │
│ 4. 写工具定义给 LLM → 两个 tools 的 JSON Schema │
│ 5. 测试、调试、维护 → 每个 API 版本更新都得改 │
│ │
│ 这就两个工具。如果还要加: │
│ Slack、Gmail、数据库、文件系统、Jira、Google Drive... │
│ │
└───────────────────────────────────────────────────────────┘问题暴露出来了:每接入一个新工具,你就要写一套全新的集成代码。 你的 AI 应用和 N 个外部工具之间是 M 个 AI 应用 × N 个工具的排列组合。
┌───────────────────────────────────────────────────────────┐
│ 现状:N×M 困境 │
├───────────────────────────────────────────────────────────┤
│ │
│ AI App 1 ──→ GitHub API (集成代码 A1) │
│ ──→ Notion API (集成代码 A2) │
│ ──→ Slack API (集成代码 A3) │
│ ──→ Gmail API (集成代码 A4) │
│ ──→ Database (集成代码 A5) │
│ │
│ AI App 2 ──→ GitHub API (集成代码 B1) │
│ ──→ Notion API (集成代码 B2) │
│ ──→ Slack API (集成代码 B3) │
│ ──→ Gmail API (集成代码 B4) │
│ ──→ Database (集成代码 B5) │
│ │
│ 每个组合 = 一次独立开发。 │
│ 5 个工具 × 5 个应用 = 25 套集成代码。 │
│ │
└───────────────────────────────────────────────────────────┘0.2 MCP 的思路:把"集成"标准化
MCP 的想法非常朴素——这个问题是不是听着很耳熟?
在 MCP 出现之前,计算机历史上有一个完全同构的问题:硬件外设的连接。在 USB 出现之前,每种外设都要单独写驱动:打印机驱动、扫描仪驱动、键盘驱动、鼠标驱动。每种设备的驱动程序都跟操作系统强绑定。USB 协议出现后,所有设备只要遵循 USB 标准,插上去就能用。
MCP 对 AI 工具做的事,和 USB 对硬件外设做的事完全一样:
┌───────────────────────────────────────────────────────────┐
│ MCP = AI 工具集成领域的 "USB 协议" │
├───────────────────────────────────────────────────────────┤
│ │
│ 没有 USB 时: │
│ 每台电脑 + 每种外设 = 独立驱动 │
│ Windows × N台打印机 × M台扫描仪 = 无数种组合 │
│ │
│ 有了 USB 后: │
│ 外设遵循 USB 标准 → 任何电脑都能识别 │
│ N 台设备 + M 台电脑 → 1 套协议就够了 │
│ │
│ ───────────────────────────────────────────────────── │
│ │
│ 没有 MCP 时: │
│ 每个 AI 应用 + 每个外部工具 = 独立集成 │
│ Claude × Slack, GPT × Slack, Gemini × Slack │
│ = 3 套不同的 Slack 集成代码 │
│ │
│ 有了 MCP 后: │
│ 写一次 MCP Server(如 Slack Server)→ 所有 AI 应用 │
│ 只要实现了 MCP Client,就能直接发现和调用 Slack │
│ │
│ N 个工具 + M 个 AI 应用 → 写 N 个 MCP Server 就够 │
│ │
└───────────────────────────────────────────────────────────┘0.3 一句话定义
MCP(Model Context Protocol)是一个开放协议,标准化了 AI 应用与外部工具/数据源之间的通信方式。它让你只需要写一次工具集成,就能被任何实现了 MCP Client 的 AI 应用使用。
它由 Anthropic 在 2024 年 11 月开源发布,现在已成为 AI 工具集成领域的事实标准。
第1部分:MCP 的数据流
1.1 整体架构
MCP 采用经典的 Client-Server 架构:
┌───────────────────────────────────────────────────────────┐
│ MCP 架构全景 │
├───────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────────┐ │
│ │ AI 应用 │ │ 外部工具/数据源 │ │
│ │ (Claude │ │ │ │
│ │ Desktop) │ │ Slack API │ │
│ │ │ │ GitHub API │ │
│ │ 充当 MCP │←───────→│ 文件系统 │ │
│ │ Client │ MCP │ 数据库 │ │
│ │ │ 协议 │ ... │ │
│ │ 发起请求 │ │ │ │
│ │ 消费结果 │ │ 充当 MCP Server │ │
│ │ │ │ 响应请求 │ │
│ └──────────────┘ └──────────────────┘ │
│ │
│ 关键:Client 和 Server 是解耦的。 │
│ 一个 Server 可以被多个不同的 Client 使用。 │
│ 一个 Client 可以连接多个不同的 Server。 │
│ │
└───────────────────────────────────────────────────────────┘1.2 Transport 层:消息怎么传输?
MCP 协议定义在应用层,传输层有两种标准方式:
┌───────────────────────────────────────────────────────────┐
│ MCP Transport 层 │
├───────────────────────────────────────────────────────────┤
│ │
│ 方式一:stdio(标准输入输出) │
│ ┌───────────────────────────────────────────────────┐ │
│ │ • Client 启动 Server 为子进程 │ │
│ │ • 通过 stdin/stdout 交换 JSON-RPC 消息 │ │
│ │ • 适用于本地工具(如文件系统、本地数据库) │ │
│ │ • 零网络开销,无需端口管理 │ │
│ │ │ │
│ │ Client ──spawn──→ Server (子进程) │ │
│ │ stdin ← JSON-RPC request → stdout │ │
│ └───────────────────────────────────────────────────┘ │
│ │
│ 方式二:HTTP + SSE(Server-Sent Events) │
│ ┌───────────────────────────────────────────────────┐ │
│ │ • Server 作为 HTTP 服务运行,监听端口 │ │
│ │ • Client 通过 HTTP POST 发送请求 │ │
│ │ • Server 通过 SSE 流式推送响应 │ │
│ │ • 适用于远程工具、多客户端共享、云端部署 │ │
│ │ │ │
│ │ Client ──HTTP POST──→ Server (远程/本地端口) │ │
│ │ Client ←──SSE──────── Server │ │
│ └───────────────────────────────────────────────────┘ │
│ │
└───────────────────────────────────────────────────────────┘两种方式的选择取决于部署场景:
| 场景 | Transport | 原因 |
|---|---|---|
| 本地文件系统工具 | stdio | 简单,无网络配置 |
| 企业内部 API 网关 | HTTP+SSE | 多客户端共享 |
| 个人开发工具 | stdio | 安全,本地即可 |
| 云端 SaaS 工具 | HTTP+SSE | 需要远程访问 |
1.3 JSON-RPC:协议消息的格式
无论用哪种 transport,MCP 的消息体都是 JSON-RPC 2.0 格式。每条消息就是一个 JSON 对象:
JSON-RPC 消息的基本结构:
请求(Client → Server):
┌─────────────────────────────────────────────────────────┐
│ { │
│ "jsonrpc": "2.0", │
│ "id": 1, ← 请求 ID,用于匹配响应 │
│ "method": "tools/list", ← 要执行的操作 │
│ "params": {} ← 操作参数(可选) │
│ } │
└─────────────────────────────────────────────────────────┘
响应(Server → Client):
┌─────────────────────────────────────────────────────────┐
│ { │
│ "jsonrpc": "2.0", │
│ "id": 1, ← 匹配请求的 ID │
│ "result": { ← 返回结果 │
│ "tools": [...] │
│ } │
│ } │
└─────────────────────────────────────────────────────────┘
通知(任意方向,不需要响应):
┌─────────────────────────────────────────────────────────┐
│ { │
│ "jsonrpc": "2.0", │
│ "method": "notifications/initialized", │
│ "params": {} │
│ // 注意:通知没有 "id" 字段,对方不回复 │
│ } │
└─────────────────────────────────────────────────────────┘JSON-RPC 非常简单:它本质上就是一种约定——"你发一个 JSON 给我,我回一个 JSON 给你"。MCP 在这个基础上定义了标准的方法名(method)集合。
1.4 一次完整的会话:从连接到调用
下面展示一个 MCP Client 与 MCP Server 之间的真实消息序列。这是整个 MCP 协议的核心——一旦理解了这段交换,MCP 的全貌就清楚了。
阶段一:初始化握手
┌───────────────────────────────────────────────────────────┐
│ 阶段一:Client 连接 Server,协商能力 │
├───────────────────────────────────────────────────────────┤
│ │
│ Step 1: Client 发送 initialize │
│ ───────────────────────────────────────────────────── │
│ Client → Server: │
│ { │
│ "jsonrpc": "2.0", │
│ "id": 1, │
│ "method": "initialize", │
│ "params": { │
│ "protocolVersion": "2024-11-05", │
│ "capabilities": { │
│ "roots": {"listChanged": true}, │
│ "sampling": {} │
│ }, │
│ "clientInfo": { │
│ "name": "my-ai-app", │
│ "version": "1.0.0" │
│ } │
│ } │
│ } │
│ │
│ Step 2: Server 回复自己的能力 │
│ ───────────────────────────────────────────────────── │
│ Server → Client: │
│ { │
│ "jsonrpc": "2.0", │
│ "id": 1, │
│ "result": { │
│ "protocolVersion": "2024-11-05", │
│ "capabilities": { │
│ "tools": {"listChanged": true}, │
│ "resources": {"subscribe": true, │
│ "listChanged": true} │
│ }, │
│ "serverInfo": { │
│ "name": "weather-server", │
│ "version": "1.0.0" │
│ } │
│ } │
│ } │
│ │
│ Step 3: Client 发送 initialized 通知 │
│ ───────────────────────────────────────────────────── │
│ Client → Server: │
│ { │
│ "jsonrpc": "2.0", │
│ "method": "notifications/initialized" │
│ } │
│ │
│ 至此握手完成,双方知道了彼此的能力。 │
│ │
└───────────────────────────────────────────────────────────┘阶段二:发现工具并调用
┌───────────────────────────────────────────────────────────┐
│ 阶段二:Client 发现工具,用户触发调用 │
├───────────────────────────────────────────────────────────┤
│ │
│ Step 4: Client 列出可用工具 │
│ ───────────────────────────────────────────────────── │
│ Client → Server: │
│ { │
│ "jsonrpc": "2.0", │
│ "id": 2, │
│ "method": "tools/list", │
│ "params": {} │
│ } │
│ │
│ Step 5: Server 返回工具列表 │
│ ───────────────────────────────────────────────────── │
│ Server → Client: │
│ { │
│ "jsonrpc": "2.0", │
│ "id": 2, │
│ "result": { │
│ "tools": [ │
│ { │
│ "name": "get_weather", │
│ "description": "获取指定城市的实时天气", │
│ "inputSchema": { │
│ "type": "object", │
│ "properties": { │
│ "city": { │
│ "type": "string", │
│ "description": "城市名称" │
│ } │
│ }, │
│ "required": ["city"] │
│ } │
│ } │
│ ] │
│ } │
│ } │
│ │
│ Step 6: Client 调用工具 │
│ ───────────────────────────────────────────────────── │
│ Client → Server: │
│ { │
│ "jsonrpc": "2.0", │
│ "id": 3, │
│ "method": "tools/call", │
│ "params": { │
│ "name": "get_weather", │
│ "arguments": {"city": "北京"} │
│ } │
│ } │
│ │
│ Step 7: Server 返回执行结果 │
│ ───────────────────────────────────────────────────── │
│ Server → Client: │
│ { │
│ "jsonrpc": "2.0", │
│ "id": 3, │
│ "result": { │
│ "content": [ │
│ { │
│ "type": "text", │
│ "text": "北京当前温度 28°C,晴,湿度 35%" │
│ } │
│ ] │
│ } │
│ } │
│ │
│ 至此,Client 获得了工具执行的结果。 │
│ Client 把这个结果交给 LLM → LLM 整合成回答 → 返回给用户。 │
│ │
└───────────────────────────────────────────────────────────┘1.5 整个数据流的"全景图"
把上面的两个阶段串起来,MCP 的完整数据流就是:
┌───────────────────────────────────────────────────────────┐
│ MCP 完整数据流(端到端) │
├───────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌──────────┐ ┌───────────┐ │
│ │ 用户 │ │AI App │ │MCP Server │ │
│ │ │ │(Client) │ │(Tool) │ │
│ └────┬────┘ └────┬─────┘ └─────┬─────┘ │
│ │ │ │ │
│ │ 1. "北京天气?" │ │ │
│ │──────────────────→│ │ │
│ │ │ │ │
│ │ │ 2. initialize │ │
│ │ │───────────────────→│ │
│ │ │ ←── capabilities │ │
│ │ │ │ │
│ │ │ 3. tools/list │ │
│ │ │───────────────────→│ │
│ │ │ ←── tool schemas │ │
│ │ │ │ │
│ │ │ 4. 把 tool schemas 转成 │
│ │ │ Function Calling 格式 │
│ │ │ 发给 LLM │
│ │ │ │ │
│ │ │ 5. LLM 返回 tool_call: │
│ │ │ get_weather("北京") │
│ │ │ │ │
│ │ │ 6. tools/call │ │
│ │ │───────────────────→│ │
│ │ │ │ │
│ │ │ 7. Server 真正 │ │
│ │ │ 调用天气 API │ │
│ │ │ ← 28°C, 晴 │ │
│ │ │ ←── result │ │
│ │ │ │ │
│ │ │ 8. 把结果返回给 LLM │
│ │ │ LLM 生成最终回答 │
│ │ │ │ │
│ │ 9. "北京今天 28°C, 晴" │ │
│ │←──────────────────│ │ │
│ │ │ │ │
└───────────────────────────────────────────────────────────┘关键观察:MCP Server 从不直接跟 LLM 通信。它只做一件事——暴露工具并执行。LLM 由 MCP Client(你的 AI 应用)自己管理。MCP Server 不知道也不关心是哪种 LLM 在调用它。
第2部分:MCP 的四大核心概念
MCP 协议定义了四种"原语"(primitives),Server 可以按需实现其中任意组合:
┌───────────────────────────────────────────────────────────┐
│ MCP 四大核心原语 │
├───────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 📁 Resources — 暴露数据/文件 │ │
│ │ "我需要读这个文件" "这个数据库里有什么?" │ │
│ │ URI 标识,支持读取、订阅变更 │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 🔧 Tools — 暴露可执行操作 │ │
│ │ "帮我查天气" "帮我发邮件" "帮我搜文档" │ │
│ │ 有输入参数(JSON Schema),有返回结果 │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 💬 Prompts — 暴露预定义提示词模板 │ │
│ │ "用这个模板帮我写代码审查" │ │
│ │ 可包含参数,Server 告诉 Client "你可以这样问我" │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 🧠 Sampling — Server 反向请求 LLM │ │
│ │ "我需要在执行过程中让 LLM 帮我做判断" │ │
│ │ 唯一让 Server 主动发起 LLM 调用 的机制 │ │
│ └─────────────────────────────────────────────────┘ │
│ │
└───────────────────────────────────────────────────────────┘2.1 Resources(资源)——"我能给你什么数据"
Resources 让 MCP Server 暴露结构化或非结构化的数据。每个 Resource 有一个唯一的 URI,Client 可以通过这个 URI 读取数据。
┌───────────────────────────────────────────────────────────┐
│ Resources 概念示意 │
├───────────────────────────────────────────────────────────┤
│ │
│ 类比:Resources 相当于 REST API 的 GET 端点。 │
│ 但它是结构化和协商过的——Server 告诉 Client │
│ "我有这些数据,格式是这样的,需要的时候来读"。 │
│ │
│ 典型资源: │
│ • file:///home/user/docs/report.md │
│ • postgres://database/users/table │
│ • slack://workspace/channels/general │
│ │
│ Client 可以: │
│ • resources/list → 列出所有可用资源 │
│ • resources/read → 读取指定资源 │
│ • resources/subscribe → 订阅资源变更通知 │
│ │
└───────────────────────────────────────────────────────────┘代码示例——Server 端注册一个资源:
# MCP Server 注册资源
@server.list_resources()
async def list_resources() -> list[Resource]:
return [
Resource(
uri="file:///home/user/docs/readme.md",
name="项目 README",
description="项目的说明文档",
mimeType="text/markdown"
),
Resource(
uri="db://users/table",
name="用户数据表",
description="所有注册用户的信息",
mimeType="application/json"
)
]
@server.read_resource()
async def read_resource(uri: str) -> str:
if uri == "file:///home/user/docs/readme.md":
with open("/home/user/docs/readme.md") as f:
return f.read()
elif uri == "db://users/table":
return json.dumps(fetch_users_from_db())2.2 Tools(工具)——"我能帮你做什么"
Tools 是 MCP 最核心、最常用的原语。它跟 Function Calling 的工具定义几乎完全对应,区别是:它运行在 MCP Server 进程里,而不是你的 AI 应用进程里。
MCP Tool 的定义结构:
┌───────────────────────────────────────────────────────────┐
│ MCP Tool 的结构 │
├───────────────────────────────────────────────────────────┤
│ │
│ { │
│ "name": "get_weather", ← 工具名称 │
│ "description": "...", ← 给 LLM 看的说明 │
│ "inputSchema": { ← 参数 JSON Schema │
│ "type": "object", │
│ "properties": { │
│ "city": {"type": "string", "description": "..."} │
│ }, │
│ "required": ["city"] │
│ } │
│ } │
│ │
│ 注意:和 Function Calling 的 tool 定义几乎一样! │
│ MCP 在协议层把这个定义标准化了。 │
│ │
└───────────────────────────────────────────────────────────┘MCP Client(你的 AI 应用)拿到 Tool 定义后,直接将其转换为 LLM 的 Function Calling 格式发给 LLM。当 LLM 返回 tool_calls 时,Client 不做任何变化,而是通过 MCP 的 tools/call 发送给 Server 执行。Client 只是"转手"——它不执行工具,它把执行委托给 Server。
2.3 Prompts(提示词模板)——"你可以这样跟我对话"
Prompts 是 MCP 的一个独特设计:Server 可以定义一组"推荐提问方式",让用户或 AI 应用选择使用。
┌───────────────────────────────────────────────────────────┐
│ Prompts 示意 │
├───────────────────────────────────────────────────────────┤
│ │
│ 一个 MCP Server 可以暴露 Prompts: │
│ │
│ { │
│ "name": "code_review", │
│ "description": "对选中的代码进行代码审查", │
│ "arguments": [ │
│ { │
│ "name": "language", │
│ "description": "编程语言", │
│ "required": true │
│ } │
│ ] │
│ } │
│ │
│ Client 获取这个 Prompt 后,组合成消息: │
│ "请使用 [language] 的最佳实践,审查以下代码:..." │
│ │
│ Prompts 本质上就是"预制的 system prompt 模板"。 │
│ 它让工具作者可以告诉用户 "我的工具最好这样用"。 │
│ │
└───────────────────────────────────────────────────────────┘2.4 Sampling(采样)——Server 反向调用 LLM
这是 MCP 协议中最特殊的能力。通常情况下是 Client 调用 Server;但 Sampling 允许 Server 主动请求 Client 帮它调用 LLM。
┌───────────────────────────────────────────────────────────┐
│ Sampling 流程 │
├───────────────────────────────────────────────────────────┤
│ │
│ 正常流程: │
│ Client ──tools/call──→ Server ──result──→ Client │
│ │
│ Sampling 流程(Server 需要 LLM 帮忙): │
│ Client ──tools/call──→ Server │
│ │ │
│ │ "这个数据太复杂了, │
│ │ 我需要 LLM 帮我做判断" │
│ │ │
│ Client ←──sampling request── Server │
│ │ │
│ │ Client 把 Server 的请求发给 LLM │
│ │ LLM 返回结果 │
│ │ │
│ Client ──sampling result──→ Server │
│ │ │
│ │ Server 用 LLM 的判断 │
│ │ 继续处理,最终返回结果 │
│ │
│ 适用场景: │
│ • Server 内部处理复杂文本,需要 LLM 做摘要 │
│ • Server 需要 LLM 帮你做决策(如"这个文件应该分类到哪?")│
│ │
│ 注意:Client 有权拒绝 Server 的 sampling 请求。 │
│ Server 不能假设 LLM 一定可用。 │
│ │
└───────────────────────────────────────────────────────────┘第3部分:构建一个具体的 MCP Server
3.1 场景:天气查询 MCP Server
假设我们要写一个 MCP Server,封装一个天气查询 API。任何实现了 MCP Client 的 AI 应用(Claude Desktop、Cursor、Continue)都能自动发现并使用它。
用 Python MCP SDK 来写:
"""
weather_server.py — 天气查询 MCP Server
这是一个完整的、可运行的 MCP Server 示例。
"""
import asyncio
import httpx
from mcp.server import Server, NotificationOptions
from mcp.server.models import InitializationCapabilities
from mcp.server.stdio import stdio_server
# Step 1: 创建 Server 实例
server = Server("weather-server")
# Step 2: 定义工具列表
@server.list_tools()
async def list_tools() -> list:
"""
Client 调用 tools/list 时触发。
返回这个 Server 提供的所有工具定义。
"""
return [
{
"name": "get_current_weather",
"description": (
"获取指定城市的实时天气信息。"
"返回温度、湿度、天气状况和风速。"
),
"inputSchema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,例如 '北京'、'上海'、'东京'"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "温度单位,默认 celsius"
}
},
"required": ["city"]
}
},
{
"name": "get_weather_forecast",
"description": (
"获取指定城市未来 3 天的天气预报。"
"返回每天的最高温、最低温和天气状况。"
),
"inputSchema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称"
},
"days": {
"type": "integer",
"description": "预报天数,1-7,默认 3",
"minimum": 1,
"maximum": 7
}
},
"required": ["city"]
}
}
]
# Step 3: 实现工具调用
@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list:
"""
Client 调用 tools/call 时触发。
name: LLM 决定的工具名
arguments: LLM 决定的参数
"""
if name == "get_current_weather":
city = arguments["city"]
unit = arguments.get("unit", "celsius")
# 真实项目中这里调用天气 API
weather_data = await fetch_weather_from_api(city, unit)
# 返回结果 —— 必须是 content 数组
return [{
"type": "text",
"text": (
f"🏙️ {city}\n"
f"🌡️ 温度: {weather_data['temp']}°{unit[0].upper()}\n"
f"💧 湿度: {weather_data['humidity']}%\n"
f"☁️ 天气: {weather_data['condition']}\n"
f"🌬️ 风速: {weather_data['wind_speed']} m/s"
)
}]
elif name == "get_weather_forecast":
city = arguments["city"]
days = arguments.get("days", 3)
forecast = await fetch_forecast_from_api(city, days)
lines = [f"📅 {city} 未来 {days} 天天气预报:\n"]
for day in forecast:
lines.append(
f" {day['date']}: "
f"{day['high']}° / {day['low']}°, "
f"{day['condition']}"
)
return [{"type": "text", "text": "\n".join(lines)}]
else:
raise ValueError(f"Unknown tool: {name}")
# Step 4: 模拟 API 调用(真实项目替换为实际 HTTP 请求)
async def fetch_weather_from_api(city: str, unit: str) -> dict:
"""模拟天气 API 调用。真实项目用 httpx.get()"""
# 真实代码:
# async with httpx.AsyncClient() as client:
# resp = await client.get(
# "https://api.weather.com/v1/current",
# params={"city": city, "unit": unit},
# headers={"Authorization": f"Bearer {API_KEY}"}
# )
# return resp.json()
return {
"temp": 28,
"humidity": 45,
"condition": "晴朗",
"wind_speed": 3.4
}
async def fetch_forecast_from_api(city: str, days: int) -> list:
"""模拟天气预报 API 调用"""
return [
{"date": "2026-07-29", "high": 32, "low": 24, "condition": "晴"},
{"date": "2026-07-30", "high": 30, "low": 23, "condition": "多云"},
{"date": "2026-07-31", "high": 28, "low": 22, "condition": "小雨"},
]
# Step 5: 启动 Server(stdio transport)
async def main():
async with stdio_server() as (read_stream, write_stream):
await server.run(
read_stream,
write_stream,
InitializationCapabilities(
sampling={},
experimental={},
),
)
if __name__ == "__main__":
asyncio.run(main())3.2 在 Claude Desktop 中配置这个 Server
当你写完 MCP Server 后,下一步就是在 AI 应用中配置它。以 Claude Desktop 为例:
在 claude_desktop_config.json 中添加:
{
"mcpServers": {
"weather": {
"command": "python",
"args": ["/path/to/weather_server.py"]
}
}
}Claude Desktop 启动时会自动执行 python /path/to/weather_server.py,把这个脚本作为子进程启动。之后通过 stdin/stdout 与它通信。用户跟 Claude 对话时,Claude 会:
- 调用
tools/list获取get_current_weather和get_weather_forecast的定义 - 把这两个工具的定义转成 Function Calling 格式发给 Claude 模型
- 如果 Claude 决定调用
get_current_weather({"city": "北京"}),Claude Desktop 通过tools/call发给 MCP Server - MCP Server 执行查询,返回天气结果
- Claude Desktop 把结果返回给 Claude 模型,让 Claude 整合成自然语言回答
3.3 线上的 JSON-RPC 消息(逐条展示)
下面是当你问 Claude Desktop "北京天气怎样?"时,实际在 stdin/stdout 上传输的完整 JSON-RPC 消息序列:
═══════════════════════════════════════════════════════════════
完整 JSON-RPC 消息交换日志
═══════════════════════════════════════════════════════════════
[Client → Server] 握手 - 初始化
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{
"protocolVersion":"2024-11-05",
"capabilities":{"roots":{"listChanged":true}},
"clientInfo":{"name":"claude-desktop","version":"0.8.0"}
}}
[Server → Client]
{"jsonrpc":"2.0","id":1,"result":{
"protocolVersion":"2024-11-05",
"capabilities":{"tools":{"listChanged":true}},
"serverInfo":{"name":"weather-server","version":"1.0.0"}
}}
[Client → Server] 通知 - 初始化完成
{"jsonrpc":"2.0","method":"notifications/initialized"}
───────────────────────────────────────────────────────────────
[Client → Server] 请求工具列表
{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}
[Server → Client]
{"jsonrpc":"2.0","id":2,"result":{"tools":[
{"name":"get_current_weather","description":"获取指定城市...",
"inputSchema":{"type":"object","properties":{
"city":{"type":"string","description":"城市名称"},
"unit":{"type":"string","enum":["celsius","fahrenheit"]}},
"required":["city"]}},
{"name":"get_weather_forecast","description":"获取指定城市未来...",
"inputSchema":{"type":"object","properties":{
"city":{"type":"string"},
"days":{"type":"integer","minimum":1,"maximum":7}},
"required":["city"]}}
]}}
───────────────────────────────────────────────────────────────
此时 Claude Desktop 把工具定义发给 Claude 模型
Claude 模型决定调用 get_current_weather({"city":"北京"})
───────────────────────────────────────────────────────────────
[Client → Server] 调用工具
{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{
"name":"get_current_weather",
"arguments":{"city":"北京","unit":"celsius"}
}}
Server 内部执行: fetch_weather_from_api("北京","celsius")
Server 拿到真实天气数据
[Server → Client]
{"jsonrpc":"2.0","id":3,"result":{"content":[
{"type":"text","text":"🏙️ 北京\n🌡️ 温度: 28°C\n💧 湿度: 45%\n
☁️ 天气: 晴朗\n🌬️ 风速: 3.4 m/s"}
]}}
───────────────────────────────────────────────────────────────
此时 Claude Desktop 把结果返回给 Claude 模型
Claude 模型生成: "北京今天天气晴朗,温度 28°C..."
───────────────────────────────────────────────────────────────第4部分:MCP vs Function Calling
这是很多人最容易混淆的地方。两者都涉及"工具定义"和"工具调用",但它们解决的问题层次完全不同。
4.1 本质区别
┌───────────────────────────────────────────────────────────┐
│ MCP vs Function Calling 本质对比 │
├───────────────────────────────────────────────────────────┤
│ │
│ Function Calling: │
│ ┌───────────────────────────────────────────────────┐ │
│ │ "告诉一个 LLM:在这次对话中,你有这些工具。" │ │
│ │ │ │
│ │ 作用域:一次对话(一次 API 请求的 tools 参数) │ │
│ │ 谁定义工具:开发者(写在代码里的 Python dict) │ │
│ │ 谁执行工具:开发者的应用代码 │ │
│ │ 复用性:每个 AI 应用都要自己写一套 │ │
│ │ │ │
│ │ 类比:每次出门前告诉你 "今天车上有地图、有扳手" │ │
│ └───────────────────────────────────────────────────┘ │
│ │
│ MCP: │
│ ┌───────────────────────────────────────────────────┐ │
│ │ "注册一个工具服务器:任何 AI 应用都能发现和使用。" │ │
│ │ │ │
│ │ 作用域:跨应用、跨会话、跨 LLM │ │
│ │ 谁定义工具:工具作者(写一次,放那里) │ │
│ │ 谁执行工具:MCP Server 自己执行 │ │
│ │ 复用性:同一个 MCP Server 被多个 AI 应用共享 │ │
│ │ │ │
│ │ 类比:车上装好工具箱,钥匙给你——谁开车都能用 │ │
│ └───────────────────────────────────────────────────┘ │
│ │
└───────────────────────────────────────────────────────────┘4.2 协作关系
MCP 和 Function Calling 不是替代关系,是协作关系:
┌───────────────────────────────────────────────────────────┐
│ MCP + Function Calling 协作流程 │
├───────────────────────────────────────────────────────────┤
│ │
│ MCP 负责: │
│ • 工具注册和发现(tools/list) │
│ • 工具的实际执行(tools/call → Server 执行) │
│ • 跨应用复用(一个 Server 多处用) │
│ │
│ Function Calling 负责: │
│ • LLM 推理"该不该调用工具" │
│ • LLM 生成工具名和参数 │
│ • LLM 整合工具结果为自然语言 │
│ │
│ 流程: │
│ │
│ MCP Server ──tools/list──→ MCP Client │
│ │ │
│ │ 转为 Function Calling 格式 │
│ ↓ │
│ LLM │
│ │ │
│ │ 返回 tool_calls │
│ ↓ │
│ MCP Server ←──tools/call── MCP Client │
│ │ │
│ │ 执行真实操作 │
│ ↓ │
│ MCP Server ──result──→ MCP Client │
│ │ │
│ │ 把结果返回 LLM │
│ ↓ │
│ LLM │
│ │ │
│ │ 最终回答 │
│ ↓ │
│ 用户 │
│ │
└───────────────────────────────────────────────────────────┘4.3 一句话总结区别
| 维度 | Function Calling | MCP |
|---|---|---|
| 层面 | LLM 推理能力 | 工具集成协议 |
| 谁维护 | 每个应用各自维护 | 工具提供者维护一次 |
| 如何发现工具 | 硬编码在代码里 | 协议发现(tools/list) |
| 如何执行工具 | 应用代码里写死 | MCP Server 执行 |
| 跨应用复用 | 不支持 | 核心设计目标 |
| 需要 LLM 吗 | 是(LLM 决定调哪个) | 否(纯协议层) |
| 需要 MCP 吗 | 否 | — |
第5部分:为什么 MCP 重要?
5.1 它正在成为标准
MCP 发布于 2024 年 11 月,不到一年时间已经获得了广泛支持:
┌───────────────────────────────────────────────────────────┐
│ 已支持 MCP 的应用和平台 │
├───────────────────────────────────────────────────────────┤
│ │
│ AI 应用(作为 MCP Client): │
│ ┌───────────────────────────────────────────────────┐ │
│ │ • Claude Desktop — Anthropic 官方,首发支持 │ │
│ │ • Cursor — 最流行的 AI 编程 IDE │ │
│ │ • Continue.dev — 开源的 AI 编程助手 │ │
│ │ • Zed — 高性能 AI 代码编辑器 │ │
│ │ • Sourcegraph Cody — 代码理解 AI │ │
│ │ • Goose — Block 的 AI 开发助手 │ │
│ │ • Cline (VS Code) — VS Code 的自主 Agent │ │
│ │ • ChatGPT Desktop — OpenAI 的桌面应用 │ │
│ └───────────────────────────────────────────────────┘ │
│ │
│ 工具和平台(作为 MCP Server): │
│ ┌───────────────────────────────────────────────────┐ │
│ │ • GitHub — 代码仓库 │ │
│ │ • Slack — 即时通讯 │ │
│ │ • Linear — 项目管理 │ │
│ │ • Notion — 知识库 │ │
│ │ • Figma — 设计工具 │ │
│ │ • PostgreSQL/SQLite — 数据库 │ │
│ │ • Puppeteer — 浏览器自动化 │ │
│ │ • 文件系统 — 原生 stdio MCP Server │ │
│ │ • ...数百个社区 MCP Server │ │
│ └───────────────────────────────────────────────────┘ │
│ │
└───────────────────────────────────────────────────────────┘5.2 它对开发者的意义
写一次,到处使用。 这是 MCP 最核心的价值主张。
你写一个 Slack MCP Server,任何支持 MCP 的 AI 应用都能自动获得 Slack 能力。不需要为 Claude Desktop 写一套 Slack 集成,再为 Cursor 写一套,再为 Continue 写一套。
5.3 它对用户的意义
用户不需要理解"这个 AI 应用集成了哪些工具"。他们只需要:
- 找到一个 MCP Server(社区市场、官方列表)
- 在 AI 应用的配置文件里加一行配置
- 开始使用
就像你买一个 USB 鼠标——插上就能用,不用装驱动。
5.4 生态爆炸的加速度
MCP 正在触发一个正向循环:
┌───────────────────────────────────────────────────────────┐
│ MCP 生态正向循环 │
├───────────────────────────────────────────────────────────┤
│ │
│ 更多 AI 应用支持 MCP │
│ ↓ │
│ 更多开发者愿意写 MCP Server │
│ (因为他们写的 Server 能被很多应用使用) │
│ ↓ │
│ 工具生态更丰富 │
│ ↓ │
│ 更多用户选择支持 MCP 的 AI 应用 │
│ (因为他们的工具都能用了) │
│ ↓ │
│ 更多 AI 应用被迫支持 MCP(竞争压力) │
│ ↓ │
│ ↑ 循环加速 ←──┘ │
│ │
└───────────────────────────────────────────────────────────┘这个循环与当年 USB 取代 PS/2、串口、并口的逻辑完全一致——不是 USB 技术有多先进,而是标准化带来了网络效应。
5.5 MCP 的当前局限
┌───────────────────────────────────────────────────────────┐
│ MCP 的已知局限 │
├───────────────────────────────────────────────────────────┤
│ │
│ 1. 协议仍在演进(当前版本 2024-11-05,可能有 breaking │
│ changes) │
│ │
│ 2. 安全性依赖 transport 层 │
│ • stdio 很安全(本地进程) │
│ • HTTP+SSE 依赖 HTTPS + 认证(目前认证方案不成熟) │
│ │
│ 3. Server 发现机制不成熟 │
│ • 目前主要靠手动配置 + 社区市场 │
│ • 缺少类似 npm/pip 的官方 registry │
│ │
│ 4. 调试工具有限 │
│ • 排查 JSON-RPC 问题较困难 │
│ • 社区正在构建 Inspector 等调测工具 │
│ │
│ 5. 本地 vs 远程边界模糊 │
│ • Server 跑在本地还是云端?谁管理生命周期? │
│ • 目前主要由 Client 负责管理 │
│ │
└───────────────────────────────────────────────────────────┘核心总结
总结1:MCP 解决 N×M 集成困境
MCP = AI 工具集成领域的 USB 协议
写一次 MCP Server → 所有实现了 MCP Client 的 AI 应用都能用
从 N×M 的集成复杂度降到 N+M总结2:MCP 架构三要素
┌───────────────────────────────────────────────────────────┐
│ │
│ MCP Client(你的 AI 应用) │
│ ↕ JSON-RPC over stdio 或 HTTP+SSE │
│ MCP Server(工具包装器) │
│ ↕ 原生 API │
│ 外部工具/数据源(GitHub, Slack, 数据库...) │
│ │
└───────────────────────────────────────────────────────────┘总结3:四大原语
| 原语 | 用途 | 方向 |
|---|---|---|
| Resources | 暴露数据(读文件、查数据库) | Client 读 Server |
| Tools | 暴露可执行操作 | Client 调用 Server |
| Prompts | 暴露提示词模板 | Client 读取 Server |
| Sampling | Server 反向请求 LLM | Server 请求 Client |
总结4:MCP vs Function Calling
- Function Calling = 单次会话的工具推理能力(LLM 层面)
- MCP = 跨应用的工具集成标准(协议层面)
- 两者协作:MCP 负责工具的注册和执行,Function Calling 负责 LLM 的推理
总结5:一个 MCP Server 的要素
# 最小化 MCP Server 的三个要素:
# 1. 工具定义(list_tools) — 告诉 Client "我能做什么"
# 2. 工具执行(call_tool) — 实际执行,返回结果
# 3. Transport 启动 — 监听 stdio 或 HTTP 端口章节测试
测试1:概念理解
MCP 解决的核心问题是什么?请用自己的话描述 N×M 困境。
测试2:协议消息
一个完整的 MCP 会话中,Client 和 Server 之间交换哪些类型的 JSON-RPC 消息?按顺序列举。
测试3:四大原语
MCP 定义了哪四种原语(primitives)?各自的作用是什么?
测试4:Transport 选择
什么情况下应该使用 stdio transport,什么情况下应该使用 HTTP+SSE?
测试5:MCP vs Function Calling
以下说法正确的是? A. MCP 是 Function Calling 的替代品 B. MCP 和 Function Calling 在不同的层面工作,是协作关系 C. MCP 运行时也需要 LLM 做推理 D. Function Calling 可以替代 MCP 的工具注册和发现
测试6:Sampling 场景
MCP 的 Sampling 原语的设计目的是什么?它和普通的 tools/call 有什么本质区别?
测试7:架构判断
以下哪项说法是正确的? A. MCP Server 可以直接调用 LLM B. MCP Server 通过 Sampling 请求,让 MCP Client 代它调用 LLM C. MCP Server 必须在云端运行 D. MCP Client 必须是 Claude Desktop
参考答案
测试1答案
答案:MCP 解决的是 AI 应用与外部工具集成的 N×M 困境。在 MCP 出现之前,每个 AI 应用(M 个)要集成每个外部工具(N 个),需要 M×N 套独立的集成代码。MCP 通过标准化协议,把这个问题化简为 N+M:写 N 个 MCP Server,M 个 AI 应用通过实现 MCP Client 就能自动使用所有 N 个工具。
测试2答案
答案:按顺序:
initialize(请求) — Client 发起握手,声明协议版本和能力initialize(响应) — Server 回复自己的协议版本和能力notifications/initialized(通知) — Client 通知握手完成tools/list(请求) — Client 列出可用工具tools/list(响应) — Server 返回工具列表及 JSON Schematools/call(请求) — Client 调用指定工具tools/call(响应) — Server 返回执行结果
测试3答案
答案:
- Resources:暴露数据/文件内容,通过 URI 标识,支持读取和订阅变更
- Tools:暴露可执行的操作,有输入参数(JSON Schema)和返回结果
- Prompts:暴露预定义的提示词模板,告诉 Client "你可以这样跟我对话"
- Sampling:允许 Server 反向请求 Client 调用 LLM,用于 Server 内部需要 LLM 推理的场景
测试4答案
答案:
- stdio:本地工具(文件系统、本地数据库、个人脚本),零网络开销,无需端口管理,安全性好
- HTTP+SSE:远程工具(云端 API、企业内部服务)、多客户端共享场景、SaaS 工具集成
测试5答案
答案:B
解析:
- A 错误:MCP 和 Function Calling 不是替代关系,是协作关系。MCP 负责注册/执行工具,Function Calling 负责 LLM 推理
- C 错误:MCP 是纯协议层,它的 tools/list 和 tools/call 不需要 LLM 参与。只有把工具结果交给用户时才需要 LLM 做自然语言整合
- D 错误:Function Calling 本身不提供工具注册和发现机制,那些都需要开发者硬编码
测试6答案
答案:Sampling 允许 MCP Server 反向请求 MCP Client 调用 LLM。
与普通 tools/call 的本质区别:
tools/call的方向是 Client → Server(Client 让 Server 做某事)sampling的方向是 Server → Client(Server 让 Client 调用 LLM 帮它做某事)
场景示例:MCP Server 在处理一个复杂文件时,需要 LLM 帮忙判断"这个文件的内容应该分类到哪个目录"。Server 本身不连接 LLM,所以它通过 sampling 请求 Client 代为调用。
测试7答案
答案:B
解析:
- A 错误:MCP Server 不能直接调用 LLM,它通过 Sampling 请求让 Client 代为调用
- C 错误:MCP Server 可以在本地运行(stdio transport),不必须云端
- D 错误:任何实现了 MCP Client 的应用都可以连接 MCP Server,包括 Cursor、Continue、ChatGPT Desktop 等
相关笔记
- [[01-function-calling]] - Function Calling 基础:LLM 如何决定调用工具
- [[00-agent-overview]] - Agent 整体学习路线
- [[02-framework-evolution]] - Agent 框架如何封装工具调用
- [[05-agent-workflow]] - 工具调用在 Agent 工作流中的应用
下一步学习
- [ ] 动手:使用 Python MCP SDK 构建一个自己的 MCP Server
- [ ] 尝试:在 Claude Desktop 或 Cursor 中配置一个 MCP Server
- [ ] 探索:浏览 MCP 官方文档 和社区 Server 市场
学习状态:🟡 开始学习