Cloudflare Agents 的 McpAgent 要怎麼遷移?先分出 stateless 與 legacy 路徑
Cloudflare Agents SDK 0.20 已將 McpAgent 標為 deprecated 且 feature-frozen,新的 MCP server 應使用 MCP SDK v2 與 createMcpHandler。不過,直接把 import 換掉並不等於完成遷移:舊 server 若依賴 MCP session、RPC、server 主動推送請求、stream 或 replay,改成 stateless route 後會改變行為。
直接結論是:沒有 session 依賴的工具 server 可以直接改成 stateless handler;有狀態功能的 server 必須先補上應用層狀態,並在過渡期同時保留 legacy 路徑。
先判斷自己的 MCP server 屬於哪一類
Cloudflare 的遷移文件把選擇拆得很清楚:不要從「用了哪個 class」判斷,而是從目前客戶端是否依賴 protocol session 判斷。
| 目前情況 | 建議路徑 |
|---|---|
| 工具只處理一次 request,沒有 session 狀態 | 直接改用 SDK v2 factory + createMcpHandler |
McpAgent 沒有 stateful 功能 | 直接改用 stateless handler |
| 需要 session、RPC、pushed request、獨立 stream 或 replay | 先新增 stateless route;legacy route 暫時並存 |
這也和「MCP elicitation 的輸入邊界」不同:elicitation 是工具呼叫中向使用者補資料;這次要處理的是 server transport 與 session 生命週期本身。
沒有狀態依賴時,改用 createMcpHandler
新的 stateless server 使用 @modelcontextprotocol/server,把 server 定義做成 factory,再交給 agents/mcp/server 的 handler。factory 很重要,因為 stateless path 的每個 POST 都會建立新的 server 與 transport。
import { createMcpHandler } from 'agents/mcp/server'import { McpServer } from '@modelcontextprotocol/server/mcp.js'
function createServer() { const server = new McpServer({ name: 'release-tools', version: '1.0.0' })
server.registerTool( 'get-release', { description: 'Read one public release record' }, async () => ({ content: [{ type: 'text', text: '…' }] }), )
return server}
export default { fetch(request, env, ctx) { return createMcpHandler(createServer, { allowedHostnames: ['mcp.example.com'], corsOptions: { origin: 'https://app.example.com' }, })(request, env, ctx) },} satisfies ExportedHandlerallowedHostnames 只填 hostname,不含 scheme 或 port。Cloudflare 也特別提醒:CORS response header 不是驗證機制;公開 endpoint 仍要以 OAuth 或其他認證層保護,且不能因為 request.url 看起來正常就省略 Host/Origin 驗證。
這些功能不能默默帶進 stateless route
預設 legacy compatibility 可讓一般 legacy client 繼續呼叫 tools、prompts 與 resources,但它不是完整的 session transport。stateless path 不會保留 MCP session ID,GET 和 DELETE 會回傳 405,也不支援 pushed elicitation、sampling、roots、standalone stream、replay 與 session deletion。
因此,以下寫法需要先重設計,而不是直接切換:
- 用 MCP session ID 當使用者或業務資料的 key。
- 讓 server 透過舊 transport 主動向 client 發送請求。
- 依靠長連線 stream 的 replay 或中斷續傳。
- 讓 Agent 和
McpAgent以 RPC 綁定交換應用資料。
例如,原本綁在 session 上的工作狀態,可移到 Durable Object、D1、KV 或 R2,並改用經驗證、具有期限的 application handle 定位。多步驟操作則應讓 client 在每次 request 明確帶回受完整性保護的 requestState,不要假定 transport 會替你保存上下文。
有 legacy client 時採雙路徑,而不是一次切斷
若現有客戶端真的需要上述 stateful 行為,Cloudflare 建議新增 SDK v2 stateless route,同時保留暫時性的 createLegacyMcpHandler 或 McpAgent route。等 client 切換、舊 session 排空後,再移除 legacy lane。
實務上可把遷移拆成四步:
- 列出每個 tool 是否讀寫 session、RPC、stream 或 pushed request。
- 先把無狀態 tools 以 SDK v2 factory 暴露在新 route,保留原路徑不動。
- 對有狀態的功能建立應用層資料邊界與過期策略,讓新 route 不再仰賴 MCP session。
- 監測 client 切換與舊 session 歸零後,才下線 legacy route。
不要在同一個 McpAgent route 內混用 SDK v2 的 McpServer。Cloudflare 文件明確指出,legacy McpAgent 仍是 SDK v1 server;它需要維持 @modelcontextprotocol/sdk 的 import,直到該路徑被移除。
上線前的最小驗證
至少用一個 SDK v2 client 與一個仍在使用中的 legacy client 分別測試。除了成功呼叫 tool,也要刻意測試 Origin、Host、未登入請求、連續 POST 與舊 client 的 session 行為。若工具會要求補資料,再確認 stateless path 的 input_required 流程沒有把敏感值放入可重放的狀態。
遷移的完成條件不是「新 endpoint 回 200」,而是每個需要 session 的行為都有明確的新歸屬,且 legacy client 不會在切換後才發現 stream 或 RPC 消失。
常見問題
Q: 新的 stateless handler 能直接相容所有舊 MCP client 嗎?
A: 不能。它能處理一般 legacy tools、prompts 與 resources,但不提供完整 protocol session;依賴 pushed request、stream、replay、sampling、roots 或 session ID 的 client 必須走暫時 legacy lane 或完成應用層重設計。
Q: 新建 Cloudflare MCP server 還應該使用 McpAgent 嗎?
A: 不應該。Cloudflare 已將 McpAgent 標示為 deprecated、feature-frozen;新的無狀態 server 應使用 MCP SDK v2 factory 與 createMcpHandler,只在既有 server 遷移期間保留 legacy 路徑。
參考資料:
Cloudflare Changelog:Agents SDK v0.20.0 支援 MCP 2026-07-28
回報錯字、失效連結,或告訴我你想看的延伸主題。