MCP 这三个字母,最近在开发者圈子里出现频率高到离谱;Session 和 Sampling,则是过去几个月每一篇 MCP 教程里必然会拎出来讲的两个重点。但就在这个月,官方把协议推翻重写了一版:Session 从规范里消失了,Sampling 被移出了核心规范。换句话说,你现在收藏夹里那些《MCP 从入门到精通》,很可能已经停在 2025 年初的版本,照着学,踩坑是迟早的事。
我写这篇内容,是想从自己实际写过 MCP server、也写过 client 的角度,跟你聊清楚这次重写到底改了什么、为什么非要这么改、以及你手头的老代码该怎么迁移。不管你是刚接触 MCP 的新手,还是已经按旧协议写了一批工具的"半老鸟",读完之后至少不会再拿着过时的认知去写新代码。
1. MCP 是什么,以及它为什么值得你重新认识一遍
1.1 一句话讲明白 MCP 的定位
MCP 全称 Model Context Protocol,也就是"模型上下文协议",2024 年 11 月由 Anthropic 以开放规范的形式对外发布。它解决的其实是一个非常朴素的问题:AI 应用和外部工具、数据源之间,到底怎么对接才不用每家各搞一套?
在没有 MCP 之前,AI 应用想调用数据库、文件系统、设计稿、浏览器这些外部能力,基本都是各自为战。模型厂商有自己的插件体系,工具厂商有自己的 SDK,Agent 框架又有自己的工具调用格式,集成一个工具动不动就是几百行胶水代码。MCP 想做的事情,是用一套基于 JSON-RPC 2.0 的协议,把"AI 应用"和"工具/数据"之间的交互标准化。你可以把它理解成 AI 世界的 USB-C:接口统一了,大家插上就能用。
这套协议有几个天然优势。首先它语言无关,Python、TypeScript、Java、Go 都能实现;其次它传输无关,本地用 stdio,远程走 HTTP;最关键的是它支持能力动态发现,Client 通过 tools/list、resources/list、prompts/list 就能知道 Server 能干什么,不用在编译期写死。
1.2 生态为什么在短时间内失控式增长
从 2024 年底开始,MCP 的采用速度确实超出了很多人的预期。Claude Desktop 第一时间支持,Cursor、Codex、Dify、Cherry Studio 等客户端陆续跟进;国内开发者熟悉的通义灵码也宣布可以通过 MCP 连接外部数据源,比如 Oracle 数据库;甚至 IDA、x32dbg 这类逆向分析工具,都有人做出来了 MCP 插件。
生态一热闹,教程就跟着泛滥。MCP 本身入手门槛不高,跑通一个最简 server 可能只需要几十行代码,"人人五分钟上手"的内容自然满天飞。但问题恰恰出在这里:协议迭代太快,教程的更新速度根本跟不上。你能搜到的大部分文章,讲的还是 2024 年 11 月那个版本的协议,而那个版本已经被官方自己推翻了。
1.3 这次重写不是坏消息,反而是协议在成熟的信号
很多人看到"推翻重写"四个字就慌,觉得是不是自己学的白费了。我的看法不太一样。一个协议能在发布后几个月内根据真实使用场景做结构性调整,说明它不是在自嗨,而是在被大量真实项目使用后发现了问题。
这次调整的本质,是 MCP 的"身份"变了。它最初是为桌面客户端这类"可信、一对一、长连接"场景设计的,但现在它要服务的是公网环境下的无状态工具调用、serverless 部署、多 server 并行连接。架构假设变了,协议不变才奇怪。
2. 旧版协议的两大支柱:Session 与 Sampling
2.1 旧版 Session:一次握手建立的"连接生命周期"
在 2024-11-05 版本里,MCP 是一个有会话概念的协议。Client 连上 Server 后,第一件事是发 initialize 请求,带上协议版本号、能力声明和 clientInfo;Server 返回自己的 protocolVersion、capabilities 和 serverInfo;然后 Client 再补一个 initialized 通知,整个 session 才算建立起来。
{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2024-11-05", "capabilities": {}, "clientInfo": { "name": "demo-client", "version": "0.1.0" } } }这个 session 不是摆设。它代表着一份"连接状态":在同一个 session 里,协议版本、双方能力、启用了哪些特性都是锁定的,任何一方发送了超出协商范围的请求,协议层面就可以报错。所以旧教程里几乎都会画一张"初始化 -> 操作 -> 关闭"的时序图,反复强调 session 的重要性。
但问题也随之而来。要维护 session,Server 就得跟踪连接状态,处理会话超时、会话重建、会话过期;在 HTTP 传输下还要考虑 session id 怎么传递。AI Agent 的真实使用场景恰恰是"频繁连接、频繁断开、一次请求完成一个任务",为了一个会话状态搞这么多基础设施,成本高得不成比例。
2.2 旧版 Sampling:让 Server 反过来"借"客户端的模型
旧版还有一个看起来特别酷的能力,叫 Sampling。常规流程是 Client 调用 Server 的 tools,但 Sampling 把这个方向反过来了:Server 可以通过 sampling/createMessage 请求,让 Client 调用自己的大模型能力,生成一段内容返回给 Server 继续处理。
{ "jsonrpc": "2.0", "id": 2, "method": "sampling/createMessage", "params": { "messages": [ { "role": "user", "content": { "type": "text", "text": "请用三句话总结这份日志" } } ], "maxTokens": 500 } }举个例子,旧教程里很常见的一个演示是:文件系统 server 读了一大堆日志,然后自己"思考"了一下,向 client 发起 sampling 请求,让 client 调用 Claude 或 GPT 做摘要,把摘要拿回来后再返回给用户。
这个能力听着很灵活,但它的边界问题非常严重。谁为这次模型调用付费?模型选择权在谁手上?如果 server 是第三方,理论上它可以借客户端的模型预算跑自己的任务,这在成本和权限上都是巨大的风险。Sampling 本质上是把"模型调用权"放在了不该拿到的角色手上。
2.3 为什么旧设计在当时看是合理的
我不是想用今天的眼光去嘲笑旧设计。MCP 最初是给 Claude Desktop 这类桌面应用用的,桌面场景本来就是可信、长连接、一对一的。在这个假设下,有 session、允许 sampling 完全说得通,甚至可以说是很自然的架构选择。
真正的问题出在协议出圈之后。当 MCP 被放到公网环境、被 serverless 服务使用、被一个 agent 同时连接几十个 server 时,"一个可信桌面应用里的会话"这个假设就不成立了。协议不是写错了,而是它面对的世界变了。
3. 2025 年 3 月的重写:到底改了什么、为什么这么改
3.1 Session 移除:从"连接状态机"走向无状态化
2025-03-26 这个版本,规范最大的变化就是取消了 protocol 层面的 session 概念。注意,不是说不能连接、不能初始化了——initialize 握手仍然存在,用来协商协议版本和能力;但协议不再维护一个贯穿连接周期、处处生效的会话状态。每次请求都是独立的,自身信息完整就够了。
打个比方:旧版是你在银行开了个户头,每笔流水都要挂在这个户头上;新版是拿身份证到柜台办一笔算一笔,事情办完就完,不需要账户状态。Server 不用再关心"当前连接处于哪个阶段",这大大降低了实现复杂度和部署成本。
为什么这么改?三个原因很现实。第一,服务端要支持 serverless 和横向扩展,如果每来一个连接都要在内存里维护 session 状态,负载均衡、故障恢复都会非常痛苦。第二,AI 客户端经常同时连接大量 server,每个 server 都维护状态,客户端和服务端的内存、CPU 开销都受不了。第三,stdio 和 HTTP 两种传输方式在"无状态"模型下行为更容易统一,不用为不同传输维护两套状态逻辑。
3.2 Sampling 弃用:能力边界重新划清
新版规范把 sampling 从核心协议里移除了,官方把它降级为一个独立的 proposal,需要重新设计、充分论证后再考虑是否回归核心。
我前面说过,sampling 的致命伤是模型调用权归属不清。在新的协议哲学里,边界变得非常明确:Server 负责提供能力和数据,Client 负责思考和决策。如果一个 server 需要"自己思考一下"才能把任务做完,说明这个任务应该交给 client 侧的 agent 编排,而不是让 server 偷偷调用别人家的模型。
不过我也想说句公道话:Sampling 的想法本身不坏,"服务端在特定场景请求模型生成"确实有真实需求,只是实现方式放错了位置。如果你真的有这类需求,短期内更务实的做法是:server 自己配置模型服务的 API key,在 server 内部自己调用模型,把成本和权限握在自己手里。虽然麻烦一点,但边界清晰、不出事。
3.3 容易被忽略的并行变化:传输方式与批处理
Session 和 Sampling 是重写的两个主角,但新版还顺手改了两个容易被忽略、实际影响很大的点。
一个是 HTTP 传输从"HTTP+SSE"升级为 Streamable HTTP。旧版只能用一条 SSE 通道把 server 的消息推给 client,理解成本和使用成本都不低;新版改成了更常规的 POST 请求加可选的 SSE 流式响应,服务端实现门槛明显下降。如果你之前写过 HTTP+SSE 的 client,这里需要动手术。
另一个是 JSON-RPC 批处理被正式纳入规范。过去一次只能发一条请求,现在客户端可以一次携带多条消息,显著减少了网络往返。对延迟敏感的场景,这算得上实打实的性能优化。
所以,如果你脑子里的 MCP 还停留在"初始化建 session -> 开 SSE -> 用 sampling",那你需要纠正的认知远不止两个点。
4. 迁移实操:老代码怎么改,新代码怎么跟上
4.1 服务端(Server)改造清单
如果你维护过 MCP server,我的建议是按下面这个顺序逐项过一遍,别跳步。
- 移除 sampling 相关实现。之前处理过 sampling/createMessage 的地方直接删掉。除非你的 server 内部自己调模型,否则不要再往 client 方向发 createMessage 请求。
- 删掉 session 状态管理。不要维护"当前连接是否已初始化"这类协议层状态机。会话校验、会话过期这些逻辑都可以大幅简化,甚至直接去掉。
- 更新协议版本号。把 SDK 升级到支持 2025-03-26 的版本,确保 initialize 响应里返回新版本。很多老 SDK 默认返回的协议版本还是旧的,光升级依赖不换版本号等于没升。
- 迁移 HTTP 传输实现。如果你用的是官方 SDK,找 Streamable HTTP 对应的 server 类替换旧的 HTTP+SSE 实现;如果是自研实现,把服务端改成接收 POST 请求、按需返回 SSE 响应。
- 重新审视能力声明。capabilities 的结构在几次迭代里有调整,不要照抄旧教程里的写法,以你当前使用的 SDK 类型定义为准。
这里我想多提醒一句:迁移不是改个版本号就完事,而是要把你脑子里的"连接状态"模型整个换掉。很多迁移后还是各种诡异报错的项目,根因都在于代码里残留了 session 的思维。
4.2 客户端(Client)改造清单
Client 侧同样有几处绕不开的改动。
- 连接方式从 HTTP+SSE 改成 Streamable HTTP。重点是想清楚怎么监听 server 主动推送的消息。新版仍然有服务端推送,但推送流的生命周期和旧版完全不同。
- 不要依赖 session id。如果你之前写过连接池、会话复用逻辑,现在可以大大简化。每次请求都是独立的,客户端不再需要维护协议层会话。
- 检查批处理的可用性。如果你的应用场景是一次要查多个工具的能力,可以试着用批量请求减少轮次,注意确认你用的语言 SDK 是否暴露了对应接口。
- 建立版本协商逻辑。客户端尽量做到"优先协商新版本,不行就降级到旧版本",这在真实环境里非常重要,因为市面上大量 server 还没跟上新版。
4.3 协议版本协商与兼容性测试
新版保留了 initialize,所以新旧版本之间是可以"谈"的。Server 可以在 initialize 响应里声明自己支持的协议版本列表,Client 会根据双方共同的最高版本进行选择。
我在实测中发现,最稳的做法是客户端实现降级逻辑:先按 2025-03-26 版本发起握手,如果对方响应不支持新版本,再退回 2024-11-05。这样做的好处是,你的工具既能在新生态里跑,也能兼容那些尚未升级的老 server。
测试方面,推荐用一个最小 client 脚本跑通三种情况:新 server 对新 client、新 server 对旧 client、旧 server 对新 client。后两种是问题高发区,很多集成问题都出在"握手成功但后续请求行为不一致"上,单独测一种情况根本发现不了。
5. 常见问题与避坑实录
5.1 问题速查表
| 现象 | 可能原因 | 处理办法 |
|---|---|---|
| 客户端提示初始化失败,或找不到 MCP server | 协议版本不匹配,或 server 还带旧 session 状态逻辑 | 更新 SDK,检查 protocolVersion 协商结果,清理 session 相关代码 |
| server 收不到请求,或连接时好时坏 | 还在用旧版 HTTP+SSE 传输 | 迁移到 Streamable HTTP,先用 curl 分别测 POST 和 GET 两条路径 |
| 代码里调用 sampling/createMessage 被拒绝 | 新版已弃用 sampling | 移除相关逻辑,改为 server 内部自行调用模型,或把决策交还给 client |
| Dify、Cherry Studio 接不上自建 server | 客户端和服务端的新旧版本协商失败 | 查客户端版本是否已支持新规范,必要时临时让 server 兼容旧版本 |
| 本地开发时请求经常超时 | 旧代码每次请求都重新握手、重建状态 | 改为无状态轻量请求,确认没有多余的生命周期管理逻辑 |
还有一个很有意思的现象:我在排查问题的时候,看到有人在问 opengauss 的 "session unused timeout" 报错,还有人在问虚拟机 "the vm session was closed" 之类的提示。这些名词都叫 session,但和 MCP 的 session 完全是两码事。术语重载也是这次改版容易误导人的地方——不是代码里出现 session 就是 MCP 的 session,先分清上下文再动手排查。
5.2 我在迁移中实际踩过的坑
第一个坑是"升级了 SDK 但没升协议版本号"。我当时把一个 Python 项目从旧 SDK 切到新版,以为万事大吉,结果 client 一连就报版本不兼容。查了半天才发现,SDK 新了,但我代码里初始化时硬编码的 protocolVersion 还是 2024-11-05。版本号写在代码里的项目,迁移时一定要全局搜一遍。
第二个坑是旧代码里的状态残留。我的一个 server 之前写了"未初始化则拒绝所有请求"的逻辑,迁移时看着觉得没问题就留下了。结果在新协议下,client 发一个 tools/list 过来,server 因为"还没完成完整握手"直接拒绝。排查了很久才发现是这层多余的状态校验在捣乱。
第三个坑和 SSE 有关。我自研过一个 HTTP+SSE 的小 server,迁移到 Streamable HTTP 后只改了 POST 处理,没动 GET 推送流。结果 server 单方面推送消息时 client 收不到。新规范里,GET 请求负责建立服务端到客户端的单向推送流,这条路径不配好,很多场景就是"半残"状态。
6. 怎么识别旧教程,以及现在该怎么学
6.1 旧教程的四个信号
要判断一篇 MCP 教程是否还值得看,不需要读完,扫几个关键词就够了。
第一个信号是它大讲 session 生命周期。凡是花大量篇幅讲"会话建立、会话状态、会话关闭"的,基本可以认定是旧版内容。新版的核心是"无状态请求",session 不再是需要重点讲解的对象。
第二个信号是它在教你怎么用 sampling。教程里出现"让 server 调用 client 的模型"这类表述,直接关掉。新版规范里,这个能力已经不在核心协议里了,照着写出来的代码上线就会被拒。
第三个信号是传输方式还写着 HTTP+SSE。如果你看到"SSE 双向通道""EventSource 连接"这类描述,说明作者写稿时用的是旧传输模型。新版是 Streamable HTTP,POST 为主,SSE 只是服务端推送的可选通道。
第四个信号是文章里没有提到协议版本号。一篇合格的 MCP 技术文章,至少应该告诉你它讲解的是哪个版本的规范。不提版本号、或者只写"最新版"的,基本都是来蹭流量的,别指望它帮你避坑。
6.2 学习 MCP 的正确姿势
我的建议是,把教程当成索引,把规范原文和 SDK 源码当成唯一真相来源。官方仓库的 CHANGELOG 和规范文档更新是最及时的,SDK 里的类型定义就是你写代码时的实时参考。
具体路径可以这样:先看官方规范里关于 initialize、tools/list、tools/call 这几个核心交互的说明,再用官方 SDK 跑一个最小 server 和一个最小 client,把两种传输方式都通一遍。之后去看几个知名开源 MCP 项目的源码,看他们怎么组织 server、怎么处理能力声明。到这一步,你对 MCP 的理解已经超过 90% 的教程作者了。
最后再分享一个小技巧:每当你看到一篇讲协议的博客,先去查它发布的日期对应的协议版本,再去官方 CHANGELOG 对照一遍。这个习惯在 MCP 这个更新频率下尤其重要。我自己也已经把"先看版本、再学内容"当成了默认动作,毕竟在这个领域,一个月前的知识就可能变成负担。