首页 / 资讯中心 / 文章详情

MCP协议新版解读:无状态架构迁移与旧教程淘汰指南

MCP协议新版解读:无状态架构迁移与旧教程淘汰指南 ★ FEATURED ARTICLE
MCP 最近这波重写动静比很多人想象中大。过去你看到的大多数教程还在教你建立 Session、维护会话状态、通过 Sampling 让服务端反向调用客户端的大模型生成内容可上个月新规范一出来Session 被移出协议核心Sampling 被明确废弃。我最早是在公司内部笔记本上发现的——按老教程写的 demo 突然连不上翻 spec 才发现不是 bug是规范变了。今天这篇不是新闻复述而是把我改代码、迁移服务、踩坑的过程拆开给你看你手里的旧教程哪些还能用、哪些必须扔读完就知道了。1. 这次改版不是小修小补是把协议“扁平化”了先别急着骂。MCP 从诞生到现在一直处于快速演进期2024 年底的 snapshot、2025 年早期的版本和现在的规范相比已经像两代协议。很多开发者看到Session 没了、Sampling 废了第一反应是功能缩水但如果你把新旧规范并排看会发现这其实是一次目标明确的架构收敛把服务端有状态的长连接协议改造成客户端无状态的短请求协议。1.1 Session 被砍掉的真正原因老版本里Session 是协议的一等公民。客户端通过 initialize 握手建立会话服务端保存会话状态包括 roots 列表、资源订阅关系、能力协商结果甚至还有 session id后面的请求都要带着这个 id 走。这套模型在桌面端单用户场景下没问题但一旦上生产就非常难受。首先是水平扩展的问题。Session 意味着服务端的每个实例都要维护内存状态负载均衡器必须开启 sticky session否则请求打到另一个实例就找不到上下文。其次是长连接的心跳和超时处理HTTP 场景下跨网关、代理非常容易断断一个 session 就要重新初始化客户端和服务端都要写一堆重连逻辑。最后是安全和权限模型老协议的 session 一旦建立状态就一直有效权限变更不能实时生效。新版直接把 Session 从核心规范里摘出去改成每次请求独立、状态自己带或存服务端外部存储的模型。initialize 仍然保留但它的作用不再是建立长期会话而是做一次单向的能力协商和版本确认。之后每个 JSON-RPC 请求都是自包含的服务端不需要记住你是谁来决定怎么回复而是根据请求里携带的凭证和上下文直接响应。这个改动过后服务端可以随便横向扩容网关也不需要做会话粘滞架构上轻了一大截。1.2 Sampling 为什么成为弃子Sampling 在旧规范里是少数让我一直觉得不舒服的设计。它的本意很好让服务端在需要调模型时通过协议请求客户端帮忙调用本地的模型避免服务端直接持有模型 API key。但在实际使用中它带来的问题远大于价值。从安全角度说Sampling 是一个天然的滥用面。服务端发起 createMessage 请求客户端必须帮它执行一次模型推理那 tokens 算谁的用户会为服务端的每次请求悄悄买单。更严重的是如果服务端被恶意控制它可以构造大量的模型请求来耗光客户端额度甚至通过精心构造的 prompt 对模型做注入。从架构职责说Sampling 也让边界变模糊。一个服务端本来应该返回结构化数据和工具执行结果却反过来去指导客户端如何生成自然语言这种服务端越权在现代 LLM 应用里会导致调试困难——服务端代码在跑但真正消耗的模型调用却发生在用户本地客户端里日志不完整成本不可控。新版把 Sampling 从协议能力里移除逻辑就清爽了服务端把数据准备好呈现和解释是客户端模型的工作。你需要摘要、转化、渲染那就在客户端处理服务端需要模型能力那就通过工具把数据交给外部模型服务而不是回手跟客户端要。这只是一种职责重新分配不是能力后退。2. 旧版 vs 新版一张表看懂核心差异与其看长篇 breaking changes 说明不如直接对照新旧两版的协议行为。我把自己迁移过程中最关注的几个点整理成了下面的表方便你有事没事拿出来对照。对比维度旧版Session Sampling 时代新版无状态、无 Sampling连接生命周期initialize 建立长期 Session之后复用initialize 只做能力协商请求之间无绑定关系会话标识服务端分配 session id后续请求携带无 session id靠请求自身凭证token、签名识别服务端状态内存里保存 roots、订阅关系、能力结果不保存协议级状态状态放数据库/缓存或由客户端传入资源订阅服务端通过 Session 推送资源变更无长连接推送改为客户端拉取或服务端主动回调接口Sampling服务端通过 sampling/createMessage 反向请求客户端推理移除该能力服务端只返回结构化数据模型调用由客户端侧完成错误处理会话级错误如 session terminated请求级错误一次请求的失败不影响下一个请求鉴权方式依赖会话内共享的上下文每次请求携带 token、JWT 或元数据标准 HTTP 鉴权模式流式输出依赖长连接 SSE 持续推送单次请求内部仍然支持流式响应但不是持久通道这张表的信息量其实很大。最核心的是服务端状态那一行——新规范不是让你不要有状态而是状态不能再藏在协议会话里。你原来的状态得换地方放比如放 Redis、放数据库或者干脆让客户端在每次请求里把必要上下文传过来。2.1 连接模型的变化对工具的影响MCP 的三大核心能力是 Tools、Resources、Prompts。这次改版里Tools 受影响最小因为工具调用天然就是你给参数、我返回结果的请求响应模型跟有没有 Session 不冲突。Resources 受影响最大老规范里资源订阅靠 Session 推送更新现在没有了长期会话服务端无法主动往客户端推资源变更只能改成客户端定时拉取或者服务端在外部回调里通知客户端刷新。Prompts 本身是模板不涉及状态基本只是能力声明里的格式微调。如果你正在写的是纯工具类 MCP server那迁移成本其实很低如果你的 server 依赖资源订阅推送就要重新设计数据同步机制常见做法是给客户端暴露一个 refresh 方法或者用消息队列驱动的 webhook 让客户端知道什么时候该重新拉。2.2 能力声明的变化旧版 initialize 返回的 serverCapabilities 里有 tools、resources、prompts、sampling、roots 这几项能力声明新版里 sampling 字段被移除roots 的语义也从会话级根目录授权变成请求级根目录提示。客户端在握手后要检查新版能力字段老代码里capabilities.sampling会直接返回 undefined不报错但逻辑会不知不觉失效。我建议你在收到服务端能力声明后加一段显式校验把不认识的 capability 字段打 log而不是默默忽略。迁移期最怕这种不报错但不干活的隐性故障。3. 实操迁移把你的 MCP server 从旧版搬到新版说再多概念不如直接改代码。下面这部分是我把自己一个旧版 sqlite 查询 server 迁移到新版的全过程按步骤拆开你可以照着对照你自己的项目。3.1 先检查你的 SDK 版本卡住的第一步永远是版本。老教程里写的是pip install mcp或npm install modelcontextprotocol/sdk但包名一样版本天差地别。新版 SDK 发布后旧版本并不会自动升级你的 lockfile 可能还锁着老版本。检查你自己项目里实际用的 SDK 版本比看教程简单得多。Python 看import mcp后mcp.__version__TypeScript 看package.json里modelcontextprotocol/sdk的版本号。如果版本号早于新版规范发布的里程碑直接升级。升级后第一件事就是把服务端能力和客户端能力打印出来看有没有你预期的字段。我的习惯是在配置文件里加一个显式的min_api_version字段服务端和客户端在握手后互相比较版本不满足就直接拒绝启动而不是等调用某个功能时才炸出来。3.2 移除 Session 依赖的三种写法改法下面是用 Python 演示迁移思路具体 API 以你使用的新版 SDK 为准。旧版里常见写法是这样from mcp.server import Server server Server(legacy-demo) server.session_opened async def on_session(session): session.state[user_id] parse_token(session.auth_token) server.tool(query_user) async def query_user(session, user_id: str): # 从 session 状态里拿当前用户 current_user session.state[user_id] return db.query(user_id, current_user)新版里没有 session.state也没有 session 回调。改造后是这样from mcp.server import Server server Server(stateless-demo) server.tool(query_user) async def query_user(user_id: str, auth_token: str): # 每次请求都从参数里拿身份信息 current_user parse_token(auth_token) return db.query(user_id, current_user)核心思路就一条原来存在 session 里的东西现在要么显式作为请求参数传入要么在服务端外部存储里按 token 查。我实际项目里遇到最多的是从 session 里拿用户上下文这个改起来最机械但也最容易漏——所有工具函数都要加一个参数所有测试都要跟着改。3.3 Sampling 调用改造成“返回结构化数据”旧版服务端调用 Sampling 最常见的用途是让客户端模型帮我把这个查询结果总结一下。旧代码类似server.tool(summarize_table) async def summarize_table(sql_result: str): reply await session.sampling.create_message( messages[{role: user, content: fSummarize: {sql_result}}] ) return reply.content[0].text新版不能再这么写。我的改造方向是服务端只做关我屁事的决策——把原始结果、统计指标、候选摘要文本全都返回由客户端模型决定怎么用server.tool(analyze_table) async def analyze_table(sql_result: str) - dict: return { raw: sql_result, row_count: sql_result.count(\n), columns: extract_columns(sql_result), top_50: sql_result[:2000], summary_candidate: generate_local_gist(sql_result) }这样客户端拿到的是一份完整的结构化数据模型可以自己决定是生成摘要、画图表还是做下一步推理。服务端还可以额外提供一个本地规则生成的摘要候选给客户端模型当素材但不再强制客户端执行推理。还有一个容易忽略的点Sampling 移除后服务端想去调外部大模型应该怎么办。我的方案是把模型调用封成普通工具比如call_llm由客户端控制何时调用、用什么 key。服务端需要智能时就调用这个工具把数据转给独立的模型服务返回结果再走正常工具链路。这是一种更干净的分层。3.4 服务端状态往外挪用 Token 和外部存储代替 Session替换 Session 最标准的方案就是 JWT。客户端在每次请求的 Authorization 头里带 token服务端解析 token 拿到用户身份和上下文这就是无状态鉴权。和广为人知的 Web 会话机制类似MCP 的新模型也回到了每次请求自证身份的思路上。需要复杂状态时把状态挪到 Redis。比如一个服务端要记住用户的查询历史、偏好设置老逻辑是存在 session.state新逻辑是在 token 里附带一个 state_id服务端启动时根据 state_id 从 Redis 加载状态用完后写回。注意这种状态不属于协议本身服务端完全可以用任何存储实现协议不管也不该管。这里有个注意事项Token 不要塞太多信息。JWT 虽然可以塞自定义 claims但请求头体积和 token 刷新复杂度都会上升。我一般只在 token 里放用户 id、租户 id、过期时间其他上下文走 Redis。3.5 资源订阅推送的替代方案如果你老项目用了 resources 订阅改造时要多想一步。旧版服务端可以在资源变化时通过 session 主动 push 通知新版没有 session 通道就必须把通知机制从协议层挪到业务层。一种我验证过可行的做法是服务端提供list_resources和read_resource两个工具客户端在有需要时主动拉取服务端检测到外部数据变化后不直接推给客户端而是记录一个resource_version字段客户端在每次请求后返回最新的 version与本地不一致就重新拉取。类似乐观锁的思路成本低也能覆盖绝大多数场景。如果要更实时的推送可以在 MCP 之外单独搭一条 webhook 或者 WebSocket 通道但这已经不属于 MCP 协议层的职责了。协议瘦身后这种协议外补充是正常的架构选择不用觉得不标准。4. 常见问题排查与踩坑实录迁移过程不会一次顺利。下面几条是我在真实环境里遇到的报错和排查思路直接整理成速查表方便你直接抄。现象原因解决方法第一次请求成功第二次返回 401服务端还在按旧逻辑查找 session找不到就拒绝改为每请求解析 token移除 session id 依赖initialize 返回后客户端没有收到 serverCapabilities.sampling新版规范已移除 sampling 能力声明客户端不要依赖 sampling 字段服务端把模型需求改造成工具调用服务端内存里的 roots 列表不生效roots 不再是会话级状态而是请求级提示信息将 roots 信息放到客户端请求上下文中或通过配置接口下发流式输出中断旧版长连接被网关切掉改成分块响应内流式返回不依赖持久通道后续请求找不到上一次会话里的变量状态没有外部化把状态写入 Redis/数据库或要求客户端在下一次请求时显式带上 key资源订阅永远收不到更新服务端还在用 session 推送但通道已不存在改成客户端定时拉取 服务端提供 version 字段做增量判断老教程里的 sampling 钩子代码不报错但也不生效SDK 兼容层吞掉了旧方法调用全局搜索 sampling / create_message全部重写成结构化返回4.1 调试技巧把协议交互录下来排查 MCP 问题最有效的工具不是断点调试而是把 JSON-RPC 消息完整记录下来。我迁移时会在客户端和服务端之间加一个简单的日志中间件把每个请求、响应、错误码打出来重点看method和params里有没有 session 相关字段。新版规范下一次正常的工具调用应该是三段式日志initialize/request、tools/call、response。如果中间混入了session/close、sampling/createMessage之类的遗留请求那说明有旧代码没清干净顺着调用栈找就行。4.2 本地生产环境不一致的坑本地跑得通、上生产就挂这种问题在新版 MCP 里尤其常见。本地工具进程是短命的、单实例的服务器上如果有负载均衡和网关旧 session 模型的问题才会暴露。新版无状态模型对生产环境更友好但前提是服务端真的不保存协议状态。我遇到过一个案例服务端代码已经改干净了但数据库连接池还保存在全局单例里配合长连接显得像有状态最后靠强制回收连接解决。记住无状态指的是协议层不保存会话状态不是业务数据不能保存。业务数据存数据库、缓存都没问题关键是每一次请求都要能被任意实例独立处理。5. 别急着删教程先学会“按版本读书”MCP 社区目前最大的混乱来自教程过期。很多热门教程写于 2025 年初那套内容在旧版规范下是准确的但放到新版就错得很离谱。你的任务不是把所有旧教程拉黑而是学会识别它的版本气息。5.1 怎么判断教程是哪一代的给你几个快速判断标准教程里如果出现了 Session 作为核心概念讲建立会话会话生命周期session id大概率是旧版。教程里出现 Sampling 或者 sampling/createMessage几乎可以确定是旧版新版已经移除了这个能力。教程里描述 transport 时提到 SSE 长连接 与 POST /messages 这种两头传消息的结构是旧式 transport新版 transport 更接近单请求流式响应的模型。教程发布日期如果是新规范发布之后并且明确写了无状态无 session移除 sampling字样才是可参考的新版资料。我建议你在看任何 MCP 教程时先翻到目录搜索这三个词session、sampling、sse。任何一项出现都要带着这可能过时了的心态去读。5.2 我推荐的入门路径如果你现在完全是 MCP 新手直接按新规范学别碰旧教程。先理解三个问题什么是工具调用、什么是资源、什么是能力协商。把这三个概念吃透再用当前版 SDK 写一个最简单的 echo server。然后加上一个真实工具比如查询数据库、读写文件、调用外部 API。最后再考虑鉴权、部署、流式输出这些工程问题。进阶一点去读规范里 capability negotiation 的部分理解方 service 如何声明能力、客户端如何选择能力这比背 API 名有用得多。MCP 更新还会继续但底层 JSON-RPC 加能力协商的骨架短期内不会变抓住主干枝叶变了也不慌。5.3 给团队内部资料打版本标签如果你所在团队也在写 MCP 代码我强烈建议像管理 API 文档一样管理协议教程开头标注 适用于 2025-XX 新版规范并用一个简单的字段记录规范版本号。这次改版让我最痛苦的就是内部 Wiki 里的教程没写版本迁移时根本不知道哪篇能信。加一个版本标签看起来只是一个小动作却能让后来的人少走很多弯路。6. 迁移完之后的真实体感最后说点实际的感受。我把自己的所有 MCP 工具全部迁到新版之后最明显的变化是服务端部署简单多了不用再做会话粘滞多个副本随便起扩容就是加实例网关层直接把 sticky 配置删了。其次是安全性更清晰每次请求都带 token权限模型跟普通 Web API 一致审计日志也能精确到单次请求不像以前哪个 session 干了什么要靠查内存。代价是客户端和工具函数的代码量变大了。身份信息要从 token 里解析传参资源变更要主动拉取模型生成逻辑要明确放在客户端侧。这不能叫缺点准确说是协议变笨了但架构变聪明了。如果你正在做批量迁移我的建议是一次性改完不要留过渡分支。新旧协议混合运行带来的兼容代码比你想的难维护得多。我踩过的坑是给旧代码留了一个兼容开关结果两周后自己都忘了哪些分支走的是旧逻辑。如果你刚接触 MCP不用担心现在学的教程会立刻过期。协议的核心——工具、资源、能力协商——并没有变变的只是连接模型和职责边界。抓住主干跟上版本剩下的都是工程细节。
阅读完成 · 觉得有帮助?
咨询建站