ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

MCP协议无状态重构实战:Session移除、Sampling收紧与MRTR迁移指南

MCP协议无状态重构实战:Session移除、Sampling收紧与MRTR迁移指南 MCP 这套协议从去年火到现在我前后在三个项目里接过它的服务端和客户端也写过两套内部教程。结果上个月翻官方仓库的变更记录发现我半年前写的东西有一大半已经跑不通了——不是小修小补是 Session 层被整个拿掉、Sampling 的调用路径被重做。如果你手上还留着 2025 年那批MCP 入门到精通的笔记现在照着敲大概率会在第一步就卡住。这篇不打算复述官方文档而是把我自己迁移旧代码时踩到的坑、以及重新理解这套协议设计意图的过程完整摊开。核心围绕四个词MCP、Session、Sampling、MRTR、Stateless。适合两类人看一类是已经用过 MCP、手里有存量代码需要升级的另一类是正准备学、但被网上新旧混杂的教程搞晕的。我会尽量把为什么这么改讲透因为只记 API 名字的话下个月可能又变了。1. 先搞清楚这次重写到底动了哪几块骨头很多人看到推翻重写四个字会以为整个协议换了其实不是。MCP 的定位没变——它依然是让模型侧和外部工具/数据源之间用统一协议对话的那层胶水。变的是连接的生命周期管理方式和模型反向调用工具能力的暴露方式。这两块恰好是旧教程里着墨最多、示例最密集的地方所以体感上像是全废了。1.1 Session 被移除从有状态长连接转向无状态请求旧版本里客户端和服务端建立连接后会协商出一个 Session后续所有请求都挂在这个 Session 上。服务端可以往 Session 里塞上下文、缓存、订阅关系。这套设计在单机 demo 里很优雅但一上生产就暴露问题水平扩容时 Session 粘性问题、进程重启后状态丢失、多副本之间同步成本高。新版把 Session 这个概念从协议层拿掉了转向**无状态Stateless**模型。每一次请求都是自包含的——该带的上下文、该带的凭据、该带的工具清单全在这一次请求里说清楚。服务端不再假设你上次来过也就没有你上次留下的东西。这个改动对写代码的人意味着什么最直接的一点你不能再依赖服务端记住任何东西。以前那种第一次调用初始化、后续调用直接复用的写法全部作废。每次调用都要把完整参数带上哪怕重复。1.2 Sampling 的语义被重做从服务端主动采样到受控的回调Sampling 在旧版里是个挺吸引人的能力——服务端可以在处理请求的过程中反过来请求模型侧做一次生成比如让模型帮忙补全一个参数、判断一个分支。听起来很强大实际用起来很危险服务端一旦能主动触发模型调用就等于把控制权和成本都交出去了很容易出现递归调用、费用失控、死循环。新版把这条路径收紧了改成一种受控的、由客户端主导的回调机制。服务端不能想调就调而是要在响应里声明我需要一次模型侧的协助由客户端决定要不要满足、怎么满足。控制权回到了调用发起方手里。1.3 MRTR 登场多轮往返有了正式的协议表达MRTR 是这次变更里最容易被忽略、但实际最重要的一块。它的意思是多轮往返Multi-Round Trip Request——一次交互不再是一问一答而是允许在协议层面表达我还需要再来一轮。旧版遇到需要多轮的情况大家是怎么做的靠 Session 里存中间状态或者靠应用层自己拼。新版既然没了 Session就必须在协议里给多轮一个正式的位置MRTR 就是干这个的。它和 Stateless 是一对因为没有状态所以每一轮都要显式声明而 MRTR 就是那个显式声明的载体。把这三块串起来看逻辑就清楚了去掉 Session 是为了可扩展去掉隐式 Sampling 是为了可控引入 MRTR 是为了在无状态前提下依然能表达复杂交互。三者不是三个独立改动是一次设计上的整体转向。维度旧版2025 教程常见写法新版连接状态Session 承载服务端可存上下文无状态每次请求自包含模型反向调用服务端可主动 Sampling客户端主导的受控回调多轮交互靠 Session 或应用层拼MRTR 协议级表达扩容方式需处理 Session 粘性天然可水平扩展教程适配大量存量教程需按新语义重写2. 无状态之后那些以前能跑的代码为什么集体报错我迁移第一个项目时最直观的感受是报错信息五花八门但根因高度集中。下面按我实际遇到的顺序拆基本覆盖了大多数人会撞上的几类。2.1 初始化握手阶段的字段缺失旧代码里客户端连上之后会发一个初始化请求服务端回一个 Session 标识之后所有请求带着这个标识走。新版没有这个标识了取而代之的是每次请求都要携带完整的协商信息——协议版本、能力声明、客户端信息。我第一次跑新版时服务端直接返回参数校验失败提示缺少能力声明字段。当时我以为是版本号写错了折腾了半天才发现旧代码把能力声明放在初始化那一次后续请求默认继承新版要求每次请求都带上或者至少带上服务端需要的部分。提示迁移时优先检查你的请求构造函数看它是不是只在初始化时组装了一次参数、后续复用了同一个对象。无状态模型下这种复用是错误来源的重灾区。2.2 依赖服务端记住我的逻辑全部失效有个功能我印象很深旧版里客户端第一次调用某个工具时传了用户身份服务端把它存进 Session后续调用就不用再传了。新版下这个功能直接崩——第二次调用服务端说我不知道你是谁。修复方式很朴素把身份信息提到每一次请求里。听起来啰嗦但这就是无状态的代价也是它可扩展的原因。我后来干脆封装了一个请求构造器把公共字段身份、版本、能力统一注入业务代码只关心差异部分反而比旧版更清爽。2.3 并发场景下的状态竞争消失了但新问题来了旧版有 Session 的时候并发请求共享同一个 Session经常出现状态互相覆盖的 bug。新版无状态这类竞争自然没了。但代价是原本靠 Session 隐式传递的中间结果现在必须显式在请求间传递。我遇到一个典型场景一个需要三步完成的任务旧版把第一步的结果存 Session第二步直接读。新版下第二步拿不到第一步的结果除非第一步的响应里把它返回给客户端客户端再在第二步请求里带上。这其实就是 MRTR 要解决的问题后面单独讲。2.4 错误处理路径需要重写旧版的错误处理里有一类错误是Session 失效客户端收到后重新初始化即可。新版没有 Session也就没有这类错误取而代之的是请求级别的校验失败。这意味着你的重试逻辑要改不能再靠重新握手来恢复而是要靠修正请求参数后重发。我踩的坑是旧的重试逻辑遇到校验失败会无脑重试新版下这会变成死循环——参数不对重试一万次还是不对。后来加了校验类错误不重试、直接上报的分支才解决。3. Sampling 收紧后模型反向调用该怎么写Sampling 这块是我花时间最多的地方因为旧教程里它的示例最花哨迁移时落差也最大。3.1 旧版 Sampling 为什么危险旧版允许服务端在处理请求时直接向模型侧发起一次生成请求。问题在于服务端并不知道这次生成要花多少钱、要跑多久、会不会触发下一轮 Sampling。一个设计不当的工具可能因为一次 Sampling 触发另一次 Sampling形成递归。我在测试环境里就制造过一次事故一个工具在参数缺失时调用 Sampling 让模型补全模型补全的结果又触发了同一个工具循环了十几轮才被超时掐断。那次之后我对服务端主动 Sampling 一直很警惕。3.2 新版把控制权交回客户端新版的做法是服务端在响应里声明我需要一次模型协助并给出它需要的输入和期望的输出格式。客户端收到后决定是否调用模型、用哪个模型、要不要加限制。生成结果再由客户端带回给服务端继续后续处理。这个转向的本质是把不可控的副作用变成可控的显式步骤。服务端不再有偷偷调用模型的能力所有模型调用都发生在客户端可见、可审计、可限流的路径上。3.3 实际迁移时的写法调整迁移时我做了三件事把服务端里所有主动 Sampling 的代码删掉改成在响应里返回一个需要协助的标记和所需输入。在客户端加了一个统一的协助处理器负责调用模型、做格式校验、做次数限制。给协助处理器加了最大轮次和超时防止任何形式的循环。第三点特别重要。即使新版把控制权交给了客户端客户端自己也可能写出循环。加一个硬性的轮次上限是我认为迁移后必须做的一件事。注意不要把协助处理器写成收到请求就无脑调模型。至少要校验输入格式、限制单次请求的协助次数、设置整体超时否则你只是把旧版的风险从服务端搬到了客户端。3.4 一个具体的对照示例假设有个工具需要模型帮忙判断一段文本的情感倾向。旧版写法是服务端直接调 Sampling 拿结果。新版写法是服务端返回一个协助请求客户端调模型后把结果回传。{ status: needs_assistance, assistance: { type: text_classification, input: 这段文本……, expected: positive | negative | neutral } }客户端处理后再发一次请求带上协助结果{ assistance_result: { value: positive } }服务端拿到结果继续处理。整个链路里模型调用发生在客户端服务端只负责声明需求。这个模式迁移起来不难难的是改掉服务端什么都能干的思维惯性。4. MRTR 到底解决什么问题以及它和 Session 的本质区别很多人第一次听说 MRTR 会问这不就是换了个名字的 Session 吗不是。这个区别想清楚了整套新设计就通了。4.1 Session 是服务端替你记MRTR 是你自己带着走Session 的本质是服务端维护一份状态客户端只拿一个引用。MRTR 的本质是每一轮往返都由客户端显式发起中间状态由客户端持有。前者把状态放在服务端后者把状态放在客户端。这个区别决定了可扩展性。Session 模式下服务端要扩容就得同步状态MRTR 模式下服务端是无状态的状态在客户端手里扩容就是加机器没有任何同步成本。4.2 一次 MRTR 的完整生命周期我用一个需要用户确认才能继续的场景来说明。旧版靠 Session 存等待确认这个状态新版靠 MRTR客户端发起请求 A。服务端处理到需要确认的步骤返回一个需要继续的响应附带继续所需的上下文。客户端拿到上下文向用户展示确认界面。用户确认后客户端发起请求 B带上第 2 步返回的上下文。服务端基于上下文继续处理返回最终结果。整个过程中服务端没有存任何东西。第 2 步返回的上下文就是全部状态客户端负责保管并在第 4 步带回来。4.3 上下文该放什么、不该放什么这是实操里最容易出问题的地方。我的经验是上下文只放服务端继续处理所必需的最小信息不要把整个中间状态都塞进去。原因有两个。一是上下文要在网络上传输太大影响性能二是上下文可能被客户端篡改放太多敏感信息有风险。我一般只放任务标识、当前步骤、必要的业务参数、一个用于校验完整性的签名。提示如果你的上下文里出现了用户完整档案数据库连接串这类东西说明设计有问题。上下文应该是轻量的、可校验的、不敏感的。4.4 和旧版应用层自己拼多轮的区别旧版没有 MRTR 的时候大家也能实现多轮——无非是应用层自己定义一套继续的约定。但那是应用层的私有约定换个客户端就不认了。MRTR 把它提到了协议层意味着任何遵循协议的客户端都能理解需要继续这个语义互操作性完全不同。这也是为什么我说 MRTR 是这次变更里最重要的部分。它让无状态模型下的复杂交互有了标准表达而不是各写各的。5. 迁移实操我按什么顺序改、每步验证什么讲完原理说点具体的。我迁移时没有一次性全改而是分阶段每阶段都能独立验证。这个顺序你可以直接参考。5.1 第一步先让请求能通不管业务第一步目标很单纯让一个最简单的请求在新版下跑通。做法是把旧代码里所有和 Session 相关的逻辑先注释掉请求构造改成每次全量组装然后发一个最基础的工具调用。这一步大概率会遇到字段校验失败按报错逐个补字段即可。不要在这一步纠结业务逻辑先把能通这个底线守住。5.2 第二步把身份和上下文提到每次请求请求能通之后开始处理服务端记不住我的问题。把所有原本依赖 Session 传递的信息逐个提到请求里。这一步工作量最大因为要翻遍所有调用点。我的做法是先列一张表把所有以前靠 Session 传、现在要显式传的字段列出来然后逐个调用点核对。这张表后来成了我迁移文档的核心。原本靠 Session 传递迁移后处理方式用户身份每次请求携带协议版本每次请求携带能力声明每次请求携带或按需携带中间计算结果通过 MRTR 上下文传递订阅关系改为每次请求声明5.3 第三步重写 Sampling 相关逻辑这一步要动的是服务端和客户端两侧。服务端把主动 Sampling 改成返回协助请求客户端加协助处理器。改完之后一定要做压力测试重点看协助处理器会不会被高频触发。我当时的测试方法是故意构造一个会反复触发协助的输入看轮次上限能不能兜住。第一次测的时候上限设得太松跑了二十多轮才停后来收紧到五轮。5.4 第四步把多轮场景改成 MRTR最后处理多轮。把所有靠 Session 存中间状态的多轮逻辑改成 MRTR 的上下文传递。这一步改完整个迁移基本就完成了。验证方法是把服务端重启一次看多轮流程还能不能继续。旧版下重启会丢 Session多轮就断了新版下上下文在客户端重启不影响。这个测试能直观验证无状态改造是否到位。5.5 迁移中最容易忽略的兼容性问题有个坑我差点栽进去新旧版本混用。迁移期间可能有一部分客户端已经升级、一部分还没。如果服务端同时支持新旧两套语义很容易出现旧客户端发来的请求被新逻辑处理的错乱。我的处理方式是迁移期间服务端按版本号分流旧版本请求走旧逻辑新版本请求走新逻辑等所有客户端升级完再下线旧逻辑。这个过渡期比想象中长别急着删旧代码。6. 那些网上教程不会告诉你的坑这部分是我自己踩出来的官方文档里基本不会写但实际迁移时几乎一定会遇到。6.1 别信改个版本号就能跑的说法我见过一些帖子说这次变更兼容性很好改个版本号就行。实测下来完全不是。Session 相关的代码不改改多少版本号都没用。这种说法要么是没真正迁移过要么是只跑了个 hello world。6.2 上下文签名别用弱算法MRTR 的上下文要在客户端和服务端之间来回传如果要做完整性校验签名算法别图省事用弱的。我一开始用了个简单的哈希后来意识到客户端可以轻易伪造上下文赶紧换成了带密钥的校验方式。6.3 协助处理器的限流要按用户维度做我最初把协助处理器的限流做成了全局的结果一个用户疯狂触发协助把其他用户的额度也占了。后来改成按用户维度限流才合理。这个细节在文档里不会提但不做的话线上一定出问题。6.4 无状态不等于可以无限重试无状态模型下请求可以随便重发看起来很适合重试。但要注意如果请求本身有副作用比如写数据无脑重试会造成重复写入。重试策略要区分幂等和非幂等操作这点和有没有 Session 无关但无状态模型下更容易被忽略。6.5 日志要重新设计旧版日志里Session 标识是串联一次会话所有请求的关键。新版没有 Session日志串联要靠你自己在请求里带一个追踪标识。我迁移时忘了这茬结果排查问题时发现日志全是散的根本串不起来。后来在请求构造器里统一加了一个追踪 ID 才解决。7. 重新理解这套协议它到底想让你怎么用迁移完之后回头看我对 MCP 这套设计的理解变了不少。旧版更像是一个方便但脆弱的框架新版更像是一个啰嗦但可靠的协议。这个转向背后其实是一个很朴素的判断在分布式环境里任何隐式状态都是负债。Session 就是典型的隐式状态。它让写 demo 很爽让上生产很痛。新版把它拿掉短期看是增加了使用者的负担——你要自己管上下文、自己管多轮、自己管协助。但长期看这些负担换来了可预测的扩容、可审计的调用、可组合的交互。Sampling 的收紧也是同一个逻辑。让服务端能主动调模型短期看很强大长期看是失控的根源。把控制权交给客户端短期看是麻烦长期看是安全。MRTR 则是这套逻辑的必然产物。既然状态不能放服务端那多轮交互就必须有个地方放状态MRTR 就是那个地方。它不是 Session 的替代品它是无状态世界里的多轮表达方式。所以如果你现在还在用旧教程学 MCP我的建议是先把 Session 和 Sampling 这两块从脑子里清掉然后带着每次请求都是独立的、每次多轮都要显式声明这个前提去读新文档。这个思维切换比记 API 名字重要得多。至于那些还在传的 2025 年教程不是说它们完全没价值——协议的基本概念、工具定义的方式、消息格式的骨架这些变化不大。但凡涉及连接管理、状态传递、模型反向调用的部分一律以新版为准。我自己的做法是把旧笔记里这几块直接划掉重新记了一遍虽然费事但比在错误的基础上打补丁强。最后分享一个我迁移时用的小技巧先写一个最小可跑的端到端示例把无状态、MRTR、协助回调这三条路径都走一遍再拿这个示例去对照旧代码逐块替换。比一上来就改存量代码效率高得多也不容易漏掉某条路径。这个示例后来成了我团队内部迁移的基准新同事上手也是先跑它。
返回列表