跳到主要内容

精华设计、边界与代码地图

前四章讲「怎么转」。本章讲「妙在哪、哪会崩、跟谁比、源码从哪进」。

1. 巧妙之处(可借鉴的技术)

1.1 Dispatcher 解耦:一层薄接口撬动整个可测试性

妙在哪: 把「MCP 语义」和「线格编码」用一个只认 (str, dict)Dispatcher 协议隔开(shared/dispatcher.py)。代价极小(一个 Protocol),收益巨大:同一内核 ServerRunner 直接跑在内存 DirectDispatcher 上做零序列化测试,或跑在 JSONRPCDispatcher 上上线。这是「用接口切一刀换来可测试性和可演进性」的教科书案例。

1.2 类型标注即 schema:func_metadata

妙在哪:pydantic.create_model 把函数签名动态变成模型,再 model_json_schema()(server/mcpserver/utilities/func_metadata.py:191)。用户写零 schema,协议要的 schema 全自动。返回类型同理生成 output schema。这把「协议的 schema 负担」完全转嫁给了语言本身的类型系统。

1.3 Resolve:工具参数的依赖注入 DAG

妙在哪: 参数标注成 Annotated[T, Resolve(fn)] 时,该参数不由 LLM 填,而是调 resolver 算出来(server/mcpserver/resolve.py)。resolver 之间能声明依赖、能取 Context、能取别的参数——形成一个 DAG,框架按拓扑序跑。

更妙的是 resolver 能返回 Elicit[T] 向用户要输入,框架按协议纪元自动选实现(resolve.py 文件头):

协议版本elicitation 怎么做
≥ 2026-07-28返回 InputRequiredResult 带一批问题,client 重试时带 input_responses/request_state 恢复(多轮)
≤ 2025-11-25调用中途同步发 elicitation/create 请求

「独立的问题一轮问完,依赖别人答案的问题排到后续轮」——把「向用户追问」也变成了可组合的依赖注入。

1.4 协议错误 vs 业务错误:双通道

妙在哪: 工具抛 MCPError → 顶层 JSON-RPC 协议错误;抛别的 → CallToolResult(is_error=True) 业务失败(server/mcpserver/tools/base.py Tool.run)。客户端因此能区分「这个调用协议上就不对」和「工具跑了但失败了」。异常→线格的归一只在 handler_exception_to_error_data 一处发生(shared/jsonrpc_dispatcher.py:83),是单一边界。

1.5 版本门控收进一张表

妙在哪: 「方法在哪个版本存在、按哪个纪元校验」全收进 mcp-types/methods.py(method, version) 表。键缺失即版本门控,校验按纪元分流。上层内核只写 validate_client_request(method, version, params) 一句,复杂度不外溢。

1.6 请求级 vs 连接级外发通道

妙在哪: HTTP 下,server 给「正在处理的某请求」推进度时,消息要落到那个请求自己的响应流,而非乱发。ServerSession 用请求级 dctx(带 request_id)和连接级 standalone 通道两条路,靠 related_request_id 选(server/runner.py:301)。

1.7 缓存提示

妙在哪: cache_hints 让服务端给结果打 ttlMs/cacheScope,在序列化前按协议版本决定是否带这些字段(server/runner.py:176 _innerapply_cache_hint),客户端侧有 ClientResponseCache(client/caching.py)。协议感知的缓存,而非盲缓存。

2. 边界与局限(诚实)

  • v2 是预发布,明确「不要上生产」。 README 顶部 CAUTION 写得很重:v2 是 alpha/beta,2.0.0aN/2.0.0bN,每个预发布都可能对上一个有破坏性改动;生产请用 v1.x(v1.x 分支)。若你的包依赖 mcp,应加 <2 上界。
  • 多处 API 标注 provisional。 代码里大量 TODO(L##) / # provisional:Dispatcher.run 生命周期面(shared/dispatcher.py)、Context/中间件签名(server/lowlevel/server.py self.middleware 上方注释)在 v2 stable 前还会变。
  • initialize 路径不能 await 对端。 无握手改造中,initialize 内联跑(读循环停着),此路径上任何等对端的操作会死锁(server/runner.py:184 附近 TODO(L29) 注释)。
  • 能力 API 尚待重整。 get_capabilities 目前从已注册 handler 推导,但 list_changed 标志要外部传 NotificationOptions(server/lowlevel/server.py:518 附近 TODO(L53))。
  • 部分能力已弃用。 2026-07-28 起 logging、roots、client→server progress 能力弃用,注册对应 handler 会发 MCPDeprecationWarning(server/lowlevel/server.py:309 内 warnings)。
  • SSE 流恢复将被移除。 resumption_token 仅支持 2025-11-25 及更早;下个协议修订移除 SSE-stream 恢复(shared/dispatcher.py resumption_token 文档)。

3. 横向对比

作为 ai-agent-reference / protocols 域内的参照,几个取舍值得对比:

维度MCP Python SDK v2 的取舍
schema 来源从 Python 类型标注自动生成,而非手写 IDL/schema。
传输耦合Dispatcher 协议彻底解耦语义与编码;可换 JSON-RPC / 内存 / 未来 gRPC。
版本兼容集中式 (method, version) 表 + 双纪元收循环,而非分散的 if-else。
双向性server↔client 对称,server 能反向请求(采样/追问);many RPC 框架单向。
与 TS SDK刻意对齐(如 coerce_request_id"7"==7 语义注明 matches the TS SDK)。

(同 shelf 的 TypeScript SDK / 其他语言 SDK 实现同一 MCP 规范;本 SDK 的独特点在「类型即 schema」+「Dispatcher 解耦」。)

4. 代码地图(导航索引)

给人和 agent 的跳转表。行号会随上游漂移,优先用符号名 grep;本表 as-of commit 53117cb

主题文件路径符号名
好用层入口 / 装饰器src/mcp/server/mcpserver/server.pyMCPServerMCPServer.toolMCPServer._handle_call_tool
工具解剖(注册时)src/mcp/server/mcpserver/tools/base.pyTool.from_functionTool.run
类型→schema 魔法src/mcp/server/mcpserver/utilities/func_metadata.pyfunc_metadataFuncMetadata_try_create_model_and_schema
Context 注入src/mcp/server/mcpserver/utilities/context_injection.pyfind_context_parameter
工具运行期 Contextsrc/mcp/server/mcpserver/context.pyContext(report_progress/read_resource/elicit/log)
Dispatcher 协议src/mcp/shared/dispatcher.pyDispatcherOutboundDispatchContextCallOptionscoerce_request_id
JSON-RPC 实现src/mcp/shared/jsonrpc_dispatcher.pyJSONRPCDispatcherrunsend_raw_request_handle_requesthandler_exception_to_error_data
内存直调实现src/mcp/shared/direct_dispatcher.pyDirectDispatchercreate_direct_dispatcher_pair
每连接内核src/mcp/server/runner.pyServerRunnerServerRunner._on_request_make_contextserve_dual_era_loopmodern_on_request
lowlevel handler 表src/mcp/server/lowlevel/server.pyServerServer.add_request_handlerServer.runHandlerEntry
每连接状态 / 反向通道src/mcp/server/connection.pyConnectionConnection.from_envelopeConnection.for_loop
服务端会话src/mcp/server/session.pyServerSession
stdio 传输src/mcp/server/stdio.pystdio_server
Streamable HTTPsrc/mcp/server/streamable_http.pysrc/mcp/server/streamable_http_manager.pyStreamableHTTPServerTransportStreamableHTTPSessionManagerEventStore
依赖注入 / 追问src/mcp/server/mcpserver/resolve.pysrc/mcp/server/elicitation.pyResolveresolve_argumentsElicitationResult
客户端入口src/mcp/client/client.pyClientClient.call_tool_connect_inproc
客户端会话src/mcp/client/session.pyClientSessionClientSession.initialize
多轮追问驱动src/mcp/client/_input_required.pyrun_input_required_driver
线格类型src/mcp-types/mcp_types/_types.pysrc/mcp-types/mcp_types/_wire_base.pyWireModel
版本门控表src/mcp-types/mcp_types/methods.pyvalidate_client_requestserialize_server_resultSPEC_CLIENT_METHODS
版本常量src/mcp-types/mcp_types/version.pyHANDSHAKE_PROTOCOL_VERSIONSMODERN_PROTOCOL_VERSIONS

5. 一句话收束

MCP Python SDK v2 的两个核心洞见:(一)用语言的类型系统当协议 schema,让写 server 只剩写函数;(二)用一层不懂 MCP 的 Dispatcher 把语义和传输切开,让同一内核可测试、可嵌入、可跨编码演进。 其余的版本门控、双纪元、依赖注入、双向追问,都是围绕这两点的工程化展开。