你把 MCP SDK 升到最新版,TypeScript 也重新 compile 過,測試全綠。
Production 上線後,Client 還是在做 initialize。Server 還在讀 Mcp-Session-Id。Load Balancer 還是 sticky。某些 Tool 依賴 server-to-client request,舊的 Tasks wire 也還在跑。
這種 migration failure 很常見,因為 SDK 版本和 wire 上實際跑的 protocol era 可以不同。
MCP 2026-07-28 這次升級同時碰到 protocol session、per-request metadata、server-to-client interaction、Tasks、HTTP transport、authorization 和 deprecated features。[1]
所以真正值得量的是:Production traffic 還有多少依賴 2025-era 的 wire contract、session assumptions 與 feature semantics?
Part 01 處理 State 去哪裡。Part 02 處理沒有 Session 後,多輪互動、長任務、Routing、Caching 與 Tracing 怎麼運作。
Part 03 收最後一條線:一個已經在 Production 跑的 MCP Client / Server,怎麼跨過 protocol era,而不是一次把所有舊 Client 切斷。
閱讀前提:如果你還不熟悉「protocol session 拿掉後,state 去哪裡」,先讀 Part 01;如果 MRTR、Tasks、Headers 還很陌生,先讀 Part 02。這一篇預設你已理解前兩篇,主要寫給正在維護 MCP client/server、gateway 或 production integration 的讀者。
版本範圍:本文以 MCP
2026-07-28與 2026-08-18 可取得的 current specification、Deprecated Features registry 與 Tier-1 SDK documentation 為準。MCP 仍快速演進,正式發布前應再做一次 drift check。SEP-2596 目前已是 Final;HTTP+SSE 的 current registry 仍以「SEP-2596 reaches Final 後三個月」表達 earliest-removal 條件。本文沿用 registry 的 current wording,不自行推算成固定日曆日期。[5][7]
先確認:wire 上實際跑的是哪個 protocol era?
官方 TypeScript SDK v2 的 migration guide 寫得很直接:即使程式已經在 v2 packages 上,預設也不代表它會自動在 wire 上講 2026-07-28。Client 端要透過 versionNegotiation 選擇 modern era;Server 端則要走新的 serving entry。[3]
這個細節非常適合拿來當 migration 的第一個檢查點。
假設你只做:
npm update
npm test
然後看到 CI 綠燈,不代表下面這些事情已經發生:
initialize/initialized已經離開 Production pathMcp-Session-Id已經沒有業務依賴- Session-local State 已經搬成 explicit application state
- server-initiated requests 已經遷移成 MRTR 或其他新 lifecycle
- 舊 change notifications 已經換成
subscriptions/listen - experimental Tasks 已經換成 current Tasks Extension
- HTTP+SSE 或 2025-era Streamable HTTP compatibility 已經有退場計畫
- OAuth flow 已經符合
2026-07-28的 issuer / resource binding 規則 - DCR 使用量已經下降到可以安全轉向 CIMD
- Roots / Sampling / Logging 的依賴已經被盤點
所以我不會從「升 SDK」開始。
我會先做 Protocol Migration Inventory。
第一階段先盤點 legacy dependency
一個實際的 inventory 可以先分成七類:
| 類別 | 你要找什麼 | 2026-07-28 要往哪裡走 |
|---|---|---|
| Protocol lifecycle | initialize、initialized、Mcp-Session-Id | per-request metadata、server/discover |
| State | session-local memory、sticky affinity、shared protocol session store | explicit application state / durable backend |
| Server-to-client interaction | elicitation、sampling、roots request flow | MRTR / new input lifecycle;deprecated features另行遷移 |
| Notifications | long-lived unsolicited list/resource notifications | subscriptions/listen |
| Long-running work | 2025 experimental Tasks、同步長 request | current Tasks Extension |
| HTTP transport | GET/DELETE session semantics、Last-Event-ID、legacy HTTP+SSE | 2026-07-28 Streamable HTTP |
| Authorization / governance | old issuer assumptions、DCR、Roots/Sampling/Logging | current Authorization、CIMD、documented migration paths |
這個 Inventory 最有價值的地方,不是做一張漂亮 Checklist。
而是逼團隊回答:
哪些東西只是 SDK API?哪些已經進到 Load Balancer、Redis、OAuth Server、Browser Host、Observability 和業務資料裡?
如果 Session 已經滲進 architecture,升級就不是 Search & Replace。
Modern-first,不代表遇到任何 400 就回 Legacy
2026-07-28 的 Streamable HTTP spec 提供了一個很重要的 backward compatibility 原則。
支援 modern 與 legacy 的 Client,可以先送 modern request。如果收到 400 Bad Request,不能直接判定「這台 Server 是舊版」。[2]
因為 modern Server 本身也會用 400 回:
- Unsupported Protocol Version
- Missing Required Client Capability
- header validation failure
正確邏輯比較像:
Send modern request
↓
Success
↓
Stay modern
400 Bad Request
↓
Inspect response body
├─ Recognised modern JSON-RPC error
│ ↓
│ Fix / renegotiate modern request
│
└─ Empty or not a recognised modern error
↓
Fall back to initialize-era flow
這個差別看起來很小,實際上會決定你的 compatibility layer 是「version negotiation」,還是「看到錯誤就亂降級」。
如果你把所有 400 都當 legacy signal,一個 modern request 只是 Header 寫錯,也可能被 Client 悄悄切回舊 protocol era。
Production 會變成最難查的那種狀態:
沒有真的 fail,但一直沒有完成 migration。
官方 TypeScript SDK 把這個問題包成 versionNegotiation。mode: "auto" 會先用 server/discover probe modern era,再對 2025-only Server fallback;pin: "2026-07-28" 則不 fallback。[3]
但這是 SDK implementation pattern,不是 MCP 規範要求所有語言都必須用同一套 API。
不同 Tier-1 SDK 甚至可以有不同 default。這也是為什麼 runbook 應該記錄「實際 negotiated protocol era」,而不是只記 package version。

Figure 3-1|Migration 的核心不是一次切換,而是辨識 protocol era、把 modern / legacy traffic 放進可觀測的相容路徑,再根據 telemetry 逐步 drain legacy。Dual-era serving 是 migration strategy,不是 MCP 規定的部署拓撲。
Dual-era 可以暫時共存,但一定要有退場條件
Server 端也一樣。
官方 TypeScript SDK v2 的 createMcpHandler() 可以用同一個 endpoint 服務 2026-07-28,並預設以 stateless legacy mode 兼容部分 2025-era traffic。[3]
如果你原本是 sessionful v1 Streamable HTTP,migration guide 甚至示範另一種做法:
Incoming request
↓
Legacy classifier
├─ legacy → existing legacy handler
└─ modern → strict modern handler
這很好用,因為你可以:
- 先加 modern lane
- 不立刻砍掉 legacy lane
- 看真實 traffic 走哪一條
- 修掉剩下的依賴
- 最後才移除 legacy handler
但這裡要守一條界線:
Dual-era support 是 transition architecture,不是 protocol requirement。
如果兩年後你的架構還保留:
if legacy:
old_stack()
else:
new_stack()
而且沒有 telemetry、沒有 drain target、沒有 removal criteria,那不是「向後兼容做得很好」。
那叫 migration 永遠沒結束。
Routing 只是第一步,真正的工作在舊 semantics
即使 modern 和 legacy request 已經能被正確分類,migration 還只完成一半。
因為舊 protocol era 的一些責任,不只是 wire format 不同。
1. Session State 不能只是搬到另一個隱藏角落
舊 Server 如果用:
Mcp-Session-Id
↓
in-memory session object
↓
browser context / basket / workspace / workflow progress
新的做法不是把它改成:
someSingletonMap[clientId]
然後宣布「我們沒有 Mcp-Session-Id 了」。
這只是在 API 表面 Stateless,Application 仍偷偷綁 Instance。
Part 01 的原則要在 migration 時真的落地:
- Protocol context 隨 Request
- Application state 用 explicit handle 定位
- Durable state 放 application backend
- Authorization 每次 Call 仍獨立判斷
2. Server-to-client request 不能只換 Method Name
2026-07-28 移除了原本的 server-to-client JSON-RPC request channel。[3]
如果 Tool 執行途中需要 Client Input,modern path 應該回 input_required,讓 Client retry 原 request,也就是 Part 02 的 MRTR。
如果是長時間工作,中途又需要 Input,則走 Tasks 自己的 tasks/get → tasks/update lifecycle。
不要把兩者混成:
「反正都是 input_required。」
它們的 State ownership 完全不同。
3. Change notification 的生命週期也變了
2025-era 可以依賴長駐 connection 送 list_changed 或 resources/updated。
Modern era 把 change notifications 移到 subscriptions/listen stream。[3]
如果你原本的 Client UI 假設:
連線建立後,Server 有任何變化都會自己推過來
這個假設也要進 migration inventory。
4. 2025-era Streamable HTTP 和 2026 Streamable HTTP 不是同一個 Wire Contract
2025-03-26 到 2025-11-25 也叫 Streamable HTTP,但那一代仍可以:
- mint
Mcp-Session-Id - 用 HTTP GET 打開 standalone SSE stream
- 用 HTTP DELETE 結束 Session
- 透過
Last-Event-ID做 stream resume - 在 SSE stream 上送 server-initiated JSON-RPC requests
這些都不是 2026-07-28 的 current behaviour。[2]
所以 migration 文件不要只寫:
Transport = Streamable HTTP,已完成。
要寫清楚:
哪個 protocol revision 的 Streamable HTTP?
授權(authorization)也在 migration 範圍內
如果 Remote MCP 有 OAuth,2026-07-28 的 migration 不應只看 transport。
Current Authorization spec 明確把 Authorization 定義成 OPTIONAL,但 HTTP-based implementation 一旦支援 Authorization,SHOULD conform to the current spec。[4]
其中幾個最值得 migration team 逐項檢查的地方:
Authorization response issuer validation
Client 在 redirect user-agent 前,要從 validated Authorization Server metadata 記錄預期 issuer。
收到 Authorization response 後:
- 如果有
iss,要和記錄的 issuer 比對 - 如果 metadata 宣告
authorization_response_iss_parameter_supported=true,但 response 沒有iss,要拒絕 - 比對不能自己做 host case folding、移除 default port 或其他 URI normalization[4]
這是防 OAuth mix-up 類問題的 trust boundary。
Token 要綁定到真正的 MCP Resource
Client 必須依 RFC 8707 把 resource 放進 Authorization Request 與 Token Request,指向要使用的 MCP Server。[4]
Server 收到 Access Token 時,也必須驗證 Token 是為自己這個 resource / audience 發行。
所以 migration 時不要只問:
Token 還能不能 decode?
要問:
這顆 Token 本來就是發給這台 MCP Server 的嗎?
CIMD 是主路徑,DCR 還不是「立刻刪掉」
Current Authorization spec 對 Client ID Metadata Documents(CIMD)的方向很明確:
- Authorization Server 和 MCP Client SHOULD support CIMD
- DCR MAY support,主要保留 backward compatibility
- Current Deprecated Features registry 已把 Dynamic Client Registration 列為 Deprecated,migration path 是 CIMD[4][5]
但這不等於:
今天把 DCR endpoint 關掉。
如果你的 Authorization Server、Client fleet 或企業 IdP 還沒有完整支援 CIMD,直接關掉 DCR 只會製造 auth outage。
比較合理的 rollout 是:
Add CIMD support
↓
Measure registration path
↓
Prefer CIMD where available
↓
Keep DCR fallback for legacy counterparts
↓
Drain DCR usage
↓
Remove only when compatibility allows
Migration 的關鍵字又回到同一個:Telemetry。
Deprecated 不是 Removed,更不是每個 Feature 同一個 Clock
Part 03 最容易被寫錯的地方,是把所有 deprecated feature 都丟進一句:
「MCP 給你一年,之後就會刪掉。」
Current Deprecated Features registry 不是這樣寫的。[5]
目前可以分成兩組。
2026-07-28 進入 Deprecated 的 Features
- Roots
- Sampling
- Logging
- Dynamic Client Registration
Registry 給它們的 earliest removal 都是:
First revision released on or after 2027-07-28
但這個日期只是 eligible for removal。
不是 2027-07-28 00:00 自動消失。
Current registry 明確寫:actual removal 是 Core Maintainer 在 release preparation 的決定,可能更晚。[5]
Grandfathered deprecations
Legacy HTTP+SSE 不一樣。
它從 2025-03-26 就被描述為 deprecated,早於 feature lifecycle policy。Current registry 把它列成 grandfathered case,migration target 是 Streamable HTTP,earliest removal 寫成:
Three months after SEP-2596 reaches Final[5]
SEP-2596 目前已進入 Final。[7] Current Deprecated Features registry 仍把 HTTP+SSE 的 earliest removal 寫成「Three months after SEP-2596 reaches Final」。[5]
在 registry 沒有給出更具體日期前,production runbook 直接引用這個 current condition 即可,不自行製造 calendar date。
也不要只拿 release blog 裡比較概括的「year-long offramp」來寫 production removal plan。[1]
對 rollout runbook,我會以 current Deprecated Features registry 為 operational reference。

Figure 3-2|Roots、Sampling、Logging、DCR 在 2026-07-28 進入 Deprecated;HTTP+SSE 則是 grandfathered case。Earliest removal 只代表具備移除資格,不是自動 removal date。圖中狀態以 2026-08-18 為準。
每個 Deprecated Feature 應該搬去哪?
這些 feature 的 migration target 分散在不同 layer,MRTR 只處理其中一類互動。
Current registry / SEP-2577 給的 migration path 是:[5][6]
| Deprecated feature | Migration direction |
|---|---|
| Roots | Tool parameters、resource URIs、server configuration |
| Sampling | Server 直接整合 LLM provider APIs |
| Logging | stdio 用 stderr;structured observability 用 OpenTelemetry |
| DCR | Client ID Metadata Documents(CIMD) |
| HTTP+SSE | Streamable HTTP |
這個表其實透露一個很重要的設計方向:
MCP 不再試圖把所有 Application / Infrastructure responsibility 都塞進 Core Protocol。
有些 responsibility 回到 Tool Contract。
有些回到標準 OAuth。
有些回到 OpenTelemetry。
有些變成 Extension。
所以這一段不能只做 old/new method 對照;很多 replacement 根本落在不同 abstraction layer。
Production migration 我會這樣推進
如果是已經有真實使用者的 Remote MCP,我不會一次切。
Gate 1:建立 Baseline
先量舊世界到底還剩多少。
至少記:
% requests using initialize-era flow
% requests carrying Mcp-Session-Id
GET / DELETE / Last-Event-ID traffic on MCP endpoint
legacy HTTP+SSE connections
server-initiated elicitation / sampling / roots calls
experimental 2025 Tasks usage
DCR vs CIMD registration path
Roots / Sampling / Logging negotiated or invoked
modern protocol errors and fallback reasons
沒有這些數字,後面所有「應該可以砍了」都只是感覺。
Gate 2:把 modern 路徑上線,但先不拔 legacy
Client 可以 modern-first probe。
Server 可以加 modern handler。
Gateway 可以開始辨識 MCP-Protocol-Version 與 modern request metadata。
這一階段的目標不是立刻把所有 traffic 切過去。
是先確認:
Modern path 在真實 workload 下能完整跑完。
Gate 3:搬 semantics,也一起把授權邊界收斂
這裡才是 migration 最花時間的地方:
- session-local state → explicit handle + durable backend
- server-to-client interactive flow → MRTR
- long-running work → current Tasks Extension
- change notifications →
subscriptions/listen - 2025 transport assumptions → 2026 per-request model
- hard-coded wire/error assumptions → current protocol semantics
這些如果沒做,只把 Request route 到 modern handler,通常很快就會撞牆。
授權也在這一階段一起驗證
如果有 OAuth:
- issuer validation
resourceparameter- token audience validation
- CIMD support
- DCR fallback telemetry
- scope / step-up behaviour
要和 protocol migration 一起測,不要等最後才發現 modern Client 連得到 Server,但 auth flow 全壞。
Gate 4:Drain Legacy
開始設定退場判斷。
例如:
legacy traffic < internal threshold
no critical customer depends on legacy-only path
no unexplained modern→legacy fallback
deprecated feature invocation is understood
DCR fallback is within accepted compatibility budget
rollback path has been tested
這裡我故意不給固定 1% 或 0.1%。
因為 B2B MCP Gateway、公開 MCP Server、內部 Agent Platform 的 compatibility cost 完全不同。
Threshold 是產品與風險決策,不是 protocol spec。
Gate 5:Remove
最後才是:
- 刪 legacy handler
- 移除 sticky-routing rules
- 下掉 protocol-session store
- 關閉不再需要的 old transport
- 移除 deprecated feature implementation
- 清掉 fallback-only tests / dashboards
- 更新 runbook 與 incident playbook
Removal 是 migration 的最後一個 action,不是第一個。
Migration 最容易卡在這些地方
SDK 升了,就以為 Protocol 升了
官方 TypeScript SDK 本身就證明這個假設不可靠。[3]
Protocol era 要從實際 negotiated behaviour 觀測。
遇到任何 400 就 fallback
Modern Server 也會回 400。
不看 error body 就 fallback,會把真正的 modern misconfiguration 藏起來。[2]
Dual-era 跑得很穩,所以永久保留
Compatibility path 沒有 drain plan,就會變新的 legacy。
Mcp-Session-Id 拿掉了,但 State 還在 Instance RAM
這不是 Stateless migration,只是 Session ID 改名。
看到 DCR Deprecated,就直接關 DCR
Current spec 仍允許 DCR 做 backward compatibility。[4]
先有 CIMD coverage,再 drain DCR。
看到 Deprecated,就當 Removed
Current registry 目前甚至明確寫著:在這套 policy 下 尚沒有 Feature 被移除。[5]
用 Launch Blog 的一句話當 Removal Calendar
Release blog 很適合理解方向。
Production runbook 的 exact status / earliest removal 應該回到 current registry 與 current spec。[1][5]
特別是 HTTP+SSE,現在就不適合硬寫某個 calendar removal date。
最後,用一張 checklist 檢查退場條件
如果我要在 PRD / Architecture Review 裡用一張表收掉,會是這樣:
| Area | Ready to migrate? |
|---|---|
| Protocol era | 能辨識 modern / legacy,不會 blind fallback |
| Client | modern negotiation 已啟用並有 telemetry |
| Server | modern handler 可獨立完成真實 workload |
| State | 不再依賴 protocol session / instance-local hidden state |
| Interaction | MRTR / Tasks / subscriptions lifecycle 分工清楚 |
| Transport | 2025 Streamable HTTP 與 HTTP+SSE dependency 已盤點 |
| Authorization | issuer、resource/audience、CIMD/DCR path 已測 |
| Deprecations | Roots / Sampling / Logging / DCR / HTTP+SSE 有 migration owner |
| Observability | 能看到 era、fallback reason、deprecated usage |
| Rollback | modern rollout 失敗可以退,不需要資料回滾賭運氣 |
| Removal | legacy traffic 達到內部退場門檻後才執行 |
這張表裡最重要的其實不是「全部打勾」。
而是每一格都有 owner、measurement、exit criterion。
回頭看這三篇,其實只在回答三個問題
Part 01 的問題是:
Session 拿掉後,State 去哪裡?
答案是 protocol session state 消失,但 application state、domain data 和 authorization responsibility 沒有消失。它們只是被迫變得更 explicit。
Part 02 的問題是:
沒有 Session,互動和長時間工作怎麼繼續?
答案是 MRTR、Tasks、Headers、Caching、Trace Context 各自接手一段明確 lifecycle,而不是再造一個萬用 Session。
Part 03 最後的問題是:
已經在跑的系統怎麼安全跨過去?
重點是把 protocol era 變成可辨識、可路由、可觀測的 production state;把舊 semantics 一個個搬走;等 legacy traffic 真正失去存在理由,再移除 compatibility path。
Migration 真正完成的那一刻,不是 package.json 裡版本號變了。
是 Production 裡那些只有舊 protocol era 才需要的架構假設,終於不再收到流量。
參考資料
- Model Context Protocol, The 2026-07-28 Specification, 28 July 2026. https://blog.modelcontextprotocol.io/posts/2026-07-28/
- Model Context Protocol, Streamable HTTP: 2026-07-28 Specification. https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http
- MCP TypeScript SDK, Supporting protocol revision 2026-07-28. https://ts.sdk.modelcontextprotocol.io/v2/migration/support-2026-07-28
- Model Context Protocol, Authorization: 2026-07-28 Specification. https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization
- Model Context Protocol, Deprecated Features: 2026-07-28 Specification. https://modelcontextprotocol.io/specification/2026-07-28/deprecated
- Model Context Protocol, SEP-2577: Deprecate Roots, Sampling, and Logging. https://modelcontextprotocol.io/seps/2577-deprecate-roots-sampling-and-logging
- Model Context Protocol, SEP-2596: Specification Feature Lifecycle and Deprecation Policy. https://modelcontextprotocol.io/seps/2596-spec-feature-lifecycle-and-deprecation