MCP 协议设计与实现

第21章 设计模式与架构决策

作者 杨艺韬 · 7,438 字

第21章 设计模式与架构决策

“Patterns are not invented, they are discovered — in the recurring decisions that successful systems make when facing the same fundamental tensions.”

“好的协议不是发明出来的,是被迫发现的——当一个系统面对多个根本性张力时,它会收敛到一组几乎唯一的设计选择。”

本章要点

  • 从全书内容中提炼六大核心设计模式,每个模式对应一对根本张力
  • 理解每个模式在 MCP 不同章节中的具体体现,建立横向视图
  • 掌握模式间的协同关系与互相强化,它们不是孤立的条目而是一套咬合系统
  • 认识 MCP 设计决策对未来协议演进的约束与启示
  • 建立可迁移到其他系统设计的架构决策框架——协议设计的”六脉神剑”

21.1 模式的发现

21.1.1 为什么”从现象总结”比”空谈原理”更有力

本章是全书的收官之章。我不打算从理论出发罗列”好协议的 10 条标准”——那种文章网上太多。我要做的是反向操作:带你回看前 20 章的具体决策,从中反向归纳出 MCP 作者群最底层的思考模型。

为什么这样做?

  • 空谈原理时人人点头,落地时人人走样。抽象的原则很难内化
  • 反之,从具体 API 反推”为什么这里要三个参数而不是两个”,就能在大脑里建立可复用的模式匹配器
  • 下次你自己设计协议时,遇到类似张力,这些模式会自动触发,替你做出更好的决策

21.1.2 全书脉络回顾

回顾这 21 章的旅程——从 MCP 的诞生动机(第 1 章)到架构全景(第 2 章),到 JSON-RPC 基础(第 3 章),到生命周期管理(第 4 章),到三大原语的深入分析(第 5-7 章),到两大 SDK 的实现解读(第 8-11 章),到传输层的多重选择(第 12-14 章),到安全与授权(第 15 章),到发现与注册(第 16 章),到反向通道与配置管理(第 17-18 章),到真实产品中的落地(第 19 章),再到亲手构建 Server(第 20 章)——我们积累了足够的材料来做一件更深层的事:

识别贯穿整个协议的设计模式。

这些模式不是凭空创造的规则,而是从源码、规范和实践中反复出现的决策逻辑中提炼出来的。它们回答的不是”MCP 做了什么”,而是”MCP 为什么这样做,而不是别样做”。

mindmap
  root((MCP 六大设计模式))
    协议层
      能力协商<br/>解决<兼容性>张力
      版本演进策略<br/>解决<稳定与演进>张力
      传输抽象<br/>解决<解耦与实用>张力
    语义层
      三原语模型<br/>解决<灵活与标准>张力
      控制平面分离
    安全层
      安全即默认<br/>解决<开放与安全>张力
    工程层
      渐进式特性暴露<br/>解决<强大与易用>张力

21.1.3 阅读本章的方式

每个模式按以下结构解析:

  1. 根本张力——该模式试图调和哪两个对立目标
  2. 模式描述——具体是怎么做的
  3. 在 MCP 中的体现——全书哪些章节、哪些 API 是它的实例
  4. 可迁移的洞察——离开 MCP 后,这个模式还能用到哪里

这个四段式让你可以跳读:只看”张力”部分就能快速把握;想深入就读”体现”章节;想迁移到自己项目就看”洞察”。

21.2 模式一:能力协商(Capability Negotiation)

21.2.1 解决的张力

协议需要稳定(Client 和 Server 可能由不同团队在不同时间开发),但功能需要演进(新特性不断被添加)。如何让新旧版本、功能残缺版和功能齐全版在同一张网里共存

如果用简单的”版本号”来解决——1.0、1.1、2.0——立刻会撞上三堵墙:

  1. 维度坍塌:Server A 实现了 Tools 和 Resources 但不支持 Completion,Server B 实现了 Tools 和 Completion 但不支持 Resources。他们都是 “1.0 兼容”还是”1.1 兼容”?版本号没法描述这种部分实现
  2. 升级锁步:任何新特性需要升版本号,所有 Client/Server 都要跟着升——在一个百万级实现的生态里这是灾难。
  3. 回归恐怖:一个 Client 想用版本 2.0 的新特性,但只能和支持 2.0 的 Server 通信,用户就骂”为什么我的老 Server 不兼容”。

21.2.2 模式描述

MCP 的初始化握手不仅是”我是谁”的自我介绍,更是”我能做什么”的能力宣告。Client 和 Server 各自声明自己支持的特性集合,双方在交集内工作:

graph LR
    subgraph "Client 能力"
        CR["roots"]
        CE["elicitation<br/>(form + url)"]
        CS["sampling"]
    end

    subgraph "Server 能力"
        ST["tools"]
        SR["resources<br/>(subscribe)"]
        SP["prompts"]
        SL["logging"]
        SC["completions"]
    end

    subgraph "协商结果"
        N1["Client 提供 roots 信息 → Server 可查询"]
        N2["Client 支持 elicitation → Server 可提问"]
        N3["Server 提供 tools → Client 可调用"]
        N4["Server 支持 logging → Client 可设置级别"]
    end

    CR --> N1
    CE --> N2
    ST --> N3
    SL --> N4

    style CR fill:#3b82f6,color:#fff,stroke:none
    style CE fill:#3b82f6,color:#fff,stroke:none
    style CS fill:#3b82f6,color:#fff,stroke:none
    style ST fill:#10b981,color:#fff,stroke:none
    style SR fill:#10b981,color:#fff,stroke:none
    style SP fill:#10b981,color:#fff,stroke:none
    style SL fill:#10b981,color:#fff,stroke:none
    style SC fill:#10b981,color:#fff,stroke:none

21.2.3 在 MCP 中的体现

这个模式贯穿全书多个章节:

  • 第 4 章(生命周期)initialize 请求和响应中的 capabilities 字段是整个协商的起点
  • 第 5 章(Tools):Server 声明 tools 能力后,Client 才能调用 tools/listtools/call
  • 第 6 章(Resources)resources.subscribe 是可选的子能力,Client 只在 Server 声明时才订阅
  • 第 17 章(Sampling):Server 必须检查 Client 是否声明了 sampling 能力
  • 第 18 章(Elicitation):精细到区分 formurl 两种子模式的声明

SDK 中 enforceStrictCapabilities 选项控制是否严格执行能力约束——开发阶段可以关闭以便调试,生产环境必须开启。

21.2.4 能力协商的三层嵌套

MCP 的能力协商不止一层,而是能力 → 子能力 → 参数的三层嵌套:

{
  "capabilities": {
    "elicitation": {       // 第一层:顶层能力
      "form": {},          // 第二层:子模式
      "url": {             // 第二层:另一个子模式
        "timeout": 600     // 第三层:子模式的参数
      }
    }
  }
}

这种嵌套让协议能力粒度极细——同一个顶层能力下,可以逐步添加子特性而不破坏协商。

21.2.5 可迁移的洞察

能力协商模式适用于任何需要渐进式兼容的系统。它的核心智慧是:不要假设对方的能力,问了再用。这比版本号更灵活——版本号是线性的,能力集合是多维的。

对比可见的同类实现:

系统能力协商机制
HTTP/2SETTINGS 帧:窗口大小、头表大小、最大并发流
TLS 1.3ClientHello 的 supported_versions / signature_algorithms 扩展
WebSocketSec-WebSocket-Protocol、Sec-WebSocket-Extensions
LSP(语言服务器)initialize 响应中的 serverCapabilities
OAuth 2.0 Metadata Discovery.well-known/oauth-authorization-server 中的 grant_types_supported
gRPCDeadline、Metadata、Compression 多层协商
MCPinitializecapabilities 字段(三层嵌套)

能看到,几乎所有成功的协议都选择了能力协商而非硬版本号。这不是偶然

21.3 模式二:传输抽象(Transport Abstraction)

21.3.1 解决的张力

协议逻辑应该与通信方式解耦(同样的 Tool 定义,不应因为从 stdio 切换到 HTTP 而改变),但不同部署场景需要不同的传输特性(本地进程用 stdio 更简单,远程服务需要 HTTP 的鉴权和重连)。

21.3.2 模式描述

MCP 定义了一个极简的 Transport 接口——只有发送消息、接收消息、关闭连接三个操作——然后在此之上构建了所有协议逻辑。具体的传输实现是可插拔的:

// TypeScript SDK 的 Transport 接口(简化)
interface Transport {
  start(): Promise<void>;
  send(message: JSONRPCMessage): Promise<void>;
  close(): Promise<void>;
  onmessage?: (message: JSONRPCMessage) => void;
  onclose?: () => void;
  onerror?: (error: Error) => void;
}

任何实现了这个接口的对象都可以作为传输层——这意味着你可以在 Electron IPC、gRPC、甚至蓝牙上运行 MCP 协议,而无需修改任何上层代码。Protocol 类只调用 transport.send() 和监听 transport.onmessage完全不知道消息是通过 stdin 还是 HTTP 传输的

21.3.3 在 MCP 中的体现

  • 第 12 章(stdio):最简单的传输,一行命令启动 Server,通过标准输入输出通信
  • 第 13 章(Streamable HTTP):支持无状态部署、会话管理、SSE 推送的现代 HTTP 传输
  • 第 14 章(SSE + WebSocket):补充传输方式,各有适用场景
  • 第 20 章(构建 Server):同一套 Server 代码通过切换传输层支持本地和远程两种部署模式

21.3.4 传输特性差异矩阵

各传输层的实际差异:

维度stdioStreamable HTTPSSEWebSocket
部署形态本地子进程远程 HTTP 服务远程 HTTP 服务远程 HTTP 服务
启动成本进程 fork(~10ms)TCP+TLS 握手(~100ms)同 HTTP同 HTTP
会话维持进程生命周期显式 session单连接单连接
Server → Client 推送原生(stdout)SSE 子通道原生原生
扩展性单实例可水平扩展(配合会话存储)较差(每连接绑实例)较差
鉴权进程继承环境OAuth 2.1同 HTTP同 HTTP
调试工具简单(终端)成熟(curl/Postman)浏览器 DevTools专用工具
典型延迟<1ms10-100ms10-100ms10-100ms

同一套 Server 代码可以对接这四种传输,只要传输实现了 Transport 接口。这是分层纪律的胜利。

21.3.5 可迁移的洞察

传输抽象的核心启示是分层的纪律:协议层只依赖抽象的消息收发接口,永远不引用具体的传输细节。这需要在设计之初就明确分层边界,之后严格执行。

一旦协议层”泄露”了对特定传输的假设(比如依赖 HTTP 的 Header 机制),抽象就会被破坏。LSP(Language Server Protocol)采用了同样的设计,这不是巧合——MCP 的设计者明确将 LSP 视为先驱。

反面教训:SMB/CIFS 协议早期紧耦合于 NetBIOS over TCP,导致后来想在现代 TCP/IP 网络上直接跑时,要额外搞一层 NBT(NetBIOS over TCP/IP)适配层——至今仍在背这个包袱。MCP 从第一天就定义清楚 Transport 接口,避免了这种”历史债务”。

21.4 模式三:三原语模型(Three-Primitive Model)

21.4.1 解决的张力

AI Agent 需要灵活地与外部世界交互(读数据、执行操作、获取指导),但交互模式必须标准化(否则每个 Server 都自创接口,生态无法形成)。

更深层的张力是:同样是”获取数据”,由谁决定何时获取,安全模型完全不同

21.4.2 模式描述

MCP 将 Server 能提供的一切抽象为三种原语,区分标准不是数据类型,而是谁控制何时使用

原语隐喻控制方典型交互安全模型
Tools手(执行能力)LLM 决定调用tools/call需要权限检查 + 用户确认
Resources眼(感知能力)应用程序控制resources/read应用已经决定加载,不需额外权限
Prompts脑(知识模板)用户选择prompts/get用户主动触发,最高信任

控制方的不同直接决定了安全模型:

  • Tools 需要权限检查——LLM 的决策是概率性的,可能误调用
  • Resources 不需要额外权限——应用程序自己选择加载什么
  • Prompts 需要用户确认——用户主动触发的操作

21.4.3 在 MCP 中的体现

  • 第 5 章(Tools):工具是 MCP 最活跃的原语,从简单的 API 调用到复杂的多步操作
  • 第 6 章(Resources):资源提供了声明式的数据访问,支持订阅和变更通知
  • 第 7 章(Prompts):提示模板定义了可复用的对话结构,引导 LLM 的行为
  • 第 20 章(构建 Server):实战中三者的协同——Prompt 引导 LLM,LLM 决定调用 Tool,Tool 执行中读取 Resource

21.4.4 三原语的”否”问题

经常有人问:“为什么不能只要 Tool,不要 Resource 和 Prompt?”

从纯功能上说确实可以——Resource 可以用”只读 Tool”实现,Prompt 可以用”带参数的 Tool”实现。但这样做会破坏三件事:

  1. 安全模型统一化:如果一切都是 Tool,Client 必须对每次调用都弹用户确认,体验崩坏
  2. LLM 的选择效率:LLM 在数十个 Tool 里选哪个调用已经很困难,再混入数百个”只读 Tool”会把选择空间炸穿
  3. 语义透明度:Client UI 无法再给用户展示”这个 Server 提供 3 个只读资源 + 5 个可调用工具”这样的清晰视图

所以三原语的存在不是”冗余”,而是把控制权维度显式化。不区分它们,代价不仅在协议层,更在用户体验和 LLM 使用效率层。

21.4.5 可迁移的洞察

三原语模型的深层智慧是按控制权分类,而非按数据类型分类。在设计任何 Agent 交互框架时,区分”谁决定何时使用”比区分”这是什么类型的数据”更有架构价值。数据结构可以相同(三个原语都返回 content 数组),但控制语义的差异决定了完全不同的安全和交互模型。

这个思路可以推广到所有需要”受控暴露能力”的系统

  • 浏览器:Permission(用户授权)、Capability(安全上下文)、API(开发者控制)三层
  • 移动 OS:前台 Activity(用户控制)、Service(应用控制)、ContentProvider(跨应用约定)三类组件
  • 数据库:DDL(DBA 控制)、DML(应用控制)、DQL(查询方发起)三层

“按控制权而非数据类型分层”是一种成熟的架构品味。

21.5 模式四:渐进式特性暴露(Progressive Feature Disclosure)

21.5.1 解决的张力

协议功能丰富(Elicitation、Sampling、Progress、Completion 等),但入门门槛应该低(一个最简单的 Server 应该几十行代码就能写出来)。

21.5.2 模式描述

MCP 的每个特性都是可选的。一个 Server 可以只暴露一个 Tool,不支持 Resources、不支持 Prompts、不支持 Completion、不支持 Logging——它仍然是一个完全合法的 MCP Server。随着需求增长,开发者可以逐步添加更多特性:

graph TB
    L1["Level 1: 最小可用<br/>一个 Tool + stdio 传输"] --> L2["Level 2: 丰富交互<br/>+ Resources + Prompts"]
    L2 --> L3["Level 3: 智能补全<br/>+ Completion + Tool Annotations"]
    L3 --> L4["Level 4: 操作反馈<br/>+ Progress + Logging"]
    L4 --> L5["Level 5: 反向通道<br/>+ Elicitation + Sampling"]
    L5 --> L6["Level 6: 远程部署<br/>+ HTTP 传输 + OAuth"]

    style L1 fill:#10b981,color:#fff,stroke:none
    style L2 fill:#3b82f6,color:#fff,stroke:none
    style L3 fill:#6366f1,color:#fff,stroke:none
    style L4 fill:#8b5cf6,color:#fff,stroke:none
    style L5 fill:#ec4899,color:#fff,stroke:none
    style L6 fill:#f59e0b,color:#fff,stroke:none

每一级都是可生产的——你不必一路爬到顶再发布。实际上绝大多数生产 MCP Server 停留在 Level 2-3 就已经很有价值。

21.5.3 在 MCP 中的体现

  • 第 8 章(TypeScript Server SDK)McpServer 高级 API 让创建基本 Server 只需十几行代码
  • 第 10 章(Python Server SDK)@server.tool() 装饰器让工具定义简洁到极致
  • 第 18 章(Elicitation/Roots):这些高级特性只在 Server 需要时才涉及
  • 第 20 章(构建 Server):从最简开始,逐步添加 Completion、Progress 等特性

21.5.4 渐进式暴露 vs 钩子膨胀

有一个反模式叫**“钩子膨胀”**:框架为了”灵活”,暴露了几十个 Hook(before/after/aroundXYZ),开发者被迫理解全部钩子才能写一个最简单的 Server。早期的 Enterprise Java、某些重型 IoC 容器都患有这个病。

渐进式特性暴露的正确姿势

  1. 有一个”最小心智模型”:10 分钟就能读完文档入门
  2. 高级特性在自己的文档章节:不看不影响用
  3. API 的默认行为即合理行为:不用配置就能跑
  4. 每个特性独立可开关:不产生横向耦合

MCP 做到了这四点。对比一下:SOAP 协议把所有特性堆在一个 WSDL 里,写一个 Hello World 都要 200 行 XML——这就是渐进式暴露的反例。

21.5.5 可迁移的洞察

渐进式暴露的关键是正交性——每个特性应该独立于其他特性工作。如果添加 Completion 支持要求同时实现 Resources,那就不是渐进式的。MCP 做到了几乎完全的正交性:任何特性组合都是合法的。

这对协议和框架设计的启示是:在添加新特性时,始终问**“这个特性是否可以独立于其他所有特性使用?”** 如果答案是否,就要重新设计依赖关系。

21.6 模式五:安全即默认(Security by Default)

21.6.1 解决的张力

开放性是生态繁荣的前提(任何人都能写 Server),但安全是用户信任的基础(恶意或有缺陷的 Server 不应该损害用户)。

21.6.2 模式描述

MCP 在协议层面内建了多层安全机制,而不是把安全作为”可选的最佳实践”留给实现者:

  1. 人在回路(Human-in-the-loop):工具调用默认需要用户确认,Elicitation URL 需要用户同意
  2. 最小权限原则:能力协商确保只有声明支持的特性才会被使用
  3. 安全分级:Form Mode 禁止收集密码和 API 密钥,URL Mode 确保敏感数据不经过 Client
  4. 传输安全:远程部署强制 OAuth 2.1 + PKCE,Host Header 验证防止 DNS 重绑定
  5. 信息隔离:Server 默认看不到对话历史,不能访问其他 Server 的数据
  6. 环境隔离:stdio 传输默认只继承白名单环境变量,防止凭证泄露

21.6.3 在 MCP 中的体现

  • 第 5 章(Tools):Tool Annotations(readOnlyHintdestructiveHint)帮助 Client 做权限决策
  • 第 15 章(OAuth):MCP 授权规范基于 OAuth 2.1,强制 PKCE,禁止隐式授权流
  • 第 18 章(Elicitation):Form Mode 禁止收集敏感信息,URL Mode 必须在安全浏览器中打开
  • 第 19 章(Claude Code):MCP 工具与内置工具冲突时内置优先,防止第三方覆盖核心功能

21.6.4 “默认安全”的四个等级

根据”让用户做正确的事有多容易”的不同,安全机制可以分四级:

等级名字特征MCP 中的例子
L1不安全是默认需要配置才能安全(MCP 没有这种设计)
L2安全是可选默认不安全,文档提醒你开(MCP 没有这种设计)
L3安全是默认默认安全,可以手动关闭stdio 环境变量白名单
L4安全不可关协议强制,无法绕过URL Mode 禁止预认证 URL

MCP 把安全机制大多数放在 L3-L4——默认就是安全的,且绕过它要付出代价

21.6.5 可迁移的洞察

安全即默认意味着不安全的行为应该比安全的行为更难实现。在 MCP 中,发送敏感数据通过 Form Mode 是被规范禁止的,你必须使用更复杂但更安全的 URL Mode——增加的摩擦是有意为之的。

这个理念可以这样概括:让做正确的事比做错误的事更容易(Make the right thing easy, and the wrong thing hard)。

现实中同类思想:

  • Rust 的借用检查器——默认安全,需要 unsafe 才能突破
  • HTTPS Everywhere——浏览器默认 HTTPS,HTTP 要点”继续”
  • CSP(Content Security Policy)——严格模式下内联脚本默认禁止
  • iOS 沙箱——App 默认只能访问自己的数据,跨 App 访问要走 Intent

这些都是”安全即默认”的模式实例。

21.7 模式六:协议版本策略(Protocol Versioning Strategy)

21.7.1 解决的张力

协议需要演进(新功能、修复设计缺陷),但已部署的实现不能被破坏(百万级 Client 和 Server 不可能同时升级)。

21.7.2 模式描述

MCP 使用日期格式的版本标识(如 2025-06-182025-11-25),而非语义化版本号。初始化时 Client 提出支持的版本,Server 响应实际使用的版本。如果不兼容,连接直接失败——这是有意的设计:宁可连接失败,也不要在不兼容的状态下运行

21.7.3 日期版本 vs SemVer 的对比

SemVer(如 1.2.3)日期版本(如 2025-11-25)
可比性需要约定比较规则天然按时间序
”补丁”概念无(每版本都是完整规范)
什么叫 Breaking主版本+1直接发新日期
争论焦点”这算 major 还是 minor”无——所有变更都是新版本
实现同步需要 major 严格锁步能力协商解决兼容
对开发者直观度需要理解 major/minor直接看”2025 年 6 月版”

MCP 协议没有”补丁”的概念——每个版本都是完整规范。这决定了日期版本比 SemVer 更合适。

21.7.4 与能力协商的配合

版本号 + 能力协商构成了双重兼容性策略

  • 版本号处理宏观的协议演进(消息格式变化、新方法添加)
  • 能力协商处理微观的特性差异(同一版本内,不同实现支持不同特性)

这就像 HTTP 版本(1.1、2、3)与 HTTP Headers 的关系:版本决定了基本的通信规则,Headers 协商具体的行为。两个层次各司其职,互不干扰。

21.8 模式间的协同

这六个模式不是孤立的。它们形成了一个互相强化的系统:

graph TB
    CN["能力协商"] --> |"决定可用特性"| PFD["渐进式暴露"]
    CN --> |"确保安全特性生效"| SBD["安全即默认"]
    TA["传输抽象"] --> |"任何传输上工作"| TPM["三原语模型"]
    TA --> |"HTTP 传输启用"| SBD
    TPM --> |"按控制权分层"| PFD
    PFD --> |"每层独立演进"| PVS["版本策略"]
    SBD --> |"安全约束纳入规范"| PVS
    PVS --> |"新版本添加新能力"| CN

    style CN fill:#3b82f6,color:#fff,stroke:none
    style TA fill:#8b5cf6,color:#fff,stroke:none
    style TPM fill:#ec4899,color:#fff,stroke:none
    style PFD fill:#10b981,color:#fff,stroke:none
    style SBD fill:#f59e0b,color:#fff,stroke:none
    style PVS fill:#6366f1,color:#fff,stroke:none

考虑一个具体的例子:当 MCP 在 2025-11-25 版本中引入 URL Mode Elicitation 时——

  • 版本策略让旧 Client 可以安全地拒绝新版本的连接
  • 能力协商让新 Client 可以声明是否支持 url 模式
  • 安全即默认要求敏感数据必须通过 URL Mode,不能走 Form Mode
  • 渐进式暴露确保不需要 URL Mode 的 Server 完全不受影响
  • 传输抽象保证这个特性在 stdio 和 HTTP 上都能工作
  • 三原语模型中,URL Elicitation 自然地嵌入到 Tool 执行流程中

六个模式像齿轮一样咬合,每个都让其他模式更有效。这就是成熟协议的标志——不是”有多少特性”,而是”特性之间有多融洽”。

21.9 MCP 的设计权衡

没有完美的设计。每个模式背后都有权衡,诚实地记录这些权衡是本章的责任。

21.9.1 能力协商的代价

初始化握手增加了首次连接的延迟。每个 Client 和 Server 都需要维护能力映射表。但这个代价换来了无需版本锁步升级的自由度——对于一个开放生态来说,这是值得的。

可量化的代价initialize 请求/响应的 RTT(stdio 上约 1ms,HTTP 上约 50-100ms),以及约 1KB 的能力声明数据。对于长连接场景可以忽略,对于冷启动场景可用预热或 keep-alive 缓解。

21.9.2 三原语的边界模糊

在实践中,Tool 和 Resource 的边界有时不清晰。“获取文件内容”是 Resource 还是 Tool?

MCP 规范的回答是看控制方——如果是应用程序自动加载上下文,用 Resource;如果是 LLM 决定需要获取,用 Tool。这个区分在概念上清晰,但在实现中需要开发者做出判断。

实践建议:

场景选 Resource选 Tool
数据集合固定、提前知道 URI
数据依赖 LLM 推理后才能确定 URI 或参数
Client 要展示”所有可用的 X”
操作有副作用(哪怕是读缓存也 mutate)

21.9.3 安全的摩擦

安全机制增加了开发复杂度。一个简单的 Server 如果需要收集 API 密钥,必须实现 URL Mode Elicitation 的完整流程——包括 HTTPS 页面、用户身份验证、回调处理。对于小型项目来说,这可能显得过重。但协议设计者做出了明确的取舍:宁可让少数开发者多写代码,也不让多数用户面临安全风险

这条”摩擦线”画在哪里,体现了协议作者的价值判断——MCP 明显站在用户一侧。

21.9.4 JSON-RPC 的局限

MCP 选择 JSON-RPC 2.0 作为基础,获得了简单性和广泛的语言支持,但也继承了它的限制:

  • 没有原生的流式传输——需要 SSE 补充
  • 没有二进制消息——大文件传输效率较低(base64 膨胀 33%)
  • 没有内建的请求多路复用——高并发下需要应用层管理

这些限制通过传输层的扩展来弥补,但增加了整体复杂度。如果重新设计,是否会选 Protocol Buffers + gRPC?社区讨论中有此声音,但主流意见是:JSON 的人类可读性 + 开箱即用的调试能力在 AI Agent 场景(本来就以文本为主)中胜过了性能考量。

21.10 全书架构决策总结

把前 20 章分散的设计决策汇总成一张表,作为本书的”架构决策卡片”:

决策选择放弃的方案理由相关章节
消息格式JSON-RPC 2.0REST, gRPC, 自定义简单、双向、人类可读第 3 章
版本号格式日期 (2025-11-25)semver避免兼容性歧义第 4 章
交互原语Tool/Resource/Prompt统一 Action 模型按控制权分类更精确第 5-7 章
本地传输stdioUnix Socket, Named Pipe跨平台、零配置第 12 章
远程传输Streamable HTTPREST+WebSocket单连接、渐进增强第 13 章
认证OAuth 2.1 + PKCEAPI Key, mTLS无需预存关系、支持作用域第 15 章
Schema 验证JSON Schema 2020-12TypeScript 类型、Protobuf语言无关、工具丰富第 5 章
Server 发现配置文件 + OAuth 发现DNS-SD, mDNS简单、显式、安全第 16 章
敏感信息收集URL Mode ElicitationForm Mode 或 Prompt 注入数据不经 Client/LLM第 18 章
反向请求关联绑定到当前正向请求独立异步通道传输层兼容性第 18 章
Token 存储Server 端会话绑定Client 传递防 Token 泄露到 LLM第 15 章
Sampling 鉴权用户确认每次Token/API Key防滥用 LLM 费用第 17 章

21.11 面向未来的思考

MCP 的设计模式揭示了几个面向未来的方向:

21.11.1 Agent-to-Agent 通信

当前 MCP 是 Client-Server 模型,但 AI Agent 生态正在走向 Agent 间直接协作。MCP 的传输抽象和能力协商机制可以自然地扩展到 Agent-to-Agent 场景——只要把”Agent”也视为一个 Peer,双方在 initialize 时协商对等能力即可。

已经出现的实验包括 Google A2A 协议、Anthropic 的 Agent SDK,它们或多或少借鉴了 MCP 的设计模式。

21.11.2 多模态原语

当前的三原语模型主要处理文本和简单的二进制数据。随着多模态 AI 的发展,视频流、实时音频、3D 场景等数据类型可能需要新的原语或现有原语的扩展

一个可能的演进方向是在三原语上增加”Stream”原语——专门处理连续的时序数据,但这会触碰到 JSON-RPC 本身的边界,可能需要传输层的深度修改。

21.11.3 去中心化发现

当前的 Server 发现依赖配置文件或中央注册表。未来可能出现基于 DNS-SD、mDNS 或区块链的去中心化发现机制——类似于 IPFS 对静态内容做的事,应用到 Agent Service Discovery 上。

21.11.4 长生命周期任务

MCP 的 Progress 和 Cancellation 机制为长操作提供了基本支持,但随着 Agent 执行越来越复杂的任务(几小时甚至几天),可能需要更完善的任务管理原语——MCP 规范中已出现的实验性 Tasks 特性就是这个方向的探索。

Tasks 特性的核心思路:把长操作从 “request-response” 模式升级为 “fire-poll-retrieve” 模式,类似于 AWS Step Functions 的异步执行语义。

21.12 全书总结

MCP 不只是一个协议规范——它是一套关于如何让 AI 系统安全、可靠、可扩展地与外部世界交互的设计思想。

从第 1 章到第 21 章,我们完成了一次完整的旅程:

  • 为什么:AI Agent 需要标准化的外部交互协议(第 1 章)
  • 是什么:基于 JSON-RPC 的 Client-Server 架构,三大原语,能力协商(第 2-7 章)
  • 怎么实现:TypeScript 和 Python SDK 的深度解读(第 8-11 章)
  • 怎么通信:从 stdio 到 Streamable HTTP,传输层的选择与权衡(第 12-14 章)
  • 怎么安全:OAuth 授权、Elicitation 安全隔离、工具注解(第 15、18 章)
  • 怎么发现:配置文件、注册表、动态发现(第 16 章)
  • 怎么协作:Sampling、Elicitation、Progress 等反向通道(第 17-18 章)
  • 怎么落地:Claude Code 的 12 万行实战经验(第 19 章)
  • 怎么构建:从零到生产级 Server 的完整开发流程(第 20 章)
  • 为什么这样:六大设计模式与架构决策(本章)

MCP 协议仍在快速演进。但本书覆盖的核心概念和设计模式是稳定的——它们不会随版本更新而失效,因为它们反映的是更深层的系统设计智慧。理解了这些模式,你不仅能用好 MCP,更能在面对下一个协议设计问题时,做出更好的架构决策。


模型是引擎,协议是道路。好的道路不限制车速,但确保每辆车都到达正确的目的地。

全书源码参考

  • MCP 规范:https://github.com/modelcontextprotocol/specification
  • TypeScript SDK:https://github.com/modelcontextprotocol/typescript-sdk
  • Python SDK:https://github.com/modelcontextprotocol/python-sdk

延伸阅读:协议演化的长河

协议是计算机科学里最古老的抽象之一——1969 年 ARPANET 的 NCP、1974 年 TCP/IP、1983 年 DNS、1991 年 HTTP、1997 年 TLS、2014 年 HTTP/2、2021 年 HTTP/3、2024 年 MCP——每一次协议的诞生,都是对某一类分布式协作问题的系统性回答。协议一旦被广泛采用,就会深刻塑造整个行业的形态。

观察这条演化长河,可以提炼几条恒久的设计原则。简单比复杂更持久——HTTP/0.9 几乎只有 GET 请求却活了三十年,ASN.1 设计得无所不包却几乎被弃用。可扩展比完美更重要——TCP 的保留字段后来承载了 ECN、MSS、Window Scaling;HTTP 的 Header 扩展能力催生出 WebDAV、CORS、HSTS。向后兼容是生死线——IPv6 到今天还没完全取代 IPv4,就是因为没法无痛升级;HTTP/2 能快速普及,就是因为升级路径透明。MCP 吸收了这些历史经验——简单的三原语、灵活的 capability 协商、严格的版本策略,这是它能被 Anthropic、OpenAI、Google 等厂商快速采纳的原因。

延伸阅读:协议 vs 框架——抽象层次的选择

AI 应用开发里,从低到高常见的抽象层次依次是 HTTP API(如 OpenAI Chat Completions)、协议(如 MCP)、框架(如 LangChain、LangGraph)、平台(如 Anthropic Console、OpenAI Platform)。HTTP API 最灵活但最重复;协议标准化了通信格式但不规定业务逻辑;框架提供业务抽象但绑定特定思路;平台提供端到端体验但锁定供应商。

MCP 选择”协议”这一层是刻意的抽象位置:既不像 HTTP API 那样要求每个开发者自己处理协议细节,也不像框架那样强加某种编程范式,更不像平台那样锁定到某家云服务。协议层让生态参与者(LLM 提供商、工具开发者、Agent 框架)能各自发挥又互相协作。《LangGraph 源码》第 1 章讨论过类似的”低层基础设施”定位,《OpenClaw 源码》第 9 章讨论的”可扩展的插件协议”也是同一思路。

延伸阅读:能力协商模式的普适性

MCP 的能力协商——客户端和服务端在建立连接时交换 capability 列表、只使用双方都支持的能力——在协议世界广泛出现:TLS 的 cipher suite 协商、HTTP 的 Content-Negotiation、GraphQL 的 schema introspection、TCP 的 options 字段都是同一思路。核心价值是”让新旧实现共存”——新特性被 capability 标记,老客户端不理解就跳过。

能力协商的反面是”版本硬切换”——所有客户端和服务端必须同时升级到新版本,否则无法通信。IPv4 → IPv6 就是硬切换的典型教训,因为无法渐进升级,花了 30 年才逐步替换。任何需要长期演化的系统,都应该优先采用能力协商而非版本硬切换。