2.4 SDK 的模块分层与依赖方向


2.4 SDK 的模块分层与依赖方向

本节摘要:上一节看了服务端内部的两层模型,本节把视角拉高,俯瞰整个 SDK 的模块分层。SDK 不是一个扁平的包,而是按「协议类型 → 共享工具 → 服务端核心 / 客户端核心」分层组织,依赖方向严格自下而上。理解这个分层,你就能解释几个乍看奇怪的设计:为什么协议类型是个独立的包?为什么客户端和服务端能共用同一份类型?为什么改动协议类型要同时影响两端?本节用一张分层图与依赖铁律,把这些讲透。

一、四大模块分层

SDK 的代码按职责分成四层,从下到上依赖:

┌─────────────────────────────────────────────┐ │ 应用层(你的代码) │ ← 你写的服务端/客户端 ├─────────────────────────────────────────────┤ │ 服务端核心模块 客户端核心模块 │ ← 两端各自的高层 API │ (MCPServer/Server、 (Client、 │ │ 传输、能力声明、 ClientSession、 │ │ 上下文、认证等) 会话组、认证等) │ ├─────────────────────────────────────────────┤ │ 共享工具模块 │ ← 两端共用的基础设施 │ (分发器、URI 模板、异常、 │ │ 传输抽象、订阅、可观测性) │ ├─────────────────────────────────────────────┤ │ 类型包(mcp-types) │ ← 协议类型定义 │ (JSON-RPC、方法枚举、 │ │ 协议版本、各修订的类型) │ └─────────────────────────────────────────────┘ 依赖方向:严格自下而上(禁止反向)

逐层看每层的职责:

内容 职责
类型包 协议类型、JSON-RPC、方法枚举、协议版本 定义「协议说什么」,纯数据,无业务
共享工具模块 分发器、URI 模板、传输抽象、异常、订阅 两端共用的基础设施
服务端核心 MCPServer/Server、传输实现、能力声明、上下文、认证 服务端的高层 API
客户端核心 Client/ClientSession、传输实现、会话组、认证 客户端的高层 API

二、为什么协议类型独立成包

这是分层里最值得讲的一个决策。协议类型(mcp-types)被拆成了独立的包,而不是藏在 mcp 主包里。为什么?

答案是两端必须共用同一份类型定义。考虑一个工具调用:客户端发出的请求、服务端收到的请求,必须是同一个类型;否则就会出现「客户端按自己的类型构造,服务端按另一个类型解析」的割裂。

mcp-types(独立包) ┌──────────────┐ │ 工具调用请求 │ │ 工具调用响应 │ │ 能力声明对象 │ │ ...所有协议类型│ └──────┬───────┘ ┌──────────┴──────────┐ ▼ ▼ 客户端核心 服务端核心 (引用同一份类型) (引用同一份类型)

把类型独立成包,带来三个好处:

好处 说明
类型一致 两端引用同一份定义,永远不会割裂
版本同步 协议类型有版本(对应协议修订),独立包便于锁版本
关注点分离 「协议说什么」与「怎么实现」分开,各自演进

这也是为什么第 1.1 节装 SDK 时,你会看到 mcp-types 作为独立依赖被拉进来——它是 SDK 的地基,两端都站在它上面。

三、依赖铁律:自下而上,禁止反向

整张分层图的核心约束是一条铁律:

依赖方向严格自下而上,禁止反向。

意思是:

可以依赖
类型包 (无,它是最底层)
共享工具模块 类型包
服务端/客户端核心 共享工具模块 + 类型包
应用层(你的代码) 上面任意层

禁止反向意味着:类型包不能引用服务端核心的任何东西(否则循环依赖);共享工具模块不能引用服务端/客户端核心的特定逻辑。

⚠️ 注意:这条铁律不是文档约定,而是用代码结构强制的。如果你在写扩展或自定义实现时违反它(比如让类型包引用了服务端的某个类),会在导入时直接报循环依赖错误。这是 SDK 用工程手段守住的设计原则。

四、共享工具模块:两端共用的基础设施

「共享工具模块」这一层容易被忽略,但它解释了 SDK 的很多能力。两端都需要的「跨切关注点」都放在这里:

模块 干什么 两端怎么用
分发器(Dispatcher) 把进来的请求分发到对应处理器 服务端分发请求、客户端分发响应
URI 模板 解析 greeting://{name} 这类 URI 服务端注册资源模板、客户端构造读取请求
传输抽象 定义 Transport 协议 两端都基于它实现各自传输
异常 MCPError 等协议级异常 两端共用同一套错误模型
订阅(Subscription) 资源变更通知的发布订阅 服务端发布、客户端订阅
可观测性 OpenTelemetry 钩子 两端都产生追踪数据

把跨切关注点放共享层,避免了两端各写一份的重复——这是「DRY(Don't Repeat Yourself)」在分层架构里的体现。

五、服务端与客户端核心:对称但不耦合

注意分层图里,服务端核心与客户端核心是并列的两层,互不依赖:

服务端核心 ◄──不依赖──► 客户端核心 │ │ └──── 都依赖共享层 + 类型包 ────┘

这个对称设计有两个好处:

  1. 两端可独立演进——服务端加新能力,不需要改客户端核心。
  2. 两端可独立使用——你只用服务端 API 写服务,不需要装客户端;反之亦然(虽然 pip 装的话都会装上,但代码上独立)。

它们之间的「对话」通过协议类型完成——服务端产出协议响应、客户端消费协议响应,中间不需要直接代码依赖。这与第 2.1 节讲的「服务端从不直接和模型对话」是一脉相承的设计哲学:两端通过协议对话,不通过代码耦合

六、本节的实践意义

你可能会问:了解分层对我写代码有什么用?至少三点:

  1. 遇到 import 困惑时知道去哪找——协议类型去 mcp.types(或 mcp_types),服务端 API 去 mcp.server,客户端 API 去 mcp.client(或顶层 mcp)。
  2. 写自定义扩展时不违反依赖方向——你的扩展可以依赖任何上层,但不能让底层依赖你的扩展。
  3. 理解版本兼容——协议类型有版本,服务端/客户端核心跟着协议类型锁版本,这就是为什么客户端连服务端要做「协议版本协商」(第 9 章)。

本节要点回顾

  1. SDK 分四层:类型包 → 共享工具模块 → 服务端核心 / 客户端核心 → 应用层。
  2. 协议类型独立成包,因为两端必须共用同一份类型定义,避免割裂。
  3. 依赖铁律:自下而上,禁止反向,用代码结构强制,违反会报循环依赖。
  4. 共享工具模块放跨切关注点(分发器、URI 模板、传输抽象、异常、订阅),两端共用。
  5. 服务端与客户端核心对称但不耦合,通过协议类型对话,不通过代码依赖。
  6. 理解分层帮你:找对 import、不违反依赖、理解版本协商。

分层清楚了,最后一节我们用一次完整的「工具调用」旅程,把本章所有概念串起来。


作者与出处
原作者: 灏天文库
来源:modelcontextprotocol
许可证:MIT
整理: 灏天文库整理
由灏天文库结构化整理,提供目录导航、全文检索与在线阅读,便于系统化学习
发布者: 作者: 灏天文库 转发
评论区 (0)
U