尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

MCP协议重写:Session与Sampling废弃,Stateless与MRTR迁移实战

MCP协议重写:Session与Sampling废弃,Stateless与MRTR迁移实战 1. 当MCP 重写的消息传到我这里时我第一反应是去看自己的代码上个月有个朋友在群里甩了一句MCP 把自己推翻重写了Session 没了、Sampling 废了我当时正在调一个基于 MCP 的本地工具链第一反应不是去翻文档而是打开自己的项目目录看看哪些地方还在依赖旧的会话模型。结果不出所料三个核心模块里有两个直接踩在废弃接口上。这件事值得认真聊一聊。MCPModel Context Protocol在过去一年里几乎成了本地工具与大模型之间对接的事实标准从 IDE 插件到浏览器自动化从数据库查询到设计稿读取到处都能看到它的身影。但正因为生态铺得太快大量教程、示例代码、第三方封装都停留在 2025 年那套有状态会话 Sampling 回调的范式里。协议本身一改这些内容就从能跑变成了能编译但行为诡异。这篇文章不是协议翻译也不是官方文档的复述。我想做的是把这次重写背后的核心领域变化讲清楚Session 为什么被拿掉、Sampling 为什么被判定为废、MRTR 和 Stateless 这两个词到底在解决什么问题以及一个真实的从业者在迁移过程中会撞上哪些坑。适合已经在用 MCP 做集成、或者正准备入坑但不想学一套马上过时的东西的读者。读完你应该能判断自己手上的代码属于必须改还是可以缓一缓。2. Session 被拿掉不是删功能是把状态责任还给调用方2.1 旧模型里 Session 到底承担了什么要理解这次重写得先回到旧模型。在 2025 年那套设计里MCP 的客户端和服务端之间会建立一个长生命周期的会话Session这个会话承担了三件事上下文绑定把一次对话或一次任务的所有请求串在同一个逻辑通道里服务端可以记住这个客户端之前问过什么。能力协商初始化时双方交换支持的能力列表后续调用都基于这份协商结果。生命周期管理连接建立、心跳、超时、断线重连全都挂在 Session 对象上。听起来很合理对吧问题出在部署形态的多样化上。当 MCP 服务端从本地单进程扩展到容器化、多副本、Serverless之后Session 就成了一个烫手山芋。你没法保证同一个客户端的连续请求落到同一个副本上于是要么引入粘性路由要么把 Session 状态外置到 Redis 之类的存储里。两条路都增加了复杂度而且和 MCP 想做的轻量工具协议定位越来越远。2.2 Stateless 之后谁负责记住上下文新模型的核心变化是协议层不再维护会话状态每一次请求都是自包含的。这意味着服务端不再假设你上次来过所有必要信息必须在单次请求里带齐。这听起来像是把负担甩给了调用方但实际上它换来的是部署自由度。任何一个副本都能处理任何一个请求水平扩展变成纯粹的无状态扩展不需要粘性会话不需要共享 Session 存储。那上下文怎么办答案是由调用方通常是宿主应用或 Agent 框架自己管理需要时把相关历史作为请求参数显式传入。这个转变的本质是MCP 从有状态的对话协议退回到无状态的工具调用协议把状态编排的权力交还给上层。注意这不代表你不能再做多轮交互而是说多轮交互的记忆不再由协议层隐式提供你得自己显式传递。很多旧教程里那种初始化一次然后连续调用的写法在新模型下要么报错要么行为不可预期。2.3 迁移时最容易忽略的一个细节我在迁移时踩的第一个坑是初始化握手的位置。旧代码里客户端连上之后会先做一次能力协商然后所有后续调用都复用这次协商的结果。新模型下如果你还按这个顺序写会发现第一次调用能过第二次开始就出现能力不匹配的报错。原因很简单无状态意味着每次请求都要重新声明自己需要的能力或者至少要在请求头里带上足够的元信息。我当时的修法是抽了一个buildRequestContext()函数把能力声明、超时、追踪 ID 这些原本挂在 Session 上的东西每次调用时重新组装。改完之后代码反而更清晰了因为依赖关系从隐式的会话状态变成了显式的请求参数。这里有个经验迁移时不要试图去模拟 Session。我见过有人写了个SessionManager把状态存在内存 Map 里key 用客户端 ID本质上是在应用层重建了一个有状态层。短期能跑但一旦多副本部署就原形毕露。正确的做法是接受无状态把状态管理上移到真正该管它的地方。3. Sampling 被判废背后是控制权的一次重新分配3.1 Sampling 原本想解决什么问题Sampling 在旧模型里是一个挺有野心的设计它允许服务端反过来请求客户端宿主去调用大模型。也就是说一个 MCP 工具在执行过程中如果需要模型推理不用自己内置模型调用逻辑而是发一个 Sampling 请求给宿主由宿主决定用哪个模型、怎么调、结果怎么回传。这个设计的初衷是解耦工具作者不需要关心模型选型和 API 密钥宿主统一管理模型访问。听起来很美但实际落地时问题不少。3.2 为什么它在新模型里站不住Sampling 被废弃我认为核心原因是它和 Stateless 天然冲突。Sampling 是一个典型的反向、异步、有状态的交互服务端发起请求等待宿主回调这中间必然要维护一个等待中的请求状态。在无状态模型下这个等待状态无处安放。除此之外还有几个现实问题问题具体表现控制流反转服务端主动请求宿主打破了客户端调用、服务端响应的简单模型安全边界模糊服务端能间接驱动宿主的模型调用权限模型难以收敛调试困难一次工具调用里嵌套了模型调用链路追踪复杂生态分裂不同宿主对 Sampling 的支持程度参差不齐工具作者难以保证行为一致我个人的判断是Sampling 想做的事情工具内需要模型能力本身是合理的但用协议层的反向调用来实现是过度设计。新模型下这个需求更适合由工具自己通过标准的模型接口解决或者由宿主在调用工具前就把需要的推理结果准备好。3.3 替代路径把模型调用放回它该在的层迁移 Sampling 相关代码时我的做法是把模型调用从工具内部挪到宿主侧。具体来说原来工具里那段发 Sampling 请求等结果的逻辑改成工具只负责它真正擅长的事比如读文件、查数据库、调 API需要模型判断的部分由宿主在编排层完成。这个改动一开始让我觉得多了一层但实际跑下来反而更可控模型调用的重试、限流、成本统计全都集中在宿主工具本身变得纯粹。如果你现在的架构里 Sampling 用得很重建议先梳理清楚哪些调用是工具真的需要模型哪些只是顺手用了 Sampling后者可以直接砍掉。4. MRTR 和 Stateless 这两个词到底在描述什么4.1 MRTR不是新功能是交互模式的重新定义MRTR 这个词在热词列表里出现但很多人第一次看到会懵。它描述的是一种**多轮请求-响应Multi-Round Request-Response**的交互模式但关键在于每一轮都是独立的、无状态的请求。和旧模型的多轮对话相比区别在于旧模型多轮共享一个 Session服务端隐式知道上下文。MRTR多轮之间没有协议层的关联每一轮请求自带完整上下文服务端只负责处理当前这一轮。这带来的直接好处是可重放、可缓存、可并行。因为每个请求都是自包含的你可以把请求丢进队列、可以重试、可以在任意副本上执行不用担心这个请求依赖上一个请求留下的状态。4.2 Stateless 不是没有状态是状态不归协议管这里要澄清一个常见误解。很多人一听 Stateless 就以为整个系统不能有状态这是错的。Stateless 描述的是协议层的行为协议本身不维护跨请求的状态。至于你的应用需不需要状态、状态存在哪那是应用层的事。我见过有团队因为这个词走了极端把所有缓存都砍了结果性能暴跌。正确的理解是协议无状态应用可以有状态但状态的所有权和生命周期要明确。比如对话历史存在宿主的内存或数据库里每次调用工具时按需传入这就是一个健康的无状态协议 有状态应用的组合。4.3 一张表看清新旧模型的差异维度旧模型2025 范式新模型Stateless MRTR会话协议层维护长生命周期 Session无协议层会话请求自包含上下文服务端隐式记忆调用方显式传递反向调用Sampling 支持服务端请求宿主废弃模型调用回归宿主层部署需要粘性路由或共享状态存储任意副本可处理任意请求多轮交互依赖 Session 串联每轮独立靠调用方编排调试链路嵌套追踪复杂单请求可独立追踪和重放这张表是我自己在迁移时画的贴在显示器边上对照着改代码比翻文档快得多。建议你也画一张把每个模块映射到旧模型依赖点上逐个确认。5. 迁移实操从旧代码到新模型的完整路径5.1 第一步盘点所有 Session 依赖点迁移最忌讳上来就改代码。我的做法是先做一次依赖盘点把所有和 Session 相关的调用点列出来。具体找这几类初始化握手相关的代码initialize、capabilities协商任何形式的sessionId传递依赖上次调用结果的逻辑Sampling 相关的请求和回调处理盘点完之后你会发现真正需要改的核心点其实不多大部分是外围的样板代码。我那个项目盘下来Session 相关代码占了大概 30%但其中一半是可以直接删掉的初始化逻辑。5.2 第二步把隐式状态改成显式参数这是迁移的核心工作。原来挂在 Session 上的东西现在要变成请求参数。我整理了一个对照旧 Session 上的状态新模型下的归属能力协商结果每次请求的元信息字段对话历史调用方维护按需传入追踪 ID每次请求生成随请求传递超时配置请求级配置或客户端全局配置认证信息请求头每次携带改的时候有个技巧先加新参数再删旧逻辑。不要一次性把 Session 代码删干净而是先让新参数路径跑通确认行为一致后再清理旧代码。这样出问题时能快速回滚。5.3 第三步处理 Sampling 的替代方案如果你的代码里有 Sampling迁移会稍微麻烦一点。我的处理顺序是分类把 Sampling 调用分成工具必需和顺手用的两类。砍掉顺手用的这类通常可以直接删工具本身不需要模型能力。重构必需的把模型调用上移到宿主编排层工具只接收已经处理好的输入。验证重点测多轮场景确认没有隐式的状态依赖。这里有个坑要提醒有些 Sampling 调用是隐式触发的比如某个工具内部在特定条件下才会发 Sampling 请求。这种最容易漏掉建议用日志把所有 Sampling 触发点打出来逐个确认。5.4 第四步多副本部署验证无状态的最大价值在多副本部署时才体现出来。迁移完成后我特意做了一次多副本压测起三个副本不加粘性路由跑一轮完整的多轮交互。结果第一次跑就暴露了问题有个模块在内存里缓存了当前用户的上一次查询多副本下这个缓存命中率极低导致行为不一致。这个问题在单副本时完全看不出来。修法很简单把这个缓存挪到请求参数里由调用方维护。提示迁移后一定要做多副本验证单副本跑通不代表无状态改造成功。这是我在这次迁移里最有价值的一条经验。6. 那些还停在 2025 的教程会把你带进哪些沟里6.1 教程滞后是常态但这次滞后特别危险技术教程滞后于协议演进是常态但这次 MCP 的重写有个特殊之处旧教程的代码往往能编译通过但运行时行为是错的。这比直接报错更危险因为你会以为是自己逻辑写错了花大量时间在错误的方向上排查。我见过最典型的一个例子某教程教你在初始化时协商能力然后连续调用多个工具。在新模型下第一次调用正常第二次开始服务端返回的能力列表和请求不匹配但错误信息很隐晦看起来像是参数问题。有人为此调了两天最后才发现是模型变了。6.2 识别过时教程的几个信号我现在看 MCP 相关教程会先扫这几个信号命中任何一个就警惕出现sessionId或类似的会话标识传递有initialize之后连续调用多个工具的示例提到 Sampling 作为推荐用法强调保持长连接或会话复用示例代码里服务端有内存状态这些信号不代表教程完全没用但至少说明它的核心模型是旧的照抄会出问题。正确的用法是看它的思路不看它的代码。6.3 一个真实的排查案例说个我自己的经历。迁移后有个工具在特定输入下会返回空结果其他输入都正常。我一开始怀疑是参数解析问题查了半天没头绪。后来把请求日志打出来对比发现出问题的那次请求缺少了一个能力声明字段而这个字段在旧模型里是由 Session 协商隐式带上的。问题根源是我在迁移时漏改了一个分支那个分支走的还是旧的请求构造逻辑。因为大部分输入走的是新分支所以只有特定路径才暴露。这个案例说明迁移时一定要覆盖所有代码分支不能只测主路径。7. 无状态之后架构上反而多出来的几个好处7.1 测试变得异常简单无状态改造完成后我最大的感受是测试变简单了。旧模型下测一个工具得先建立会话、协商能力、维护上下文测试代码比业务代码还长。新模型下每个测试用例就是一个自包含的请求构造好参数直接调断言返回结果就行。我那个项目的测试代码量在迁移后减少了大概 40%而且稳定性明显提升之前那种单独跑能过、一起跑就挂的会话污染问题彻底消失了。7.2 缓存和重试有了明确的边界无状态请求天然适合缓存和重试。因为每个请求自包含你可以对幂等的查询类请求做结果缓存key 就是请求内容对失败的请求直接重试不用担心状态不一致把请求丢进队列异步处理不需要保证顺序这些在旧模型下都要小心翼翼地处理会话边界现在变成了默认能力。我在迁移后给几个高频查询工具加了缓存响应时间降了一半多。7.3 水平扩展不再需要特殊配置这是最直接的好处。旧模型下扩展副本要考虑粘性路由新模型下加副本就是加副本负载均衡随便配。对于需要应对突发流量的场景这个差异是决定性的。8. 给还在观望的人现在该做什么不该做什么8.1 该做的先盘点再动手如果你手上已经有基于 MCP 的项目我的建议是先做依赖盘点不要急着改。把 Session 依赖点、Sampling 调用点、隐式状态依赖点全部列出来评估工作量。很多时候你会发现真正需要改的核心逻辑没那么多大部分是样板代码。盘点的时候顺便确认一下你的部署形态如果一直是单进程本地跑迁移的紧迫性没那么高如果有多副本或 Serverless 的计划那这次重写其实是帮你扫清了障碍越早迁越好。8.2 不该做的不要试图兼容两套模型我见过有人想写一个适配层同时支持新旧两种模型。这个想法听起来聪明实际是灾难。两套模型的假设根本冲突一个有状态一个无状态适配层会变成一堆条件判断维护成本极高而且很容易在边界情况下出诡异 bug。正确的做法是选定新模型一次性迁完。旧代码该删就删不要留兼容分支。我迁移时狠心删掉了所有旧路径虽然当时心疼但后面维护起来轻松太多。8.3 关于学习路径的建议如果你是新入坑直接学新模型别碰旧教程。新模型的概念其实更少没有 Session、没有 Sampling上手更快。重点理解三件事请求自包含、状态归调用方、模型调用在宿主层。这三条想明白了剩下的都是细节。如果你是从旧模型迁过来的重点不是学新概念而是改掉旧习惯。最难改的是那种连上之后就能连续调用的思维定式一旦接受每次请求都是独立的后面就顺了。9. 我在这次迁移里最后悔和最庆幸的两件事最后说点个人体会。这次迁移我最后悔的是一开始试图保留 Session 的抽象写了个包装层去模拟会话行为。结果多花了一周时间最后还是推倒重来。如果一开始就接受无状态直接改能省下这一周。最庆幸的是坚持做了多副本验证。单副本跑通的时候我一度以为迁移完成了多副本一测才发现内存缓存的问题。如果直接上线这个 bug 会在流量上来后才暴露排查成本高得多。还有个小的经验迁移期间把新旧请求的日志格式统一方便对比。我在排查那个特定输入返回空结果的问题时就是靠对比新旧日志才快速定位到缺失字段的。日志格式统一这个习惯值得在平时就养成。MCP 这次重写表面上是删了两个特性实质上是把协议拉回到轻量、无状态、可组合的定位上。对于做集成的人来说短期有迁移成本长期是减负。那些还停在 2025 的教程该放就放新模型的概念更少、边界更清晰学起来其实更快。
返回列表