
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 应用想调用数据库、文件系统、设计稿、浏览器这些外部能力基本都是各自为战。模型厂商有自己的插件体系工具厂商有自己的 SDKAgent 框架又有自己的工具调用格式集成一个工具动不动就是几百行胶水代码。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 与 Sampling2.1 旧版 Session一次握手建立的连接生命周期在 2024-11-05 版本里MCP 是一个有会话概念的协议。Client 连上 Server 后第一件事是发 initialize 请求带上协议版本号、能力声明和 clientInfoServer 返回自己的 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 的重要性。但问题也随之而来。要维护 sessionServer 就得跟踪连接状态处理会话超时、会话重建、会话过期在 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 传输从HTTPSSE升级为 Streamable HTTP。旧版只能用一条 SSE 通道把 server 的消息推给 client理解成本和使用成本都不低新版改成了更常规的 POST 请求加可选的 SSE 流式响应服务端实现门槛明显下降。如果你之前写过 HTTPSSE 的 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 类替换旧的 HTTPSSE 实现如果是自研实现把服务端改成接收 POST 请求、按需返回 SSE 响应。重新审视能力声明。capabilities 的结构在几次迭代里有调整不要照抄旧教程里的写法以你当前使用的 SDK 类型定义为准。这里我想多提醒一句迁移不是改个版本号就完事而是要把你脑子里的连接状态模型整个换掉。很多迁移后还是各种诡异报错的项目根因都在于代码里残留了 session 的思维。4.2 客户端Client改造清单Client 侧同样有几处绕不开的改动。连接方式从 HTTPSSE 改成 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 收不到请求或连接时好时坏还在用旧版 HTTPSSE 传输迁移到 Streamable HTTP先用 curl 分别测 POST 和 GET 两条路径代码里调用 sampling/createMessage 被拒绝新版已弃用 sampling移除相关逻辑改为 server 内部自行调用模型或把决策交还给 clientDify、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 有关。我自研过一个 HTTPSSE 的小 server迁移到 Streamable HTTP 后只改了 POST 处理没动 GET 推送流。结果 server 单方面推送消息时 client 收不到。新规范里GET 请求负责建立服务端到客户端的单向推送流这条路径不配好很多场景就是半残状态。6. 怎么识别旧教程以及现在该怎么学6.1 旧教程的四个信号要判断一篇 MCP 教程是否还值得看不需要读完扫几个关键词就够了。第一个信号是它大讲 session 生命周期。凡是花大量篇幅讲会话建立、会话状态、会话关闭的基本可以认定是旧版内容。新版的核心是无状态请求session 不再是需要重点讲解的对象。第二个信号是它在教你怎么用 sampling。教程里出现让 server 调用 client 的模型这类表述直接关掉。新版规范里这个能力已经不在核心协议里了照着写出来的代码上线就会被拒。第三个信号是传输方式还写着 HTTPSSE。如果你看到SSE 双向通道EventSource 连接这类描述说明作者写稿时用的是旧传输模型。新版是 Streamable HTTPPOST 为主SSE 只是服务端推送的可选通道。第四个信号是文章里没有提到协议版本号。一篇合格的 MCP 技术文章至少应该告诉你它讲解的是哪个版本的规范。不提版本号、或者只写最新版的基本都是来蹭流量的别指望它帮你避坑。6.2 学习 MCP 的正确姿势我的建议是把教程当成索引把规范原文和 SDK 源码当成唯一真相来源。官方仓库的 CHANGELOG 和规范文档更新是最及时的SDK 里的类型定义就是你写代码时的实时参考。具体路径可以这样先看官方规范里关于 initialize、tools/list、tools/call 这几个核心交互的说明再用官方 SDK 跑一个最小 server 和一个最小 client把两种传输方式都通一遍。之后去看几个知名开源 MCP 项目的源码看他们怎么组织 server、怎么处理能力声明。到这一步你对 MCP 的理解已经超过 90% 的教程作者了。最后再分享一个小技巧每当你看到一篇讲协议的博客先去查它发布的日期对应的协议版本再去官方 CHANGELOG 对照一遍。这个习惯在 MCP 这个更新频率下尤其重要。我自己也已经把先看版本、再学内容当成了默认动作毕竟在这个领域一个月前的知识就可能变成负担。