跳到主要内容

客户端与线格类型层

前三章站在服务端。本章换到客户端,并往下看整个 SDK 的地基:mcp-types 线格类型层,以及协议版本兼容怎么做。

1. Client:三种连法,一个抽象

client/client.py:261 Client 是包了 ClientSession + 传输管理的统一入口。它连什么,由构造参数决定,但最终都归到一个 Dispatcher:

连法传给 Client(...)底层 Dispatcher
内存里的 server 对象Client(mcp)(一个 MCPServer/Server)DirectDispatcher(零序列化)
HTTP URL一个 streamable-HTTP transportJSONRPCDispatcher
stdio 子进程一个 stdio transportJSONRPCDispatcher

内存直连的接线在 _connect_inproc(client/client.py:99),它用 create_direct_dispatcher_pair + modern_on_request 把 client 直接对接 server 内核。这就是第一章那个「10 行客户端直连内存 server」为什么不需要任何传输——测试和嵌入场景巨省事。

用法形状

async with Client(transport) as client: # __aenter__ 建会话、握手
tools = await client.list_tools() # 列工具
result = await client.call_tool("add", {"a": 1, "b": 2})
res = await client.read_resource("greeting://Ada")

__aenter__(client/client.py:431)里 _build_sessionClientSession 并完成协议协商(见 §4)。call_tool(client/client.py:673)、list_tools(client/client.py:846)等都是薄封装:打包 params → send_request → 解析结果模型。

2. ClientSession:反向请求的落点

client/session.py:308 ClientSession 是客户端侧对称于服务端的会话对象。它处理的是服务端反向发来的请求:采样(sampling/createMessage,让 client 用自己的模型跑一次推理)、elicitation(向用户追问)、roots(问客户端有哪些根目录)。

这些通过回调注入:构造 Client 时可传 SamplingFnT / ElicitationFnT / ListRootsFnT / MessageHandlerFnT(client/session.py 里这些类型别名)。server 反向请求到来时,ClientSession 调对应回调拿结果发回。

3. 多轮追问驱动器(input-required)

2026-07-28 引入的一个重要模式:工具可能中途需要更多用户输入才能继续。客户端侧由 run_input_required_driver(client/_input_required.py)驱动:

call_tool(...)


server 返回 InputRequiredResult(带一批问题)
│ driver 拦下,不当最终结果

driver 收集答案(input_responses)+ request_state
│ 带着答案重试同一个调用

server 恢复执行 → 最终结果(或又一轮问题,最多 N 轮)

client/client.py:673 call_tool 内部的 retry 闭包 + _drive_input_required(client/client.py:805)实现这个循环。服务端如何产生这批问题,见第 5 章的 Resolve / elicitation。

4. 版本协商:握手纪元 vs 2026 无握手纪元

协议在换代,SDK 要同时兼容。Clientmode 参数(client/client.py ConnectMode)有三档:

mode行为
"legacy"initialize 握手(旧纪元)。
"auto"先尝试 discover(新纪元),失败回退到握手。
具体版本串(如 "2026-07-28")直接采用该现代版本。

"auto" 的探测在 _probe.py negotiate_auto。版本常量在 mcp_types/version.py(HANDSHAKE_PROTOCOL_VERSIONS / MODERN_PROTOCOL_VERSIONS)。这套设计让「一个新客户端连一个旧服务端(或反之)」能自动谈拢。

5. mcp-types:分纪元的线格层

整个 SDK 的地基是独立子包 src/mcp-types/mcp_types/。它有两个身份:

  1. 给用户用的、无版本的模型(CallToolResultToolClientCapabilities…),从 src/mcp-types/mcp_types/_types.py(2193 行)导出,用户代码收到的就是这些。
  2. 分纪元的内部校验模型:v2025_11_25/v2026_07_28/ 两个子包,是每个 schema 纪元的严格线格形状(内部用,不供直接 import)。

methods.py:版本门控的中枢

src/mcp-types/mcp_types/methods.py(746 行)是「哪个方法在哪个版本存在、长什么样」的权威表。文件头注释说清了两类表:

  • Surface 表:键是 (method, version) → 该版本的线格类型。键缺失 = 该版本没有这个方法(版本门控就靠这个)。schema 校验按纪元走(pre-2026 都用 2025-11-25,2026 用 2026-07-28)。
  • Monolith 表:键是 method → 无版本的用户模型。

内核 ServerRunner._on_request 里那句 validate_client_request(method, version, params)(第 2 章)就是查 Surface 表:方法在当前协商版本不存在就 METHOD_NOT_FOUND,存在就用对应纪元的 schema 校验。版本兼容的复杂度被收敛进这一张表,上层内核逻辑保持干净。

线格模型的基类

生成的线格模型都继承 WireModel(src/mcp-types/mcp_types/_wire_base.py),它只干一件事:开 populate_by_name,让字段能按 Python 名或线格 alias 双向填(比如 _metaprogress_tokenprogressToken)。

6. 本章要点

  • Client 三种连法(内存/HTTP/stdio)都归到 Dispatcher;内存直连走 DirectDispatcher,测试/嵌入极省事。
  • ClientSession 处理服务端反向请求(采样/elicitation/roots),靠回调注入。
  • 多轮追问由客户端 input-required 驱动器带着答案重试实现。
  • 版本兼容全靠 mcp-types/methods.py(method, version) 表做门控与分纪元校验。

→ 最后一章:精华设计、边界、横向对比、代码地图。