9.4 客户端能力声明、第一类 Client 与 ClientSession 本节摘要:第 9 章收尾节,讲三个收尾性主题。一是客户端能力声明——客户端也声明能力,告诉服务端「我支持采样、根、引导填写」等反向能力,这套双向声明影响服务端行为。二是第一类 与低层 的关系—— 是 之上的便捷封装,日常用它够,需要细粒度控制才下沉。读完本节,第 9 章的客户端入门完整收口,为第 10 章进阶做好准备。 一、客户端能力声明:双向的 第 3.3 节讲过服务端的能力声明——服务端告诉客户端「我支持 tools/resources/prompts」。
本节摘要:第 9 章收尾节,讲三个收尾性主题。一是客户端能力声明——客户端也声明能力,告诉服务端「我支持采样、根、引导填写」等反向能力,这套双向声明影响服务端行为。二是第一类
Client与低层ClientSession的关系——Client是ClientSession之上的便捷封装,日常用它够,需要细粒度控制才下沉。读完本节,第 9 章的客户端入门完整收口,为第 10 章进阶做好准备。
第 3.3 节讲过服务端的能力声明——服务端告诉客户端「我支持 tools/resources/prompts」。客户端也声明能力,告诉服务端「我支持什么反向能力」:
服务端声明(正向能力): "我支持 tools、resources、prompts" → 客户端据此知道该问什么 客户端声明(反向能力): "我支持 sampling、roots、elicitation" → 服务端据此知道可以反向请求什么
「正向」指客户端→服务端的请求(tools/call 等);「反向」指服务端→客户端的请求(第 7 章的采样、根、引导填写)。客户端声明反向能力,让服务端知道「这些反向操作可以做」。
客户端能声明的反向能力(回顾第 7 章):
| 能力 | 服务端据此能做什么 | 现状 |
|---|---|---|
sampling |
反向请客户端做 LLM 补全 | 弃用趋势(第 7.4 节) |
roots |
反向问客户端工作区文件夹 | 弃用趋势 |
elicitation |
反向问客户端用户输入 | 主流(第 7.1-7.3 节) |
关键规则:如果客户端没声明某能力,服务端就不该尝试用。例如客户端没声明 elicitation,服务端的引导填写就不会工作(因为没有反向通道)。
# 客户端构造时声明能力(概念性) async with Client( url, capabilities={ "elicitation": {}, # 声明支持引导填写 # "sampling": {}, # 不声明,服务端不会请求采样 } ) as client: ...
这套双向声明影响服务端的实际行为。考虑一个例子:
场景:服务端有个 transfer 工具,需要引导填写确认 客户端 A(声明支持 elicitation): 服务端知道:可以反向问客户端 → transfer 执行中,触发引导填写 → 用户确认,转账完成 客户端 B(不声明 elicitation): 服务端知道:不能反向问 → transfer 执行中,引导填写无反方式 → 要么直接失败,要么用其他方式确认
服务端会根据客户端声明调整行为——这是双向声明的实际价值。它让服务端不必假设「客户端一定支持某反向能力」,而是动态适应。
⚠️ 注意:声明能力等于承诺实现。如果你声明支持
elicitation,就要在客户端实现「收到引导填写请求时,把问题转给用户、拿答案回传」的逻辑。声明了不实现,服务端的反向请求会挂起或失败。第 7 章的反向能力,客户端侧的实现由宿主(或你用 SDK 自研 Host)负责。
第 9.1-9.3 节用的都是第一类 Client。它是 v2 新增的高层封装,内部基于低层的 ClientSession:
高层(日常用) ┌─────────────────────────────────────────┐ │ Client │ │ - 按参数类型自动选传输 │ │ - async with 管理连接生命周期 │ │ - call_tool 等便捷方法 │ │ - 内部维持一个 ClientSession │ └─────────────────────────────────────────┘ 构建于 ┌─────────────────────────────────────────┐ │ ClientSession(低层) │ │ - 直接操作传输的读写流 │ │ - 需手动管理连接 │ │ - 更细粒度的协议控制 │ └─────────────────────────────────────────┘
类比服务端的两层模型(第 2.3 节):Client 之于 ClientSession,就像 MCPServer 之于低层 Server——高层做便捷,低层做兜底。
绝大多数场景,第一类 Client 够用。只有这些情况才下沉到 ClientSession:
| 场景 | 为什么下沉 |
|---|---|
| 复用一个传输跑多个会话 | Client 一对一,ClientSession 可复用 |
| 自定义握手逻辑 | Client 自动协商,ClientSession 可控 |
| 直接操作协议对象 | 调试、特殊协议操作 |
| 极致性能控制 | 减少高层封装的开销 |
# ClientSession 的概念性用法(下沉) from mcp import ClientSession async with stdio_client(params) as (read, write): # 手动创建会话 async with ClientSession(read, write) as session: await session.initialize() # 手动握手 result = await session.call_tool("add", {...})
下沉的代价是要手动管理——手动 initialize、手动处理传输、手动清理。除非必要,别下沉。
💡 技巧:默认用第一类
Client,把它当成「客户端的 FastAPI」——绝大多数需求它都能满足,代码也更简洁。只有当你遇到Client的能力边界(如要复用传输、要自定义握手),才考虑下沉到ClientSession。这个判断与第 2.3 节「日常用 MCPServer,特殊才下沉到低层 Server」完全一致。
用一个决策树帮你选:
你要写客户端? │ ├─ 是 → 默认用第一类 Client │ │ │ ├─ 需要复用传输跑多会话? → 下沉 ClientSession │ ├─ 需要自定义握手? → 下沉 ClientSession │ └─ 都不需要 → 用 Client(推荐) │ └─ 你要写 Host(编排多服务) → 用 Client + 第 10 章的会话组
绝大多数读者用 Client 就够。第 10 章会讲会话组(ClientSessionGroup),它是基于 Client/ClientSession 的更高级抽象,用于编排多个服务端。
读完本节,你应该带走这两点:
Client 是 ClientSession 的便捷封装——日常用 Client(简单),需要细粒度控制才下沉到 ClientSession(灵活但繁琐)。这两点与第 2 章的架构思想一脉相承:第 2.3 节的「两层服务端模型」对应这里的「两层客户端模型」,第 3.3 节的「能力声明」在这里扩展成「双向」。理解了这些,你就理解了客户端设计的整体逻辑。
Client 是 ClientSession 的便捷封装,类比 MCPServer 之于低层 Server。Client 处理:自动选传输、连接生命周期、便捷方法;ClientSession 是底层会话。Client,除非遇到它的能力边界才下沉——与「日常用 MCPServer」同理。第 9 章结束。你已经能连上 MCP 服务、消费它的全部能力。第 10 章讲客户端进阶——传输接入细节、会话组、缓存、订阅。