MCP 在 2026 年 7 月 28 日發布新版規範,這一版把協定本身從有狀態連線改成無狀態請求,把 Roots、Sampling、Logging 三項功能標為 Deprecated,並改掉 OAuth 的 client 註冊方式。以下整理會動到實作的幾項改動,以及自建 MCP server 要跟著調整什麼。
規範拿掉了兩樣東西:Streamable HTTP 的 Mcp-Session-Id 標頭,以及 initialize 與 notifications/initialized 這組交握。

拿掉之後,每個請求得自己帶齊過去靠交握建立的資訊,位置在 _meta:
_meta 鍵 |
帶什麼 | 強制程度 |
|---|---|---|
io.modelcontextprotocol/protocolVersion |
協定版本 | 每個請求都帶 |
io.modelcontextprotocol/clientCapabilities |
client 能力 | 每個請求都帶 |
io.modelcontextprotocol/clientInfo |
client 身分 | SHOULD |
io.modelcontextprotocol/serverInfo |
server 身分,放在每個結果的 _meta |
SHOULD |
規範頁上的 tools/call 範例就是新版一個請求的全貌:
POST /mcp HTTP/1.1
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: get_weather
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": { "location": "Seattle, WA" },
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": { "name": "ExampleClient", "version": "1.0.0" },
"io.modelcontextprotocol/clientCapabilities": {}
}
}
}
版本對不上時回 UnsupportedProtocolVersionError,回應裡會列出該 server 支援的版本。
新增的 server/discover 是 server MUST 實作的 RPC,用來告知支援的協定版本、能力與身分。Client 可以在其他請求之前先呼叫它選版本,在 STDIO 上也可以拿它當回溯相容的探測。
還有三項連帶移除:
tools/list、resources/list、prompts/list 的結果對每個請求一致ping、logging/setLevel、notifications/roots/list_changed 移除,log 層級改成每個請求用 _meta 的 io.modelcontextprotocol/logLevel 指定,沒帶這個欄位的請求,server MUST NOT 送 notifications/message
Last-Event-ID 與事件 ID)移除,回應串流斷掉就是整個請求作廢,client MUST 用新的 request ID 重送那需要跨呼叫狀態的 server 怎麼辦?規範的做法是由 server 自己鑄出 handle,當成一般的工具參數傳遞。
狀態從連線移到參數,變成呼叫雙方都看得見的東西
規範為只實作這一版的 server 列出三種舊流量的處置方式:
| 舊 client 的動作 | 新 server 的回應 |
|---|---|
| 對 MCP 端點發 HTTP GET 或 DELETE | 405 Method Not Allowed |
請求帶 Mcp-Session-Id 標頭 |
忽略,並且不鑄造也不回傳 session ID |
請求帶 Last-Event-ID 標頭 |
忽略,串流沒有續傳 |
方法不存在時回的是 404 Not Found 加上 JSON-RPC 錯誤碼 -32601,那個 JSON-RPC 錯誤體正是用來與舊 HTTP+SSE server 的 404 區分開來,新版 client 靠它判斷對方說的是哪一代協定。
這一版要求每個 POST 帶三個標頭,值一律鏡射自 body:
| 標頭 | 來源欄位 | 適用範圍 |
|---|---|---|
MCP-Protocol-Version |
_meta 的 protocolVersion |
每個請求 |
Mcp-Method |
method |
每個請求 |
Mcp-Name |
params.name 或 params.uri |
tools/call、resources/read、prompts/get |
理由寫在規範裡:中介設備(負載平衡器、閘道、可觀察性工具)可以只讀標頭就完成路由與檢查,無須解析 body。代價是兩邊必須對得起來,負載平衡器依標頭路由、MCP server 依 body 執行,兩個元件各自相信不同的真實來源時會產生漏洞。對不上時 server MUST 回 400 Bad Request 與錯誤碼 -32020:
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32020,
"message": "Header mismatch: Mcp-Name header value 'foo' does not match body value 'bar'"
}
}
Server 要主動送東西給 client,舊版有幾種零散的機制,新版整理成兩條。
變更通知走 subscriptions/listen。這是一條長時間存在的 POST 回應串流,取代原本的 HTTP GET 端點與 resources/subscribe、resources/unsubscribe。Client 明確訂閱需要的類型(toolsListChanged、promptsListChanged、resourcesListChanged、resourceSubscriptions),server 回覆確認,並在通知上標記 io.modelcontextprotocol/subscriptionId。
請求範圍內的通知走另一條路徑,notifications/progress 與 notifications/message 留在它所屬請求的回應串流上。
要跟 client 索取資訊走 MRTR(Multi Round-Trip Requests)。舊版是 server 主動發出 roots/list、sampling/createMessage、elicitation/create,新版改成 server 回一個 InputRequiredResult,把需要的東西列在 inputRequests,client 帶著 inputResponses 重送原本那個請求。
因此所有結果都多了一個必填的 resultType:
| 值 | 意思 |
|---|---|
complete |
一般結果 |
input_required |
MRTR 的中途結果,還要再一輪 |
舊版 server 不會有這個欄位,client MUST 把缺欄位的結果當成 complete。
取消的語意也跟著變:每個請求各有自己的回應串流,關掉串流就是取消那一個請求,server 收到之後 MUST NOT 再為它送出任何訊息。
通訊方向只剩一種,request 由 client 發起、server 回應,server 需要什麼就寫在回應裡等 client 再送一次
長時間任務原本是核心裡的實驗功能,新版移到官方 extension io.modelcontextprotocol/tasks:
tasks/result 換成輪詢式的 tasks/get
tasks/update,讓 client 在任務執行中把輸入送進去tasks/list
同時 ClientCapabilities 與 ServerCapabilities 多了 extensions 欄位,核心以外的功能從此有正式的宣告位置。
改動集中在 client 怎麼跟授權伺服器表明自己是誰。規範列出三種註冊機制,並給了選用順序:
client_id_metadata_document_supported 就走 CIMDregistration_endpoint 才退回動態註冊(DCR)CIMD(Client ID Metadata Documents) 是拿一個 HTTPS URL 當 client_id,URL 指向一份至少帶 client_id、client_name、redirect_uris 的 JSON,文件裡的 client_id 與該 URL 完全相同。授權伺服器看到 URL 形式的 client_id 就去解析它,兩種做法的差異在可攜性:
基於 Client ID Metadata Documents 的 client ID 可跨授權伺服器攜帶,因為它們是自行託管、由授權伺服器按需解析的 HTTPS URL,授權伺服器更換時不需要重新註冊
反過來,預先註冊與 DCR 取得的憑證綁在發行它的授權伺服器上,client MUST 以 issuer 識別碼為鍵保存,授權伺服器換掉時 MUST 重新註冊,不得沿用舊憑證。
這一版同時把「棄用」本身定成規則,三個狀態與時程寫進政策:
| 狀態 | 意思 |
|---|---|
| Active | 屬於現行版本 |
| Deprecated | 仍在規範內並仍可運作,已排定移除,新實作改用替代方案 |
| Removed | 已從 draft 刪除,下一個現行版本起消失 |
從 Deprecated 到 Removed 至少十二個月,窗口從標為 Deprecated 的那一版發布起算,窗口過後的第一個現行版本才具備移除資格。唯一的例外是已有公開資安通報或在野利用且無法就地緩解的功能,縮短仍需保留至少九十天。
登記表把每一項的替代做法與時程列得很明確:
| 功能 | 改用什麼 | Deprecated | 最早移除 |
|---|---|---|---|
| Roots | 工具參數、resource URI 或 server 組態傳遞目錄與檔案 | 2026-07-28 |
2027-07-28 之後的第一個版本 |
| Sampling | 直接接 LLM provider 的 API | 2026-07-28 |
2027-07-28 之後的第一個版本 |
| Logging | stdio 走 stderr,或改用 OpenTelemetry |
2026-07-28 |
2027-07-28 之後的第一個版本 |
| DCR(RFC 7591) | CIMD | 2026-07-28 |
2027-07-28 之後的第一個版本 |
includeContext 的 thisServer 與 allServers |
省略該欄位或填 none |
2025-11-25 |
跟隨 Sampling |
| HTTP+SSE 傳輸 | Streamable HTTP | 2025-03-26 |
SEP-2596 定案後三個月 |
登記表的 Removed 區目前是空的,這套政策上路以來還沒有任何功能真的被移除。
把上面的改動對到實作,需要動的地方是這幾項:
| 改動 | 要做什麼 |
|---|---|
| 無狀態 | 確認 server 的狀態全部由參數傳入,需要跨呼叫狀態時改鑄 handle 當工具參數 |
server/discover |
新增這個 RPC,回傳支援的協定版本、能力與身分 |
resultType |
所有結果補上這個欄位 |
_meta |
改成從每個請求讀協定版本與 client 能力 |
| 標頭驗證 | 比對 MCP-Protocol-Version、Mcp-Method、Mcp-Name 與 body,不符時回 -32020 |
| 快取欄位 | tools/list、prompts/list、resources/list、resources/read、resources/templates/list 五個方法的結果要帶 ttlMs 與 cacheScope |
| 舊流量 | GET 與 DELETE 回 405,Mcp-Session-Id 與 Last-Event-ID 一律忽略 |
| 授權 | client 側改走 CIMD,憑證以 issuer 為鍵保存 |
| 棄用功能 | 有用到 Roots、Sampling、Logging 的先規劃替代路徑 |
這幾項的急迫程度不同:
resultType 是協定層的硬改動:跟上之後才與新版 client 相容server/discover 影響的是效率與相容性:可以排在後面自建的環控 MCP server 在組態裡開了 FASTMCP_STATELESS_HTTP=true,時間點早於這次改版。
先前維持 session 的組態跑起來會這樣:連續呼叫幾次之後,agent 端收到 Session terminated,接著進重連,重連後停在逾時,整個工具呼叫的結果停在空白,錯誤訊息同樣停在空白。改成每次呼叫獨立之後,這個現象消失。
新規範把 session 從協定層拿掉,方向與這件事一致。跨呼叫的狀態一旦綁在連線上,連線的存活就成了工具呼叫的隱藏前提,而這個前提只在連線持續存在時成立,網路抖動與伺服器重啟都會打斷它,這兩件事在長期運作的系統裡都會發生。
三者的選型理由、安裝流程與組態檔的結構。