破壞性變更

MCP 把 session 拿掉了:
2026-07-28 規格全面轉向 stateless

這是 Model Context Protocol 發布以來最大的一次改版。initialize 握手不見了,協議層的 session 不見了,server 也不能再主動向 client 發問。每個 request 從此自己帶著協議版本與能力,獨立成立。

開放規格 免費:是 開源:是 第 5 版規格 向後相容:否
Protocol Revision
2025-11-25
2026
07-28
規格版本號本身就是日期。這一版把前一版的握手流程整段刪除。
Overview

摘要

摘要(若下文與此處衝突,以此摘要為準)

發布日期:2026-07-30 規格版本:2026-07-28(前一版為 2025-11-25)

Model Context Protocol(MCP)在 2026 年 7 月 28 日發布第五版規格,把整個協議改成 stateless(無狀態)。最核心的改動是移除 initialize 握手與協議層的 session:過去 client 連上 server 要先交換一次協議版本與能力,之後整條連線都沿用那次結果;現在每一個 request 都必須在 _meta 欄位裡自己帶上協議版本與 client 能力,server 也不得從先前的 request 推斷任何狀態。連帶的改動包括:server 不能再主動向 client 發起請求(改用 MRTR,由 server 回一個「需要補資料」的結果、client 補齊後重送原請求);所有 server MUST 實作新的 server/discover;清單類結果強制帶上快取指示 ttlMscacheScopepinglogging/setLevel、SSE 斷線續傳一併移除;Roots、Sampling、Logging 三個功能與 OAuth 動態註冊(DCR)列入 deprecated,退場窗口至少十二個月。

這是破壞性變更。官方相容性矩陣明列:只支援新版的 client 對上只支援舊版的 server 會失敗,反之亦然。實務上短期不會大規模出事,因為主要 client 預期會實作「雙時代(dual-era)」相容層,遇到舊 server 就退回舊流程——但那是緩衝,不是長期方案。四個 Tier 1 官方 SDK(TypeScript、Python、Go、C#)已支援新版,Rust SDK 為 beta。Anthropic 表示 Claude 各產品的支援「即將陸續上線」,未公布日期。

The Core Change

被拿掉的到底是什麼

要理解這次改版,先要分清楚兩種「狀態」。

舊版 MCP 的連線是有記憶的。client 接上 server,第一件事是送出 initialize,雙方在這一次交換裡談好用哪個協議版本、彼此有哪些能力,server 回一個 Mcp-Session-Id。之後這條連線上的每個 request 都隱含地繼承這次協商的結果——request 本身不必再說自己是誰、用哪個版本,因為 server「記得」。

2026-07-28 把這層記憶整個刪掉。規格的原文說得很直接:server MUST NOT 依賴同一條連線上先前的 request 來建立上下文(能力、協議版本、client 身分都算),每個 request 都要自己在 _meta 裡供應這些資料。

一個容易誤解的地方

被移除的是協議層的 session,不是應用層的狀態。持久終端機、長時間任務、跨呼叫的工作階段——這些需求全部還在,也全部還能做。規格明訂:需要跨越多個 request 的狀態,MUST 由一個明確的識別碼來指涉,並由 client 在每次 request 上帶著。狀態的鑰匙只是從協議的 Mcp-Session-Id 標頭,換成你自己工具參數裡的一個 ID。

規格還補了一條容易被忽略的推論:一條開著的連線不等於一段對話。即使是 stdio 這種一個行程對一個 client 的傳輸方式,client 也可以在同一條管線上交錯送出彼此無關的 request,server 不得把「連線/行程的身分」當成「對話連續性」的代理。

為什麼要這樣改

官方給的理由是部署形態。有 session 的協議,代表同一個對話的後續 request 必須回到同一台機器,否則狀態就對不上——這在雲端環境會逼出兩件麻煩事:不是要架共享儲存層把 session 存起來,就是要在負載平衡器上做 sticky session。兩者都是額外的基礎設施,也都是額外的故障點。

request 自己完備之後,任何一個 request 都可以落在一般輪詢負載平衡器後面的任何一台機器上,不需要共享儲存。MCP server 因此可以部署在 serverless 與邊緣運算平台上——那些環境本來就不保證同一個實例會處理連續請求。

Per-Request Metadata

每個 request 現在要自報身分

取代握手的是 _meta 裡的四個保留欄位。前兩個是必填,漏掉就是格式錯誤。

欄位型別必填用途
io.modelcontextprotocol/protocolVersion string 這個 request 使用的協議版本,例如 "2026-07-28"
io.modelcontextprotocol/clientCapabilities ClientCapabilities 與這個 request 相關的 client 能力
io.modelcontextprotocol/clientInfo Implementation 否(SHOULD 帶) client 名稱與版本,供顯示與除錯用
io.modelcontextprotocol/logLevel LoggingLevel 這個 request 希望 server 輸出的最低日誌等級

少了必填欄位的 request 屬於格式錯誤,server MUST 以 JSON-RPC 錯誤碼 -32602(Invalid params)拒絕;走 HTTP 時狀態碼 MUST400 Bad Request。另外,server MUST NOT 依賴 client 沒有宣告的能力——如果處理某個 request 需要 client 沒列出的能力,server MUSTMissingRequiredClientCapabilityError-32021),並在 data.requiredCapabilities 裡列出缺了哪些。

反方向,server SHOULD 在每個結果的 _meta 裡放 io.modelcontextprotocol/serverInfo 自報身分。規格特別註明:clientInfoserverInfo 都是自己宣稱的,協議不做驗證,僅供顯示、記錄與除錯,實作 SHOULD NOT 依賴它們改變行為,更不該拿來做安全決策。

request.json — 新舊對照

server/discover:唯一新增的必實作 RPC

沒有握手,client 就少了一個「一次問清楚」的時機。規格補上 server/discover:server MUST 實作它,用來公告自己支援的協議版本、能力與身分。client 這邊是選配——MAY 在送出任何其他 request 之前先呼叫它做版本選擇,也可以完全不呼叫,直接送出想送的 request,遇到 UnsupportedProtocolVersionError-32022)再從錯誤裡的 supported 清單挑一個版本重試。

它的第二個用途是探測對方是新是舊,這在下面的相容性一節會再談。

MRTR

MRTR:server 不能再主動發問了

舊版有一組反向的請求:server 可以在處理到一半時,主動向 client 發起 elicitation/create(跟使用者要資料)、sampling/createMessage(借用 client 的模型跑一次推論)或 roots/list(問可以存取哪些目錄)。這需要一條雙向開著的通道,正好是 stateless 不允許的東西。

取代它的是 Multi Round-Trip Requests(MRTR),流程反過來:

  1. client 送出原本的 request(例如 tools/call)。
  2. server 發現資料不夠,回一個 resultType: "input_required" 的結果,裡面的 inputRequests 列出它需要什麼,並可附上一個 requestState這一輪的請求到此結束。
  3. client 去把資料湊齊(問使用者、跑模型),然後重送一次原本的 request,帶上 inputResponses 與原封不動的 requestState
  4. server 從 requestState 還原上下文,完成工作,回最終結果。

幾個容易踩到的細節:重送時的 JSON-RPC id MUST 與第一次不同,因為那是兩個各自獨立的 request;client MUST NOT 檢查、解析或修改 requestState 的內容;server 只能對 prompts/getresources/readtools/call 這三種請求回 InputRequiredResult,其他一律不行。

requestState 是外部可控輸入,不是私有記憶體

這是整份規格裡最容易寫出安全漏洞的地方。requestState 會經過 client 的手再回來,規格因此要求:server MUST 把它當成攻擊者可控的輸入看待。只要它會影響授權、資源存取或商業邏輯,server MUST 保護其完整性(例如 HMAC 或 AEAD),並且 MUST 拒收驗證失敗的內容。

防重放方面,規格 SHOULD 要求在受完整性保護的內容裡放進三樣東西並逐一驗證:驗證過的主體身分(換一個主體拿來用就拒絕)、短期有效期限(過期就拒絕)、以及原始請求的識別(方法名稱加上關鍵參數的摘要,對不上就拒絕)。規格同時警告:這三項只能限縮重放窗口、擋掉跨使用者與跨請求的重用,並不保證只能用一次;需要一次性語意的場景(例如一次性兌換),MUST 自己在 server 端強制執行。

mrtr-exchange.json

連帶地,所有結果現在都 MUST 帶一個 resultType 欄位:一般結果是 "complete",MRTR 的中間結果是 "input_required"。為了相容舊 server,client MUST 把沒有這個欄位的結果一律視為 "complete"

Removed & Deprecated

被移除與被 deprecated 的清單

先分清楚兩個層級:移除是這一版就沒有了,deprecated 是還能用但已排定退場,官方訂出至少十二個月的窗口。

這一版直接移除

被移除取而代之
initialize / notifications/initialized 每個 request 在 _meta 自帶版本與能力
Mcp-Session-Id 標頭與協議層 session 需要跨呼叫狀態時,用 server 自己發的識別碼當工具參數傳
server 主動發起的請求 MRTR(InputRequiredResult + 重送)
HTTP GET 端點、resources/subscriberesources/unsubscribe subscriptions/listen:單一長連線 POST 回應串流,client 自行選擇要訂閱哪些變更類型
ping 無替代(連線本身不再代表狀態,沒有保活的意義)
logging/setLevel 改為每個 request 用 _metaio.modelcontextprotocol/logLevel 指定
notifications/roots/list_changed 無替代(Roots 本身已 deprecated)
SSE 斷線續傳(Last-Event-ID 與事件 ID) 無替代。串流斷掉就是整個 request 作廢,client MUST 用新的 request ID 重送
核心協議裡的實驗性 tasks 移入官方擴充 io.modelcontextprotocol/tasks,改用 tasks/get 輪詢

列入 deprecated(至少十二個月窗口)

功能官方建議的替代做法
Roots改用工具參數、資源 URI 或伺服器設定來傳遞目錄與檔案
Sampling直接串接 LLM 供應商的 API
Logging輸出到 stderr(stdio 傳輸),或改用 OpenTelemetry
HTTP+SSE 傳輸改用 Streamable HTTP(此傳輸自 2025-03-26 起就已標示棄用,這次正式納入退場政策)
OAuth 動態註冊(DCR,RFC 7591)改用 Client ID Metadata Documents(CIMD);DCR 保留給尚未支援 CIMD 的授權伺服器

Roots、Sampling、Logging 三個一起被標記,反映的是同一個判斷:這三者都要求 server 能反過來使喚 client,而那正是 stateless 架構最難自然容納的形狀。

Caching

清單結果現在必須說自己能快取多久

拿掉 session 之後,清單類端點(tools/listresources/listprompts/list)不再隨連線而異——同一個 server 對誰都回同一份。既然結果穩定,就值得快取。

新的 CacheableResult 介面要求 tools/listprompts/listresources/listresources/readresources/templates/list 的結果都帶上兩個欄位:

  • ttlMs——新鮮度提示(毫秒),讓 client 知道可以快取多久、少做輪詢。
  • cacheScope——"public""private",決定中介層(例如閘道、CDN)可不可以共用這份快取。

另有一條相關但常被略過的建議:server SHOULDtools/list 回傳的工具維持固定順序。理由不是美觀,是提高 LLM 的 prompt 快取命中率——工具清單通常會整段放進提示詞前段,順序一變,快取就整段失效。

同時新增的路由標頭

Streamable HTTP 的 POST 請求現在必須帶上 Mcp-MethodMcp-Name 標頭。用意是讓閘道、代理與負載平衡器不必拆開 JSON-RPC 內文,光看標頭就能做路由、限流與稽核。標頭與內文不一致時,錯誤碼是 HeaderMismatch-32020)。

Authorization

授權:往標準 OAuth 實務靠攏

這一區塊的改動比較零碎,但方向一致——讓 MCP 的授權能直接接上企業既有的身分系統,而不是靠變通。

  • 授權伺服器 SHOULD 依 RFC 9207 在授權回應裡帶上 iss 參數;client 在拿授權碼去換 token 之前,MUST 驗證這個 iss 與先前記錄的簽發者相符。
  • client 在動態註冊時 MUST 指定適當的 application_type,避免與 OpenID Connect 的 redirect URI 規則衝突。
  • client 憑證與簽發它的授權伺服器綁定:MUST 以簽發者識別碼為鍵值保存憑證、 MUST NOT 拿到別的授權伺服器重用,換了授權伺服器就 MUST 重新註冊。
  • 錯誤碼重新編號:JSON-RPC 的 -32000-32019 留給既有實作(既往不咎),-32020-32099 保留給規格本身。資源找不到的錯誤碼從 -32002 改為 -32602,以符合 JSON-RPC 慣例。
Compatibility

相容性矩陣:什麼會壞,什麼不會

規格用「時代(era)」來區分:modern 指 2026-07-28 以後、把版本與能力放在每個 request 裡的版本;legacy 指 2025-11-25 以前、用 initialize 建立 session 的版本;dual-era 指兩種都支援的實作。

ClientServer結果
ModernModern可用。版本不合會回 -32022,client 挑一個雙方都支援的版本重試
ModernLegacy失敗。舊 server 可能回一個自定義錯誤、可能不回應,甚至可能用舊語意去處理一個意義曖昧的方法
Dual-eraModern可用,且維持在新版
Dual-eraLegacy可用。探測失敗後退回 initialize 流程
LegacyModern失敗。舊 client 沒有「往前相容」的機制
LegacyDual-era可用。server 依舊版語意服務

兩個「失敗」欄位就是這次改版的實際風險所在。其中 Modern client 對 Legacy server 這格最需要注意——規格特別點出舊 server 有可能不是乾脆地報錯,而是用舊語意處理一個意義曖昧的方法,那會產生看起來成功、實際上語意不對的結果。所以走 stdio 時,client SHOULD 先送 server/discover 讓失敗發生得明確一點。

探測方式依傳輸而異:stdio 用 server/discover 試探,收到不是「新版錯誤」的任何錯誤就退回舊流程;Streamable HTTP 則是先發一個新版 request,然後檢查 400 Bad Request 的內文再決定要不要退回。規格也提醒:時代判定是 server 的屬性而非單次 request 的屬性,client SHOULD 針對該 server 行程(stdio)或來源(HTTP)快取判定結果。

給只支援新版的 server 一條體貼建議

規格建議:只支援新版的 server,SHOULD 在拒絕 initialize 的錯誤訊息裡寫出自己支援哪些協議版本。理由是舊 client 沒有任何往前相容的手段,這行錯誤訊息可能是使用者唯一能看到的線索。

Migration

現有 server 要做什麼

依改動的性質分成兩類:機械性的,跟需要判斷的。

機械性改動(規格明確,模式重複)

  1. 把官方 SDK 升到支援 2026-07-28 的版本。TypeScript、Python、Go、C# 都是 Tier 1 已支援,Rust 為 beta。
  2. 刪掉 initialize 握手與所有「從連線推斷狀態」的程式碼,改成從每個 request 的 _meta 讀取協議版本與 client 能力。
  3. 實作 server/discover。這是 MUST,不是選配。
  4. 清單類端點加上 ttlMscacheScope,並讓 tools/list 的輸出維持固定順序。
  5. 錯誤碼調整:資源找不到從 -32002 改為 -32602
  6. 把原本存在協議 session 裡的狀態,改成由 server 發放的識別碼,並在工具的 input schema 裡明確宣告成一個參數。

需要判斷的改動(不能照抄)

  • MRTR 的 requestState 安全設計。要不要簽章、用 HMAC 還是 AEAD、TTL 設多久、哪些參數要納入摘要、需不需要一次性保證——這些取決於你的 server 到底在做什麼,規格只給了必須達成的性質,沒有給答案。
  • 要不要做 dual-era。同時支援新舊兩套等於維護兩條程式路徑,換來的是相容性。這是取捨,不是預設值。
  • 被 deprecated 的三個功能怎麼替代。Sampling 改成直接串 LLM API,意味著成本與金鑰管理從 client 端移到你這邊;Roots 改用工具參數,意味著要重新設計權限邊界。都不是換個函式名就好。
  • 斷線續傳沒了怎麼辦。長時間執行的操作原本可以靠 SSE 續傳撐過網路抖動,現在斷了就得整個重送。冪等性從「最好有」變成「必須有」。
Analysis

這次擴散會有多快

以下是判斷與推論,不是官方說法。事實部分附來源,推論部分標明是推論。

協議升級的瓶頸,歷來很少是技術難度,而是有多少維護者願意排出時間動手改。一份規格再漂亮,如果存量實作的維護者各自忙著別的事,它就會停在紙上好幾年。HTTP/2 花了多年才吃下多數流量,IPv6 至今還在爬。

MCP 這次的存量不小。官方數字:MCP 剛突破每月 4 億次 SDK 下載,今年成長四倍;Claude 的 connectors 目錄列了超過 950 個 MCP server。要改的東西很多。

但這次有一個以往沒有的變數:要改的內容,剛好是 AI coding agent 最擅長的那一類。

看一眼上一節的「機械性改動」清單——刪掉握手、把版本塞進 _meta、實作一個規格已經寫死形狀的 RPC、在回傳值加兩個欄位、把一個錯誤碼換掉。這些改動的共同特徵是:規格明確、模式重複、範圍局部、而且有一份寫得極為詳盡的正式文件可以對照。這正是 coding agent 表現最穩的區間,也正是人類維護者最容易一拖再拖的雜事。

再加上一個族群因素:MCP server 的維護者幾乎必然是 AI 開發工具的重度使用者——他們寫 MCP server 就是為了餵給 agent 用。換句話說,這批人手上本來就有能執行這種改動的工具,而且熟練。這是一個高度自我選擇的族群,跟「全世界的網站管理員要不要升級 TLS」不是同一種分布。

所以我的判斷是:不要用 2025 年的經驗去估這次的擴散曲線。機械性的那一半會比歷史經驗快得多。

但真正會拖慢的,是不能自動化的那一半

上一節「需要判斷的改動」才是關鍵路徑,其中 requestState 的安全設計尤其危險。規格要求把它當成攻擊者可控的輸入,要簽章、要防重放、要綁定主體與有效期限——而這種東西寫錯了不會報錯。一個沒有做完整性保護的 requestState,功能測試全部會過,跑起來一切正常,直到有人去改它。

換句話說,AI 加速的是「改完能跑」,不是「改得安全」。這次改版把一個原本不存在的攻擊面(一段會經過 client 的手再回來、且會影響 server 行為的狀態)放進了每一個要用 MRTR 的 server 裡。擴散得越快,來不及想清楚這件事的實作就越多。

值得一併留意的是:這種「工具跑起來正常、但實際上是錯的」的失效模式,人類會起疑,agent 不會——agent 拿到什麼就信什麼。當升級動作本身也交給 agent 去做時,這條就更需要有人盯著。

FAQ

常見問題(更新日期:2026-07-30)

MCP 2026-07-28 規格是免費的嗎?怎麼收費?

是免費的。Model Context Protocol 是一份公開的開放規格,沒有授權費,schema 與文件都公開在 modelcontextprotocol.io 與 GitHub 上,官方 SDK(TypeScript、Python、Go、C#)也是開源免費。使用 MCP 的成本來自你自己的伺服器資源,以及背後呼叫的 LLM API 費用,跟協議本身無關。來源:modelcontextprotocol.io/specification/2026-07-28(更新 2026-07-30)。

我現有的 MCP server 會因為 2026-07-28 規格馬上壞掉嗎?

不會立刻壞,但這是一次破壞性變更。官方相容性矩陣指出:只支援新版的 client 連上只支援舊版的 server 會失敗,反之亦然。實務上短期不會大規模出事,是因為主要 client 預期會做成「雙時代(dual-era)」——同時支援新舊兩種握法,遇到舊 server 就退回 initialize 流程。所以舊 server 在 client 還保留相容層的期間可以繼續運作,但那是緩衝期,不是長期方案。來源:modelcontextprotocol.io/specification/2026-07-28/basic/versioning(更新 2026-07-30)。

MCP 拿掉了 session,那需要跨多次呼叫保存狀態的工具怎麼辦?

改用 server 自己發的識別碼,當成一般的工具參數傳遞。規格明文規定:需要跨越多個 request 的狀態,MUST 由一個明確的識別碼來指涉,並且由 client 在每次 request 帶上。被移除的是「協議層的 session」,不是「應用層的狀態」——持久終端機、長時間任務、購物車這類需求依然可以做,只是狀態的鑰匙從協議的 Mcp-Session-Id 標頭,換成你自己工具參數裡的一個 ID。來源:modelcontextprotocol.io/specification/2026-07-28/basic/index(更新 2026-07-30)。

MCP 2026-07-28 怎麼開始用?現有 server 要怎麼升級?

四個步驟:(1)把官方 SDK 升到支援 2026-07-28 的版本,TypeScript、Python、Go、C# 皆為 Tier 1,Rust 為 beta;(2)移除 initialize 握手與所有「靠連線推斷狀態」的程式碼,改為從每個 request 的 _meta 讀取協議版本與 client 能力;(3)實作 server/discover,這是規格要求必須實作(MUST)的;(4)如果你的 server 原本會主動發起 sampling、elicitation 或 roots/list 請求,全部改寫成 MRTR 模式。來源:modelcontextprotocol.io/specification/2026-07-28/changelog(更新 2026-07-30)。

Roots、Sampling、Logging 這三個功能什麼時候會消失?

還不會消失,但已被標記為 deprecated,官方訂出至少十二個月的退場窗口。窗口期內這三個功能仍完全可用,但新的實作不應該再採用。官方建議的替代做法:Roots 改用工具參數、資源 URI 或伺服器設定來傳遞目錄;Sampling 改為直接串接 LLM 供應商的 API;Logging 改為輸出到 stderr(stdio 傳輸)或改用 OpenTelemetry。來源:modelcontextprotocol.io/specification/2026-07-28/changelog(更新 2026-07-30)。

Claude 什麼時候會支援 MCP 2026-07-28?

尚未公布確切日期。Anthropic 在 2026-07-28 的官方公告中表示支援「即將陸續在 Claude 各產品上線」(rolling out across Claude products soon),但沒有給出具體時程表,也沒有對現有 MCP server 開發者設下強制升級期限。同一篇公告提到 MCP 已突破每月 4 億次 SDK 下載,Claude 的 connectors 目錄列有超過 950 個 MCP server。來源:claude.com/blog/bringing-mcp-2026-07-28-to-claude(更新 2026-07-30)。

Conclusion

結論

MCP 最初的設計把「一個 client 接上一個 server」當成基本圖像——像插上一條線,兩端記得彼此。2026-07-28 承認了這個圖像撐不住實際的部署形態:server 要跑在負載平衡器後面、跑在 serverless 上、跑在會隨時把你的行程回收掉的邊緣節點上。在那些地方,「記得」是一種奢侈。

所以這一版做的事,本質上是把記憶的責任從協議往上推給應用:協議只保證每個 request 自己完備,狀態要不要有、放在哪裡、用什麼識別碼指涉、怎麼保護,全部變成 server 作者的設計決定。這讓協議變簡單了,也讓 server 作者要想的事變多了。

三件現在就該做的事:

  • 盤點自己的 MCP server 有沒有依賴 session、Roots、Sampling、Logging 或 SSE 續傳——有的話,這幾項就是你的遷移工作量。
  • 機械性的部分可以交給工具做,但 requestState 的安全設計要自己想清楚:完整性保護、有效期限、主體綁定、原始請求綁定,四項缺一不可,而且缺了不會報錯。
  • 不急著拆掉舊版支援。相容性矩陣顯示 dual-era 是唯一兩邊都不失敗的組合,在 client 生態跟上之前,它值得那份維護成本。

至於這次會擴散多快——存量大、改動機械、維護者手上剛好有能做這種改動的工具。前半段大概會比多數人預期的快,後半段會卡在那些不能自動化、而且寫錯了不會報錯的地方。

← 返回 AI 知識庫