跳转至

MCP 协议详解

3.2 MCP 协议详解

一、核心概念

在 Function Calling 普及之前,每个 AI 应用都要自己实现工具集成——搜索、数据库、文件系统各写一套胶水代码,换一个模型提供商就要重写一遍。更麻烦的是,Claude 的工具调用格式和 OpenAI 的不一样,LangChain 又有自己的 Tool 抽象,生态碎片化严重。

MCP(Model Context Protocol)是 Anthropic 在 2024 年 11 月开源的工具接入标准协议,目标是做 AI 工具生态的"USB-C"——定义统一接口,让任何 LLM 应用都能无缝接入任何工具服务,而无需关心对方的实现细节。它解决的不是"模型能不能调工具"的问题(Function Calling 已经解决了),而是"工具如何被标准化地注册、发现和调用"的工程问题。

理解 MCP 的关键一句话:MCP 把工具服务器(MCP Server)变成了可独立部署、可复用的能力单元,而不是散落在各个应用里的硬编码函数。


二、原理深讲

2.1 三层架构:Host / Client / Server

MCP 的架构分三层,职责清晰:

graph LR
    subgraph Host["Host 进程(如 Claude Desktop / 你的 Agent 应用)"]
        App["应用逻辑"]
        Client1["MCP Client 1"]
        Client2["MCP Client 2"]
    end

    subgraph Servers["MCP Servers(独立进程)"]
        S1["文件系统 Server"]
        S2["数据库查询 Server"]
        S3["代码执行 Server"]
    end

    App --> Client1
    App --> Client2
    Client1 <-->|"MCP 协议"| S1
    Client1 <-->|"MCP 协议"| S2
    Client2 <-->|"MCP 协议"| S3

Host 是 AI 应用本身(Claude Desktop、你自己开发的 Agent 服务),它持有 LLM 连接和对话上下文,负责决策"要不要调工具"。

MCP Client 是 Host 内部的协议客户端,每个 Client 对应一个 Server 连接,负责协议握手、能力发现和消息路由。通常由框架(如 LangChain、Claude SDK)内置实现,开发者很少需要手写。

MCP Server 是对外暴露能力的独立进程,实现了协议规定的接口,可以用任何语言写(官方提供 Python/TypeScript SDK)。它不知道谁在调用自己,也不持有 LLM 状态——这种解耦是 MCP 可复用性的基础。

工程建议:Server 设计为无状态更易维护,如果必须有状态(如数据库连接池),用进程内单例管理,不要依赖 Client 传递状态。


2.2 Transport 层:stdio vs HTTP+SSE

MCP 协议本身是语言无关的,但消息怎么传输取决于部署场景:

维度 stdio HTTP + SSE
适用场景 本地工具、CLI 集成 远程服务、多客户端共享
部署方式 Host 直接 fork 子进程 独立部署为 HTTP 服务
延迟 极低(进程内 IPC) 网络延迟,通常 1-10ms
安全性 天然隔离,无网络暴露 需要 TLS + Auth
扩展性 每个 Host 实例启动独立子进程 多 Host 共享同一 Server
典型案例 Claude Desktop 本地插件 企业内部共享工具平台

stdio 模式的工作原理:Host 通过 subprocess fork 出 Server 进程,通过 stdin/stdout 交换 JSON-RPC 消息。消息格式严格,每行一条 JSON,换行符作为消息分隔符。

Host进程                    Server子进程
    |                           |
    |-- stdin: {"method":"initialize",...} -->|
    |<-- stdout: {"result":{"capabilities":{...}}} --|
    |-- stdin: {"method":"tools/call",...} -->|
    |<-- stdout: {"result":{...}} --|

HTTP+SSE 模式下,Server 是一个标准 HTTP 服务。Client 先发 POST 请求,Server 通过 SSE(Server-Sent Events)推送响应,支持流式返回长内容。这种模式适合需要跨团队共享或部署在 K8s 的场景。

注意:截至 2024 年底,MCP 规范中 HTTP 传输层正在从 HTTP+SSE 向 Streamable HTTP 演进(单一端点同时支持请求/响应和流式推送),新实现建议参考官方最新规范而非早期文档。

选型建议: - 开发阶段和本地工具 → stdio,调试简单,print() 直接看日志 - 生产环境、多租户、需要 Auth → HTTP,配合反向代理和 JWT


2.3 三类能力:Tools / Resources / Prompts

MCP Server 可以向 Host 暴露三种能力,理解它们的区别是设计 Server 的关键:

classDiagram
    class MCPServer {
        +list_tools() Tool[]
        +call_tool(name, args) Result
        +list_resources() Resource[]
        +read_resource(uri) Content
        +list_prompts() Prompt[]
        +get_prompt(name, args) Message[]
    }

    class Tool {
        +name: string
        +description: string
        +inputSchema: JSONSchema
    }

    class Resource {
        +uri: string
        +name: string
        +mimeType: string
    }

    class Prompt {
        +name: string
        +description: string
        +arguments: Arg[]
    }

    MCPServer --> Tool
    MCPServer --> Resource
    MCPServer --> Prompt

Tools(工具) 是最常用的能力类型,对应"执行一个操作并返回结果"——查询数据库、调用 API、执行代码。Tool 有明确的输入 Schema(JSON Schema 格式),模型根据 schema 生成参数,Host 校验后调用。设计原则:每个 Tool 职责单一,description 要精准(这是模型选工具的依据,比函数名更重要)。

Resources(资源) 是"给模型读取上下文"用的,类似只读文件系统。Resources 通过 URI 寻址(如 file:///config.yamldb://users/123),返回文本或二进制内容注入到上下文窗口。适合提供静态配置、文档片段、数据库记录的读取——这些操作不需要模型"决策调用",而是由 Host 主动拉取。

Prompts(提示词模板) 是服务器端定义的可复用提示词,支持参数化。比如一个代码审查 Server 可以注册一个 review_code Prompt,接受 languagecode 参数,返回结构化的审查指令。这个能力在 Claude Desktop 中体现为"/"斜杠命令。工程实践中这类能力使用较少,大多数 Agent 框架会在应用层管理 Prompt,无需下沉到 Server。

三类能力的工程直觉

能力 谁触发 有无副作用 典型用途
Tools LLM 决策调用 有(写操作) 执行动作、查询数据
Resources Host 主动拉取 无(只读) 提供上下文
Prompts 用户/Host 触发 复用提示词模板

三、工程视角:常见误区与最佳实践

误区一:Tool description 随便写正确做法:description 是模型选择工具的唯一依据,必须清晰描述"什么情况下用这个工具、能做什么、不能做什么"。尤其当工具数量超过 10 个时,description 写得模糊会导致模型频繁选错工具或参数填错。推荐格式:[动词] [对象],当[场景]时使用,返回[结果格式]

误区二:Server 里直接 print() 调试正确做法:stdio 模式下,stdout 是协议信道,任何非 JSON 输出都会破坏消息帧,导致 Host 解析失败且报错信息极难定位。调试日志必须写到 stderr(print(..., file=sys.stderr))或写文件。HTTP 模式没有这个问题。

误区三:一个 Server 塞所有工具正确做法:按领域边界拆分 Server(文件系统一个、数据库一个、外部 API 一个),好处有三:独立部署升级互不影响;权限控制粒度更细(生产库 Server 只授权特定 Agent);单 Server 崩溃不影响其他能力。Claude Desktop 的设计哲学也是如此:每个插件对应一个独立 Server 进程。

误区四:忽略 Server 的错误返回规范正确做法:Tool 调用失败时不要抛异常让 Host 崩溃,要返回带 isError: true 的结构化结果,并在 content 里给出人类可读的错误描述(如"数据库连接失败,请检查 DB_URL 配置")。LLM 能读懂错误信息并自动重试或向用户说明,比堆栈报错信息有用得多。

误区五:在 Server 里做鉴权正确做法:stdio 模式的 Server 天然只被 Host 进程访问,无需额外鉴权;HTTP 模式的 Server 鉴权应在反向代理层(Nginx/API Gateway)处理,而不是在 Server 业务代码里写一遍。Server 内部可以通过环境变量接收服务凭证(数据库密码、API Key),但不要实现自己的 Token 验证逻辑——职责分离。


四、延伸思考

🤔 思考题一:MCP Server 的 Resources 能力和 RAG 的向量检索在本质上都是"给模型提供上下文",那什么情况下应该用 Resource,什么情况下应该走 RAG 检索?两者能否结合——比如把向量检索结果封装为 Resource 返回?

🤔 思考题二:当 Agent 需要调用 50+ 个工具时,把所有工具一次性塞进系统提示会消耗大量 Token,且模型选工具的准确率会下降。MCP 协议本身并没有解决"工具发现"问题——你会如何在 Host 层设计一个动态工具路由机制,在每次 LLM 调用前只注入最相关的工具子集?(提示:参考 Module 3.4 工具可靠性章节的工具路由设计)