☰
MCP协议重写:Session与Sampling废弃,MRTR与无状态架构迁移指南
2026/10/8 10:14:18 网站建设 项目流程

1. 这次改版到底动了谁的奶酪

MCP 把自己推翻重写了,这句话听起来像标题党,但如果你最近半年一直在跟 MCP 打交道,应该能感觉到那种“昨天还能跑,今天全报错”的窒息感。我上个月把一套跑了小半年的 MCP 工具链升级到新版本,结果 Session 相关的代码全部失效,Sampling 的调用直接返回废弃警告,整个链路像被人抽掉了地基。那一刻我才意识到,这不是小版本迭代,这是一次伤筋动骨的重构。

先说清楚 MCP 是什么。MCP 全称 Model Context Protocol,是一套让 AI 模型与外部工具、数据源之间建立标准化连接的协议。你可以把它理解成 AI 世界的“USB 接口标准”——以前每个模型对接每个工具都要写一套私有适配,MCP 想做的就是把这个适配层统一掉。它的核心价值在于:让模型能够以一致的方式调用文件系统、数据库、浏览器、设计软件、调试器等外部能力,而不需要为每个组合重新造轮子。

这次重写的核心变化集中在三个关键词上:Session 没了、Sampling 废了、MRTR 和 Stateless 上位。如果你学的教程还停在 2025 年那套“先建 Session、再走 Sampling、最后拿结果”的流程,那现在基本等于废纸。我写这篇东西的目的很直接:把这次改版的核心逻辑拆开,告诉你哪些东西变了、为什么变、变了之后该怎么写,以及我在迁移过程中踩过的那些坑。适合两类人看——一类是正在用 MCP 做工具集成的开发者,另一类是刚接触 MCP 但不想学到过时知识的新手。

2. 旧架构为什么必须被推翻

2.1 Session 机制的原罪:状态成了负担

旧版 MCP 最核心的设计之一就是 Session。每次客户端和服务器建立连接,都会创建一个 Session 对象,后续所有的请求、上下文、工具调用记录都挂在这个 Session 上。这个设计在单机、单用户、短连接的场景下没问题,甚至还挺优雅——毕竟有状态意味着可以做上下文累积,模型能记住之前调过什么工具、拿到了什么结果。

但问题恰恰出在“有状态”这三个字上。我实际部署过一套 MCP 服务,跑在容器里,前面挂了个负载均衡。结果发现只要请求被分发到不同的实例,Session 就对不上,工具调用直接失败。你可能会说“那就做 Session 共享呗”,好,Redis 存 Session,所有实例读同一个 Redis。能跑,但延迟上来了,而且 Redis 一挂全盘皆输。更别提水平扩展的时候,Session 的同步和过期策略能把人逼疯。

还有一个更隐蔽的问题:Session 的生命周期管理。旧版协议里,Session 的创建、保持、销毁全靠客户端和服务器各自实现,没有强约束。我见过有的实现把 Session 超时设成 30 分钟,有的设成 5 分钟,还有的压根不设超时。结果就是同一个工具在不同客户端上表现完全不一样,调试的时候你根本不知道是工具的问题还是 Session 的问题。

这里插一句我的真实经历:有一次排查一个“工具调用偶发失败”的问题,查了整整两天,最后发现是某个客户端的 Session 清理逻辑有 bug,在高并发下会误删活跃 Session。这种问题在无状态架构下根本不会存在。

2.2 Sampling 的设计缺陷:模型反向调用工具的尴尬

Sampling 是旧版 MCP 里另一个被寄予厚望的功能。它的初衷是让服务器端能够“反向”请求客户端调用模型——比如工具执行到一半,需要模型帮忙做个判断或者生成一段内容,就可以通过 Sampling 发起一个模型调用请求。

听起来很美好,实际用起来一言难尽。首先,Sampling 的调用链路太长了:服务器发起 Sampling 请求 → 客户端接收 → 客户端调用模型 → 模型返回 → 客户端把结果传回服务器 → 服务器继续执行。每一层都可能出问题,而且出了问题很难定位。我有一次调试一个 Sampling 调用,光是确认请求到底卡在哪一层就花了半天。

其次,Sampling 的语义模糊。它到底是同步还是异步?超时怎么算?模型返回的结果格式谁来保证?旧版协议对这些都没有明确规定,导致不同实现之间的行为差异巨大。最要命的是,很多客户端压根不支持 Sampling,或者支持得不完整。你写了一个依赖 Sampling 的工具,换个客户端就跑不起来,这在实际项目里是致命的。

2.3 从 Session 到 Stateless:一次架构范式的转移

新版 MCP 的核心思路可以用一句话概括:把状态从协议层赶到应用层。Session 被彻底移除,每次请求都是独立的、自包含的。这意味着什么?意味着服务器不需要记住任何东西,每个请求里都带着完成这次操作所需的全部信息。

这个转变的本质是从“有状态服务”转向“无状态服务”。做过后端的人都知道,无状态是水平扩展的前提。你可以在任意时刻增加或减少实例,请求打到哪个实例上都一样,不需要做 Session 同步,不需要担心实例宕机导致状态丢失。对于 MCP 这种需要对接大量异构工具、可能部署在各种环境下的协议来说,无状态几乎是唯一正确的选择。

但代价也是有的。以前靠 Session 累积的上下文,现在需要每次请求都带上。以前靠 Sampling 做的反向调用,现在需要换一种方式实现。这就是 MRTR 和新的工具调用范式要解决的问题。

3. 新架构的核心组件拆解

3.1 Stateless 请求模型:每次都是全新的开始

新版 MCP 的请求模型非常干脆:每个请求都是一个完整的 JSON-RPC 消息,里面包含了方法名、参数、以及必要的元数据。服务器收到请求,处理,返回结果,结束。没有握手,没有 Session ID,没有后续的隐式状态。

我刚开始迁移的时候特别不适应,总觉得“这样每次都要传重复的参数,不是很浪费吗”。但实际跑下来发现,这种“浪费”换来的是巨大的简化。以前你需要管理 Session 的创建和销毁,现在不需要了。以前你需要担心 Session 过期导致的长连接断开,现在不需要了。以前你需要处理 Session 冲突和并发问题,现在不需要了。

举个具体的例子。旧版里,如果你想连续调用同一个工具的多个方法,通常的做法是建立一个 Session,然后在 Session 里依次调用。新版里,你直接发多个独立的请求就行,每个请求自带参数。服务器端不需要知道这些请求来自同一个客户端,也不需要维护任何跨请求的状态。

这里有个容易踩的坑:很多人会把“无状态”理解成“不能有上下文”。其实不是的。无状态指的是协议层不维护状态,但你的应用层完全可以自己维护上下文。比如你可以在客户端把之前的调用结果存下来,下次请求的时候作为参数传进去。区别在于,这个上下文是你自己管理的,协议不帮你管。

3.2 MRTR:取代 Sampling 的新范式

MRTR 是这次改版里最值得关注的新概念。它的全称是 Model-Requested Tool Resolution,直译过来就是“模型请求的工具解析”。简单说,它解决的是旧版 Sampling 想解决但没解决好的问题:当工具执行过程中需要模型介入时,该怎么办。

旧版 Sampling 的思路是“服务器反向调用客户端”,这个方向本身就是别扭的。新版 MRTR 把这个方向正过来了:工具在执行过程中,如果需要模型帮忙,就返回一个特殊的“需要模型处理”的响应,客户端收到后调用模型,然后把模型的结果作为新的请求参数再次发起调用。

这个设计的好处非常明显。首先,它完全符合无状态的原则——每次调用都是独立的,不需要维护任何反向通道。其次,它把模型调用的控制权交还给了客户端,客户端可以自己决定用哪个模型、怎么调、超时怎么处理。最后,它的调试链路非常清晰:工具返回“需要模型处理”,客户端调模型,客户端再调工具,每一步都是显式的、可追踪的。

我实际用 MRTR 重写了一个之前依赖 Sampling 的工具,代码量减少了大概三分之一,而且稳定性明显提升。以前 Sampling 调用偶尔会卡住,现在 MRTR 的每一步都有明确的超时和重试逻辑,不会再出现“不知道卡在哪”的情况。

3.3 工具调用协议的变化:从隐式到显式

除了 Session 和 Sampling,新版 MCP 在工具调用协议上也做了不少调整。最核心的变化是:所有工具调用的输入输出都必须是显式声明的。旧版里有些工具会依赖 Session 里的隐式上下文,比如“上次调用传了什么参数”,新版里这种玩法彻底行不通了。

具体来说,新版要求每个工具都必须定义清晰的输入 schema 和输出 schema。输入 schema 描述了调用这个工具需要哪些参数,每个参数的类型、是否必填、默认值是什么。输出 schema 描述了工具返回的结果结构。这样做的好处是,客户端可以在调用之前就知道需要准备什么,也可以在收到结果之后准确地解析。

我一开始觉得这个要求有点繁琐,尤其是对于一些简单的工具,写 schema 的时间比写逻辑的时间还长。但后来发现,这个投入是值得的。有了明确的 schema,客户端的参数校验、错误处理、结果解析都变得非常规范,不会再出现“传了错误的参数类型但服务器不报错”的情况。

4. 迁移实操:从旧版到新版的完整步骤

4.1 第一步:清理所有 Session 相关代码

迁移的第一步,也是最痛苦的一步,就是把所有跟 Session 相关的代码全部删掉。这包括 Session 的创建、获取、更新、销毁,以及所有依赖 Session ID 的逻辑。

我建议你这么做:先在代码里搜索所有包含 “session” 的关键字,把相关的代码块标记出来。然后逐个分析,看这个 Session 到底承担了什么职责。通常来说,Session 里存的东西无非几类:用户身份信息、调用上下文、临时缓存、配置参数。对于每一类,你都需要找到新的存放位置。

用户身份信息:放到每次请求的认证头里,或者作为请求参数显式传递。 调用上下文:如果确实需要跨请求保持,放到客户端的应用层去管理。 临时缓存:考虑用外部缓存服务,或者干脆每次重新获取。 配置参数:作为请求参数传递,或者通过环境变量注入。

这个过程大概花了我两天时间,主要是有些地方的 Session 依赖比较隐蔽,比如某个工具的实现里偷偷读了 Session 里的一个字段,不仔细看根本发现不了。

4.2 第二步:把 Sampling 调用改写为 MRTR 模式

Sampling 的迁移相对直接,但需要理解 MRTR 的交互模式。旧版 Sampling 的代码通常长这样:工具执行到某一步,发现需要模型介入,于是发起一个 Sampling 请求,等待模型返回,然后继续执行。

新版 MRTR 的写法是:工具执行到需要模型介入的地方,直接返回一个特殊的响应,标记为“需要模型处理”,并附上需要模型处理的内容。客户端收到这个响应后,调用模型,拿到结果,然后把结果作为新的参数再次调用同一个工具。工具在第二次调用时,会检测到模型结果已经提供,于是继续执行后续逻辑。

这里的关键是工具需要能够处理“被调用两次”的情况。第一次调用返回“需要模型处理”,第二次调用带着模型结果继续。我通常会在工具的参数里加一个字段来区分这两种情况,比如model_result字段,如果这个字段为空,就返回“需要模型处理”;如果不为空,就使用这个结果继续执行。

4.3 第三步:重新定义工具的输入输出 Schema

这一步是纯体力活,但非常重要。你需要为每个工具定义清晰的输入和输出 schema。我建议用 JSON Schema 来定义,因为 MCP 本身就是基于 JSON-RPC 的,JSON Schema 的兼容性最好。

输入 schema 要明确每个参数的类型、是否必填、默认值、取值范围。输出 schema 要明确返回结果的结构,包括成功时的字段和失败时的错误信息。我一般会把 schema 单独放在一个文件里,方便维护和复用。

实操心得:定义 schema 的时候,尽量把参数设计成“扁平”的,避免深层嵌套。深层嵌套的 schema 写起来麻烦,用起来也容易出错。如果确实需要传递复杂结构,考虑把它序列化成字符串,在工具内部再解析。

4.4 第四步:调整错误处理和重试逻辑

无状态架构下,错误处理和重试逻辑需要重新设计。旧版里,如果一次调用失败,你可以依赖 Session 里的状态来决定怎么重试。新版里,每次调用都是独立的,重试就是重新发一次请求,带上相同的参数。

这听起来简单,但实际做的时候要注意几点。首先,要区分“可重试的错误”和“不可重试的错误”。比如网络超时是可重试的,参数错误是不可重试的。其次,重试要有上限,不能无限重试。我一般设置最多重试三次,每次间隔递增。最后,重试的时候要考虑幂等性——如果工具的操作不是幂等的,重试可能会导致重复执行。

对于 MRTR 模式下的重试,情况稍微复杂一点。如果工具返回了“需要模型处理”,然后客户端调模型失败了,这时候应该重试调模型,而不是重新调工具。我通常会在客户端维护一个简单的状态机,记录当前处于哪个阶段,然后根据阶段决定重试策略。

5. 常见问题与排查技巧实录

5.1 迁移后工具调用返回“方法不存在”

这是最常见的问题,通常是因为工具注册的方式变了。旧版里,工具注册可能依赖 Session 的初始化过程,新版里需要在服务器启动时就完成注册。检查你的工具注册代码,确保它在服务器启动阶段执行,而不是在某个 Session 创建之后。

另一个可能的原因是工具名称的大小写或者命名空间变了。新版对工具名称的格式有更严格的要求,建议全部用小写字母加下划线,避免特殊字符。

5.2 MRTR 调用陷入死循环

这个问题我遇到过两次,都是因为工具在第二次调用时没有正确检测到模型结果已经提供,导致又返回了一次“需要模型处理”。排查方法是:在工具里加日志,打印每次调用时收到的参数,确认model_result字段是否被正确传递。

还有一种可能是客户端在收到“需要模型处理”后,没有正确地把模型结果传回去,而是重新发起了原始请求。检查客户端的 MRTR 处理逻辑,确保它是在原始请求的基础上追加模型结果,而不是重新构造请求。

5.3 无状态导致的性能下降

从有状态迁移到无状态,最直观的感受就是“每次都要传一堆参数,网络开销变大了”。这个问题的解法有几个方向。一是压缩请求体,比如用更紧凑的序列化格式。二是合并请求,如果多个工具调用之间没有依赖关系,可以考虑批量发送。三是客户端缓存,对于一些不常变的参数,可以在客户端缓存,避免重复传输。

但我要说的是,大多数情况下这个性能下降是可以接受的。无状态带来的可扩展性和可维护性提升,远远超过那点网络开销。如果你的场景对延迟极其敏感,那可能需要重新评估是否适合用 MCP。

5.4 常见问题速查表

问题现象可能原因排查方向解决方案
工具调用返回方法不存在工具未在启动时注册检查注册代码执行时机将注册逻辑移到服务器启动阶段
MRTR 死循环未正确检测模型结果打印每次调用的参数确保第二次调用时识别 model_result
请求超时无状态导致参数传输量大检查请求体大小压缩请求体或合并请求
工具返回格式错误Schema 定义不匹配对比实际返回与 Schema更新 Schema 或修正工具输出
并发调用冲突工具实现非线程安全检查工具内部共享状态移除共享状态或加锁

5.5 几个容易忽略的细节

第一个细节是时间戳的处理。旧版里,Session 创建时间可以作为请求的时间基准。新版里没有 Session 了,每个请求需要自己带时间戳,或者由服务器在收到请求时打时间戳。我建议在请求参数里显式传递时间戳,这样可以避免服务器时钟不一致的问题。

第二个细节是请求 ID 的生成。无状态架构下,请求 ID 是追踪调用链的唯一标识。我通常用 UUID 来生成请求 ID,确保全局唯一。客户端在发起请求时生成 ID,服务器在日志里记录这个 ID,排查问题的时候可以通过 ID 把整个链路串起来。

第三个细节是工具版本管理。新版 MCP 对工具的版本有更明确的要求,建议在工具名称或者参数里带上版本号。这样当工具升级时,旧版客户端仍然可以调用旧版工具,不会因为升级导致所有客户端同时失效。

6. 我踩过的坑和给你的建议

6.1 不要试图在协议层保留状态

我一开始迁移的时候,总想着“能不能在请求里塞一个类似 Session ID 的东西,让服务器能认出同一个客户端”。技术上当然可以这么做,但这等于把 Session 又加回来了,只是换了个名字。无状态的好处你就享受不到了。

正确的做法是:把状态管理完全放到应用层。客户端自己维护上下文,每次请求把需要的上下文作为参数传过去。服务器只管处理请求,不关心请求来自谁、之前发生过什么。

6.2 MRTR 的模型调用要设超时

MRTR 模式下,客户端调用模型是一个独立的步骤,这个步骤必须设超时。我见过有的实现没有设超时,结果模型服务响应慢的时候,整个工具调用链路就卡死了。建议给模型调用设置一个合理的超时时间,比如 30 秒,超时后返回错误,让调用方决定是否重试。

6.3 工具 Schema 要写详细

Schema 写得越详细,客户端用起来越方便,出错的时候也越容易定位。我建议至少包含以下信息:每个参数的类型、是否必填、默认值、取值范围、示例值。输出 Schema 要包含成功和失败两种情况的结构。

6.4 日志要打全

无状态架构下,日志是你排查问题的唯一依靠。建议在以下几个地方打日志:请求进入时打印请求 ID 和参数摘要,工具执行关键步骤时打印状态,返回结果时打印结果摘要,发生错误时打印完整错误信息和堆栈。

6.5 测试要覆盖无状态场景

旧版的测试通常依赖 Session 的连续性,新版测试需要模拟无状态场景。具体来说,要测试:同一个工具被独立调用多次是否正常,不同工具之间是否有隐式依赖,并发调用是否安全,请求参数缺失或错误时的行为。

7. 这套新架构还能怎么用

MCP 这次重写之后,适用的场景其实变多了。以前因为 Session 和 Sampling 的限制,MCP 更适合单机、单用户的场景。现在无状态了,它可以跑在 Serverless 环境里,可以水平扩展,可以对接更多的客户端。

我最近在尝试的一个方向是把 MCP 工具部署成边缘函数,每个请求独立处理,不需要维护任何状态。这样部署成本极低,而且天然支持高并发。另一个方向是把 MCP 和现有的 API 网关结合,让 MCP 工具成为网关的一个后端服务,统一做认证、限流、监控。

还有一个值得关注的点是 MRTR 带来的新交互模式。以前工具和模型的交互是单向的——工具执行,返回结果。现在通过 MRTR,工具可以在执行过程中“暂停”,请求模型介入,然后继续执行。这打开了很多新的可能性,比如工具可以在执行过程中做动态决策,根据模型的分析结果选择不同的执行路径。

最后分享一个小技巧:如果你在迁移过程中遇到不确定的地方,先把工具的逻辑简化到最小可运行状态,确认基本的无状态调用能跑通,然后再逐步加回复杂的逻辑。这样出问题的时候,排查范围小,定位快。我迁移第一个工具的时候花了整整一天,第二个工具只用了两个小时,就是因为第二个工具我是从最小状态开始一步步加上去的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询