跳到主要内容

塌下来的其余部分 — 断线、订阅,以及这本书还剩什么

这一章讲三件事: 第 11 章那条理由再往下推,还压塌了哪两样; 顺手删掉和顺手加上的几件零碎各是什么; 以及全书的落点——这本书哪几部分作废了、哪几部分仍然成立。

它在全书链条里的位置: 第 11 章讲的是「地基怎么塌的」(打招呼、状态、服务器发问)。 这一章讲塌下来砸到了什么,并给出全书的结账单。

1. 同一条理由,还剩两级没推完

这一节先接上一章的线头。

第 11 章那条理由只有一句:服务器多开了几份副本,几份之间不共享内存, 所以「服务器记得你上次说过什么」这件事没法保证。

那一章已经推出四级:状态取消 → 打招呼的产物跟着每条请求走 → 新增一个问路方法 → 服务器不能再主动发问。

还剩两样东西也建在「服务器记得你」上面,这一章把它们推完:

还剩哪一样它靠服务器记着什么本章第几节
断线之后从断点接上记着「我给你发过哪些消息、发到第几条」§2
服务器主动来说「清单变了」记着「谁登记过要听哪几类」§3

本章的主走查:第 08 章那次三分钟的调用,换到新规矩下跑一遍

走查的输入:第 08 章那件你已经认识的活儿——「把项目里的说明文档全转成 PDF」, 一共 214 个文件,跑到第 180 个的时候线断了。 214、180、以及下面出现的秒数、凭据字串和毫秒数,都是我们为演示编的,不是真实数值; 规矩本身(哪些被删、哪些必须重做)全部出自规范。

四步,分别落在下面四节上:

第 1 步 · 断了 旧:报「我最后收到 #180」→ 补发 #181
新:这次请求作废,换新编号整条重发 → §2
第 2 步 · 不想白跑 改用可选的「任务」扩展:先拿一个凭据回来 → §2 末、§5
第 3 步 · 重连后 必须把订阅登记重发一遍,拿回订阅编号 1 → §3
第 4 步 · 要不要重取清单 看上一份清单结果里那个保鲜期还剩多少 → §4

图说:同一次断线,新规矩下要多做两件事(重发登记、判断缓存),
少做一件事(不必再记「发到第几条」)。

2. 第五级:断线不能续传 ⟹ 只能整条重发

这一节推翻第 08 章 §5,也是主走查的第 1 步。

回看第 08 章那句伏笔:断线续传要求服务器一直记着自己发过什么消息、发到第几条。

这和「不记着你」正面冲突。 于是它被删了—— 连同那个用来记「发到第几条」的编号一起1

现在断线会发生什么?

书里那一套2026-07-28
线断了报出最后收到的编号 #180,服务器从 #181 往后补发这次请求作废
客户端要做什么重连、报编号换一个新编号,把整条请求重发一遍
已经跑完的那 180 个不用重跑白跑(除非服务器自己防了重复:同一件活儿跑第二遍,不会把已经转好的再转一遍)

这是这次改动里代价最明显的一条。 一次跑三分钟的活儿, 在第 180 个文件上断掉,现在就是从头再来——前面那 2 分 30 秒白花。

主走查第 2 步:不想白跑的话,得换一条路

规范不是没给出路,只是把它挪出了核心。

长活儿改用一个叫「任务」的可选扩展来做:服务器不再把结果攥在这条连接上, 而是先回你一个凭据(比如 task-9f3),你拿着它去问「跑到哪儿了」「结果好了没」2

不用扩展: tools/call ──── 挂 3 分钟 ────→ 断了 = 白跑
用了扩展: tools/call ──→ 立刻回 task-9f3
客户端每 10 秒问一次:task-9f3 跑到哪儿了?
断了也不要紧——**凭据不在这条连接上**,重连之后接着问

图说:关键差别不在「快不快」,在于**凭据是一张能带走的票**,
而旧那套的「发到第几条」只在这条连接里有意义。

但要看清一件事:它已经不是核心协议的一部分了(见第 5 节)。 也就是说,你连的那台服务器可以完全不支持它,那你就只能整条重发。

3. 第六级:服务器不能主动推 ⟹ 订阅改成客户端登记一条长回话

这一节推翻第 08 章 §6 的后半段、以及第 04 章 §3 那条长连接;它是主走查的第 3 步。

第 08 章讲过:清单会变,服务器可以给你打个招呼。 当时的做法是:客户端向服务器登记(resources/subscribe), 服务器往那条长连接上主动推。

这两样都被删了,理由还是同一条:服务器得记着「谁登记了什么」3

新的形状是:客户端主动发一条特殊的请求(subscriptions/listen), 在里面列明自己想收哪几类消息;这条请求的回复本身就是一条一直开着的长回话, 消息从这条回话上流下来4

客户端 → subscriptions/listen
notifications = { "toolsListChanged": true,
"resourceSubscriptions": ["file:///project/config.json"] }
服务器 ← 第一条必须是「收到了,订阅编号 1」
← notifications/tools/list_changed (带订阅编号 1)
← notifications/resources/updated (带订阅编号 1)
…一直流下去,直到一方关掉

图说:方向反过来了——不是服务器主动推,是客户端先递一条请求,
服务器沿着这条请求的回复往下写。**状态在这条请求上,不在服务器的记忆里。**

四类可以登记的东西,正好对上前面几章:工具清单变了、话术清单变了、 资源清单变了、以及某几份指定资源本身变了4

还有两条实用的规矩:

  • 服务器「禁止」发你没登记的类型;它回过来的那条确认里会写明它实际同意了哪几类, 你要拿它和自己请求的对一遍4;
  • 本机线路上如果连接断了重连,客户端「必须」把登记重发一遍—— 服务器不跨重连保留任何订阅状态4

最后这一条就是主走查的第 3 步: 线在第 180 个文件上断了, 你重连之后除了重发那条调用,还得把 subscriptions/listen 也重发一次, 再拿回一个订阅编号 1。漏掉这一步不会报错,你只是从此再也收不到「清单变了」。

4. 顺手删掉的,和顺手加上的

这一节收拢几件零碎改动,并说明它们为什么全都出自同一条理由;主走查的第 4 步在这儿。

删掉的:

删了什么这本书在哪儿讲过为什么删
探活(ping),两个方向都删第 08 章脚注提过服务器不能主动发请求了;而客户端那一侧,随便调一个正常方法就已经证明对方活着3
设置日志级别的那个方法第 08 章 §4它本身就是在改「这次连接的状态」;现在级别跟着每一条请求走1
「根目录变了」的通知第 07 章 §4根目录改成用完就问,不需要变更通知了3
资源的订阅 / 退订方法第 08 章 §6见上一节3

加上的:

每一份清单结果现在都必须带一个保鲜期。 这叫结果保鲜期—— 服务器告诉你「这份结果你可以放心用多少毫秒」;配套还有一个字段说明 这份结果能不能被共享的中间人存起来(取决于它里面有没有跟具体用户相关的内容)5

主走查的第 4 步就用它: 你重连之后要不要再要一遍工具清单? 上一次拿清单时服务器回的保鲜期是 ttlMs: 300000(5 分钟), 而这次断线前后一共只过了 40 秒——所以直接用手里那份,省掉一次来回。 要是断了 6 分钟,那就得重新要一遍。

它为什么也出自同一条理由? 因为「不记着你」之后, 清单结果不再随连接变化——同一个问题任何时候问、问哪一份副本,答案都一样。 答案稳定了,才谈得上把它存起来重复用。

规范还提醒了两件很实用的事5:

  • 保鲜期是提示,不是保证——服务器可能在到期之前就把数据改了;
  • 收到「清单变了」的消息时,哪怕还没到期也要立刻当成过期

顺带一个小改动但影响挺大: 规范现在建议服务器每次返回工具清单时保持顺序一致, 理由是这样客户端才好把它存下来重复用,模型那一侧的提示缓存命中率也更高1顺序一变,存下来的那份就全部作废——这一条第 09 章讲按需查找时也提到过同样的道理。

5. 核心之外的东西搬去了「扩展」

这一节讲一个方向性的变化,它决定了这份规矩以后会怎么长。

以前的做法是往核心里加功能。现在不是了。

规范引入了扩展机制——把非核心的东西拆成一个个可选的包,各自单独排版本; 双方在能耐里声明自己支持哪几个,靠一个带前缀的标识来指认6

第一批被搬出去的,就是第 2 节那个「任务」——它本来是核心里的实验特性, 这一版被整个挪进了一个官方扩展1所以主走查第 2 步那条出路, 能不能走通取决于对面那台服务器有没有声明它。

规矩只有一条:如果一方支持某个扩展、另一方不支持, 支持的那一方要么退回核心行为,要么明确报错6

判断(我们的,不是书里的): 这个变化对读这本书的人有一个很实际的影响—— 以后「MCP 支不支持某某功能」这个问题会越来越难回答, 因为答案取决于「哪个核心版本 + 哪几个扩展」。 所以你的客户端从现在起就该把「对面支持什么」当成运行时的数据来处理, 而不是写死在代码里的假设。 如果错,会错在: 如果扩展生态没长起来、绝大多数实现只跑核心, 那么这层运行时判断就是白写的一层麻烦。判据是:一年后主流服务器声明的扩展有几个。

6. 全书的落点:这本书还值不值得读

这一节是全书的结账单。

先给结论:值得,但只能读一半——而那一半恰好是它讲得最细的一半。

这本书教的现在的状态
三类原语:工具、资源、话术完全成立,一个字没改
「先要清单,再挑一个用」这个套路完全成立,方法名都没变
宿主 / 客户端 / 服务器三个角色的分工完全成立
工具调用的四步(列清单 → 模型挑 → 宿主跑 → 结果送回)完全成立,这是模型厂商那一侧的事,和 MCP 改不改无关
内容块、资源的地址、资源模板、话术的参数完全成立
两条线路的选择标准(在不在本机)完全成立
连接怎么建立、那三条打招呼报文⚠️ 只能当历史读(第 11 章 §4)
断线续传那套办法⚠️ 只能当历史读(§2)
资源的订阅 / 退订⚠️ 只能当历史读(§3)
采样、根目录⚠️ 仍能用,但已排进移除队列(第 07 章 §7)
日志(挂一个函数接住服务器的日志)⚠️ 仍能用,但已排进移除队列;规范建议改成让服务器写标准错误流,或者接一套通用观测工具7
服务器主动向客户端发问的写法已不被支持,必须改用第 11 章 §6 那个形状

关于最后那三样弃用,有一件事要照实说: 规范只登记了「弃用了」和「改用什么」,没有给出为什么弃用7。 所以第 08 章那句「日志后来被列进移除队列」到此为止——理由我们补不出来,不编。

判断(我们的,不是书里的): 这本书真正的价值,在于它是唯一一本从使用方这一侧 从零走一遍的书——而使用方那一侧的活儿,七成不在协议上: 怎么把清单交给模型、怎么把结果送回去、怎么处理多种内容块、怎么翻译格式、 怎么在工具太多时取舍。这些全都没有被这次改动碰到。 被碰到的是「连接怎么建立」这一层,而那一层恰恰是官方工具包替你封装掉最多的一层。 如果错,会错在: 如果工具包在改代之后大幅改动了它暴露给你的那套写法 (比如不再有「建会话时挂回调」这个形状),那么书里的代码结构也会跟着作废, 而不只是报文那一层。判据是:官方 Python 工具包在新一代下的客户端写法, 还认不认「一个会话对象 + 一串回调」这个形状。

7. 可带走的

  1. 第五级:断线续传被删——断了就是换新编号整条重发,已经跑完的那部分白跑;
  2. 想不白跑,得改用可选的「任务」扩展:服务器先回一个能带走的凭据,你拿它去问进度;
  3. 第六级:订阅改成客户端登记一条长回话(subscriptions/listen),消息沿着这条回话流下来;
  4. 本机线路重连之后必须把登记重发一遍——服务器不跨重连保留任何订阅状态,漏了不报错;
  5. 顺手删了探活、设日志级别、根目录变更通知、资源订阅,每一条都是在删「服务器要记的东西」;
  6. 顺手加了清单结果的保鲜期(多少毫秒之内可以放心用),因为答案稳定了才谈得上存下来重用;
  7. 保鲜期是提示不是保证;收到「清单变了」就立刻当过期,别等它到期;
  8. 非核心功能搬去了可选扩展,各自排版本——「MCP 支不支持某某」以后要看「核心版本 + 扩展」;
  9. 这本书仍然成立的部分:三类原语、先要清单再挑一个用、三个角色的分工、工具调用四步、两条线路的选择标准;
  10. 只能当历史读的部分:打招呼、断线续传、资源订阅、日志、采样、根目录、以及服务器主动发问;
  11. 弃用那三样的理由,规范没给——只给了改用什么。

8. 原文地图

这一章推翻的是书里这些地方——列出来方便你回头对照。

被推翻的内容原书章原文位置
断线续传Resuming Connectionstext/12-fm-resuming-connections.txt:7(搜「issue an HTTP GET to the MCP endpoint」) · :11(搜「replay messages that would have」)
那条可选的长连接Initializing the Client and Connecting to a Servertext/07-fm-initializing-the-client-and-connecting-to-a-serv.txt:230(搜「getting an SSE」)
资源的订阅 / 退订Interacting with MCP Server Capabilitiestext/08-fm-interacting-with-mcp-server-capabilities.txt:336(搜「resources/subscribe or subscribe_resource()」)
探活与设置日志级别Interacting with MCP Server Capabilitiestext/08-fm-interacting-with-mcp-server-capabilities.txt:828(搜「send_ping()」) · :831(搜「set_logging_level」)
日志走通知、挂一个函数接住Interacting with MCP Server Capabilitiestext/08-fm-interacting-with-mcp-server-capabilities.txt:834(搜「Server-initiated logs are sent to the client via notifications」)
采样与根目录Providing MCP Client Capabilitiestext/09-fm-providing-mcp-client-capabilities.txt:33(搜「allows MCP server to make use of」) · :122(搜「servers SHOULD respect boundaries」)
仍然成立的: 三类原语Example: A Simple Host Applicationtext/06-fm-example-a-simple-host-application.txt:234(搜「The three MCP primitives are tools」)
仍然成立的: 先要清单再挑一个用Interacting with MCP Server Capabilitiestext/08-fm-interacting-with-mcp-server-capabilities.txt:7(搜「discovery and use」)

Footnotes

  1. 补充(不在书里):2026-07-28 版变更日志的「重大变更」里,与本章直接相关的有:移除流恢复与消息重发(第 9 条);用 subscriptions/listen 取代 HTTP GET 端点与资源订阅/退订(第 4 条);移除探活与设置日志级别、日志级别改为每条请求携带(第 5 条);把实验性的「任务」挪进官方扩展(第 6 条);以及第 1 条里那句「清单方法的结果不再随连接而变」。「次要变更」里还有一条:建议服务器以确定的顺序返回工具清单,以利客户端缓存与模型侧的提示缓存命中。来源:MCP 规范变更日志 https://modelcontextprotocol.io/specification/2026-07-28/changelog(查阅于 2026-08-25)。 2 3 4

  2. 补充(不在书里):重做后的「任务」扩展用轮询取代了原先那个阻塞式的取结果方法,新增了一个供客户端中途送入内容的方法,去掉了列任务的方法,并允许服务器在客户端没有事先声明的情况下直接返回任务凭据。来源同脚注 1 的变更日志(查阅于 2026-08-25)。

  3. 补充(不在书里):规范提案 SEP-2575《让 MCP 无状态》的「弃用与移除的 RPC」一节逐条给了理由。资源订阅/退订被移除的理由原文是:「资源订阅天生是有状态的——服务器必须记住每个客户端订阅了哪些资源。」探活两个方向都被移除:服务器到客户端那一侧因为服务器不能再独立发起请求;客户端到服务器那一侧因为任何一次正常调用已经证明服务器活着,而连接健康更适合交给传输层机制。「根目录变了」的通知被移除,是因为根目录改成按需索取,不再需要变更通知。来源:https://modelcontextprotocol.io/seps/2575-stateless-mcp(查阅于 2026-08-25)。 2 3 4

  4. 补充(不在书里):subscriptions/listen 打开一条长期的通知流,客户端在请求里用一个过滤器列明要收哪几类(工具清单变、话术清单变、资源清单变、以及一份指定资源地址的清单);服务器禁止发送客户端没有明确请求的类型,并必须把第一条消息发成一个带订阅编号的确认,里面写明它实际同意支持的那几类。规范还规定:在本机线路上,如果连接中断后重新建立,客户端必须重新发送这条登记请求——服务器不跨重连保留任何订阅状态。 与请求相关的消息(进度、日志)仍走它们各自那条请求的回复流,不走这条订阅流。来源:MCP 规范订阅页 https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/subscriptions(查阅于 2026-08-25)。 2 3 4

  5. 补充(不在书里):规范要求服务器在服务器发现、四种列清单以及读资源这六类结果上必须带缓存提示:ttlMs 是一个毫秒数,表示客户端可以把这份结果当成新鲜的多久;cacheScopepublicprivate,后者表示结果和具体的授权身份相关、共享缓存禁止跨身份复用。规范强调 ttlMs新鲜度提示而非保证,并规定收到相应的「清单变了」通知时,即使还在有效期内也应当立刻视为过期。来源:MCP 规范缓存页 https://modelcontextprotocol.io/specification/2026-07-28/server/utilities/caching(查阅于 2026-08-25)。 2

  6. 补充(不在书里):扩展在能耐对象的 extensions 字段里声明,是一张「扩展标识 → 该扩展的设置」的表;标识必须带前缀(例如官方的界面扩展和任务扩展)。如果一方支持而另一方不支持,支持的那一方必须要么退回核心行为、要么用合适的错误拒绝该请求;每个扩展都应当写明自己的回退行为。来源:MCP 规范版本与兼容页 https://modelcontextprotocol.io/specification/2026-07-28/basic/versioning(查阅于 2026-08-25)。 2

  7. 补充(不在书里):日志与根目录、采样一起被 SEP-2577 标记为弃用。弃用登记页给日志的迁移路径是:本机线路下写标准错误流,或者改用 OpenTelemetry 这类通用观测工具做结构化可观测性;三样都享受「至少保留十二个月才有资格被移除」的过渡期。登记页和变更日志都只写了「弃用了什么」和「改用什么」,没有写为什么——所以本章不给理由。来源:MCP 规范弃用登记页 https://modelcontextprotocol.io/specification/2026-07-28/deprecated(查阅于 2026-08-25)。 2