第08章:MCP 模型上下文协议(上)

🎯 目标读者 & 阅读收益

本文面向有 Python 基础、接触过 LangChain 工具调用、但从未了解过 MCP 的开发者。你不需要懂网络协议,也不需要深入理解 JSON-RPC,只需要知道"函数可以被调用"就够了。
读完本文,你将能够:清楚解释 MCP 是什么、它解决了什么问题;看懂并运行配套的 01_mcp_concepts.py;理解工具(Tools)、资源(Resources)、提示(Prompts)三大组件的区别;判断自己的项目场景是否适合引入 MCP。
前期回顾



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)

📌 注意MCPToolResultcontent 列表而不是单一字符串,是因为 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"
}

写好描述的三个原则

  1. 说清格式 :字符串参数一定要说明格式(YYYY-MM-DDHH:MM:SS 等)
  2. 给出示例例如 2024-01-01 比单纯描述更有效
  3. 说明含义从该日期计算到今天开始日期 更清晰

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 才能正确调用工具

📌 下一章预告: 掌握基础概念后,下一章将进入实战:

  • 进阶篇 :使用真实的 mcp SDK 搭建生产级服务器
  • 集成篇:将 MCP 服务器接入 LangChain Agent 和 Claude Desktop
  • 部署篇:通过 HTTP 而非 stdio 暴露 MCP 服务,实现远程调用
    作者:阿聪谈架构

公众号:阿聪谈架构 (分享后端架构 / AI / Java 技术文章)

相关代码关注公众号:【阿聪谈架构】 回复:AI专栏代码

相关推荐
阿瑞说项目管理1 小时前
AI Agent 与普通 AI 助手的区别是什么?
大数据·人工智能·agent·智能体·企业级ai
周末也要写八哥1 小时前
浅谈:大语言模型中的逆转诅咒现象
人工智能·语言模型·自然语言处理
黎阳之光1 小时前
黎阳之光:以视频孪生+全域感知,助力低空经济破局突围
大数据·人工智能·算法·安全·数字孪生
吃一根烤肠1 小时前
CloudBase MCP 实战:用自然语言 30 分钟搭建智能待办事项
人工智能
mrchan1 小时前
markdown 画图总结
后端
开心就好20252 小时前
全面介绍iOS开发工具:Xcode、AppCode、CocoaPods、Fastlane和Git
后端·ios
汽车仪器仪表相关领域2 小时前
Kvaser Leaf Light HS v2 M12:5 针 M12 NMEA 2000 接口,海事与工业 CAN 总线测试的防水耐用之选
大数据·网络·人工智能·功能测试·安全性测试
xiaoxiang96092 小时前
Graphify从入门到精通:用知识图谱彻底改变AI编程效率
人工智能·知识图谱·ai编程
CeshirenTester2 小时前
航旅纵横APP故障18h后,各项功能才恢复正常
人工智能