Appearance
第21章 设计模式与架构决策
"Patterns are not invented, they are discovered — in the recurring decisions that successful systems make when facing the same fundamental tensions."
本章要点
- 从全书内容中提炼六大核心设计模式
- 理解每个模式解决的根本性张力(tension)
- 掌握模式间的协同关系与互相强化
- 认识 MCP 设计决策对未来协议演进的约束与启示
- 建立可迁移到其他系统设计的架构决策框架
21.1 模式的发现
回顾全书 21 章的旅程——从 MCP 的诞生动机(第 1 章),到架构全景(第 2 章),到 JSON-RPC 基础(第 3 章),到生命周期管理(第 4 章),到三大原语的深入分析(第 5-7 章),到两大 SDK 的实现解读(第 8-11 章),到传输层的多重选择(第 12-14 章),到安全与授权(第 15 章),到发现与注册(第 16 章),到反向通道与配置管理(第 17-18 章),到真实产品中的落地(第 19 章),再到亲手构建 Server(第 20 章)——我们积累了足够的材料来做一件更深层的事:识别贯穿整个协议的设计模式。
这些模式不是凭空创造的规则,而是从源码、规范和实践中反复出现的决策逻辑中提炼出来的。它们回答的不是"MCP 做了什么",而是"MCP 为什么这样做"。
21.2 模式一:能力协商(Capability Negotiation)
21.2.1 解决的张力
协议需要稳定(Client 和 Server 可能由不同团队在不同时间开发),但功能需要演进(新特性不断被添加)。如何让新旧版本共存?
21.2.2 模式描述
MCP 的初始化握手不仅是"我是谁"的自我介绍,更是"我能做什么"的能力宣告。Client 和 Server 各自声明自己支持的特性集合,双方在交集内工作:
21.2.3 在 MCP 中的体现
这个模式贯穿全书多个章节:
- 第 4 章(生命周期):
initialize请求和响应中的capabilities字段是整个协商的起点 - 第 5 章(Tools):Server 声明
tools能力后,Client 才能调用tools/list和tools/call - 第 6 章(Resources):
resources.subscribe是可选的子能力,Client 只在 Server 声明时才订阅 - 第 17 章(Sampling):Server 必须检查 Client 是否声明了
sampling能力 - 第 18 章(Elicitation):精细到区分
form和url两种子模式的声明
SDK 中 enforceStrictCapabilities 选项控制是否严格执行能力约束——开发阶段可以关闭以便调试,生产环境必须开启。
21.2.4 可迁移的洞察
能力协商模式适用于任何需要渐进式兼容的系统。它的核心智慧是:不要假设对方的能力,问了再用。这比版本号更灵活——版本号是线性的,能力集合是多维的。一个 Server 可以支持 Tools 但不支持 Resources,这种组合用版本号无法表达。HTTP/2 的 SETTINGS 帧、TLS 的密码套件协商、WebSocket 的子协议选择,都是同一个模式的不同实例。
21.3 模式二:传输抽象(Transport Abstraction)
21.3.1 解决的张力
协议逻辑应该与通信方式解耦(同样的 Tool 定义,不应因为从 stdio 切换到 HTTP 而改变),但不同部署场景需要不同的传输特性(本地进程用 stdio 更简单,远程服务需要 HTTP 的鉴权和重连)。
21.3.2 模式描述
MCP 定义了一个极简的 Transport 接口——只有发送消息、接收消息、关闭连接三个操作——然后在此之上构建了所有协议逻辑。具体的传输实现是可插拔的:
typescript
// 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 可迁移的洞察
传输抽象的核心启示是分层的纪律:协议层只依赖抽象的消息收发接口,永远不引用具体的传输细节。这需要在设计之初就明确分层边界,之后严格执行。一旦协议层"泄露"了对特定传输的假设(比如依赖 HTTP 的 Header 机制),抽象就会被破坏。LSP(Language Server Protocol)也采用了同样的设计,这不是巧合——MCP 的设计者明确将 LSP 视为先驱。
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 可迁移的洞察
三原语模型的深层智慧是按控制权分类,而非按数据类型分类。在设计任何 Agent 交互框架时,区分"谁决定何时使用"比区分"这是什么类型的数据"更有架构价值。数据结构可以相同(三个原语都返回 content 数组),但控制语义的差异决定了完全不同的安全和交互模型。
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。随着需求增长,开发者可以逐步添加更多特性:
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 可迁移的洞察
渐进式暴露的关键是正交性——每个特性应该独立于其他特性工作。如果添加 Completion 支持要求同时实现 Resources,那就不是渐进式的。MCP 做到了几乎完全的正交性:任何特性组合都是合法的。这对协议设计的启示是:在添加新特性时,始终问"这个特性是否可以独立于其他所有特性使用?"
21.6 模式五:安全即默认(Security by Default)
21.6.1 解决的张力
开放性是生态繁荣的前提(任何人都能写 Server),但安全是用户信任的基础(恶意或有缺陷的 Server 不应该损害用户)。
21.6.2 模式描述
MCP 在协议层面内建了多层安全机制,而不是把安全作为"可选的最佳实践"留给实现者:
- 人在回路(Human-in-the-loop):工具调用默认需要用户确认,Elicitation URL 需要用户同意
- 最小权限原则:能力协商确保只有声明支持的特性才会被使用
- 安全分级:Form Mode 禁止收集密码和 API 密钥,URL Mode 确保敏感数据不经过 Client
- 传输安全:远程部署强制 OAuth 2.1 + PKCE,Host Header 验证防止 DNS 重绑定
- 信息隔离:Server 默认看不到对话历史,不能访问其他 Server 的数据
- 环境隔离:stdio 传输默认只继承白名单环境变量,防止凭证泄露
21.6.3 在 MCP 中的体现
- 第 5 章(Tools):Tool Annotations(
readOnlyHint、destructiveHint)帮助 Client 做权限决策 - 第 15 章(OAuth):MCP 授权规范基于 OAuth 2.1,强制 PKCE,禁止隐式授权流
- 第 18 章(Elicitation):Form Mode 禁止收集敏感信息,URL Mode 必须在安全浏览器中打开
- 第 19 章(Claude Code):MCP 工具与内置工具冲突时内置优先,防止第三方覆盖核心功能
21.6.4 可迁移的洞察
安全即默认意味着不安全的行为应该比安全的行为更难实现。在 MCP 中,发送敏感数据通过 Form Mode 是被规范禁止的,你必须使用更复杂但更安全的 URL Mode——增加的摩擦是有意为之的。这个理念可以这样概括:让做正确的事比做错误的事更容易。
21.7 模式六:协议版本策略(Protocol Versioning Strategy)
21.7.1 解决的张力
协议需要演进(新功能、修复设计缺陷),但已部署的实现不能被破坏(百万级 Client 和 Server 不可能同时升级)。
21.7.2 模式描述
MCP 使用日期格式的版本标识(如 2025-06-18、2025-11-25),而非语义化版本号。初始化时 Client 提出支持的版本,Server 响应实际使用的版本。如果不兼容,连接直接失败——这是有意的设计:宁可连接失败,也不要在不兼容的状态下运行。
21.7.3 日期版本的哲学
日期版本比语义化版本更适合协议:
- 协议没有"补丁"的概念——每个版本都是完整的规范
- 日期天然有序,不需要比较主版本号和次版本号
- 避免了"这个 breaking change 是 major 还是 minor"的无休止争论
- 每个版本号就是一个明确的时间快照,对开发者更直观
21.7.4 与能力协商的配合
版本号 + 能力协商构成了双重兼容性策略:
- 版本号处理宏观的协议演进(消息格式变化、新方法添加)
- 能力协商处理微观的特性差异(同一版本内,不同实现支持不同特性)
这就像 HTTP 版本(1.1、2、3)与 HTTP Headers 的关系:版本决定了基本的通信规则,Headers 协商具体的行为。两个层次各司其职,互不干扰。
21.8 模式间的协同
这六个模式不是孤立的。它们形成了一个互相强化的系统:
考虑一个具体的例子:当 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 都需要维护能力映射表。但这个代价换来了无需版本锁步升级的自由度——对于一个开放生态来说,这是值得的。
21.9.2 三原语的边界模糊
在实践中,Tool 和 Resource 的边界有时不清晰。"获取文件内容"是 Resource 还是 Tool?MCP 规范的回答是看控制方——如果是应用程序自动加载上下文,用 Resource;如果是 LLM 决定需要获取,用 Tool。这个区分在概念上清晰,但在实现中需要开发者做出判断。
21.9.3 安全的摩擦
安全机制增加了开发复杂度。一个简单的 Server 如果需要收集 API 密钥,必须实现 URL Mode Elicitation 的完整流程——包括 HTTPS 页面、用户身份验证、回调处理。对于小型项目来说,这可能显得过重。但协议设计者做出了明确的取舍:宁可让少数开发者多写代码,也不让多数用户面临安全风险。
21.9.4 JSON-RPC 的局限
MCP 选择 JSON-RPC 2.0 作为基础,获得了简单性和广泛的语言支持,但也继承了它的限制:没有原生的流式传输(需要 SSE 补充)、没有二进制消息(大文件传输效率较低)、没有内建的请求多路复用。这些限制通过传输层的扩展来弥补,但增加了整体复杂度。
21.10 全书架构决策总结
| 决策 | 选择 | 放弃的方案 | 理由 | 相关章节 |
|---|---|---|---|---|
| 消息格式 | JSON-RPC 2.0 | REST, gRPC, 自定义 | 简单、双向、人类可读 | 第 3 章 |
| 版本号格式 | 日期 (2025-11-25) | semver | 避免兼容性歧义 | 第 4 章 |
| 交互原语 | Tool/Resource/Prompt | 统一 Action 模型 | 按控制权分类更精确 | 第 5-7 章 |
| 本地传输 | stdio | Unix Socket, Named Pipe | 跨平台、零配置 | 第 12 章 |
| 远程传输 | Streamable HTTP | REST+WebSocket | 单连接、渐进增强 | 第 13 章 |
| 认证 | OAuth 2.1 + PKCE | API Key, mTLS | 无需预存关系、支持作用域 | 第 15 章 |
| Schema 验证 | JSON Schema 2020-12 | TypeScript 类型、Protobuf | 语言无关、工具丰富 | 第 5 章 |
| Server 发现 | 配置文件 + OAuth 发现 | DNS-SD, mDNS | 简单、显式、安全 | 第 16 章 |
21.11 面向未来的思考
MCP 的设计模式揭示了几个面向未来的方向:
Agent-to-Agent 通信。当前 MCP 是 Client-Server 模型,但 AI Agent 生态正在走向 Agent 间直接协作。MCP 的传输抽象和能力协商机制可以自然地扩展到 Agent-to-Agent 场景。
多模态原语。当前的三原语模型主要处理文本和简单的二进制数据。随着多模态 AI 的发展,视频流、实时音频、3D 场景等数据类型可能需要新的原语或现有原语的扩展。
去中心化发现。当前的 Server 发现依赖配置文件或中央注册表。未来可能出现基于 DNS-SD、mDNS 或区块链的去中心化发现机制。
长生命周期任务。MCP 的 Progress 和 Cancellation 机制为长操作提供了基本支持,但随着 Agent 执行越来越复杂的任务(几小时甚至几天),可能需要更完善的任务管理原语——MCP 规范中已出现的实验性 Tasks 特性就是这个方向的探索。
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