1146 字
6 分鐘

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 ExportedHandler

allowedHostnames 只填 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,同時保留暫時性的 createLegacyMcpHandlerMcpAgent route。等 client 切換、舊 session 排空後,再移除 legacy lane。

實務上可把遷移拆成四步:

  1. 列出每個 tool 是否讀寫 session、RPC、stream 或 pushed request。
  2. 先把無狀態 tools 以 SDK v2 factory 暴露在新 route,保留原路徑不動。
  3. 對有狀態的功能建立應用層資料邊界與過期策略,讓新 route 不再仰賴 MCP session。
  4. 監測 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

Cloudflare Agents Docs:MCP handler APIs

Cloudflare Agents Docs:遷移至 MCP SDK v2

Cloudflare Agents 的 McpAgent 要怎麼遷移?先分出 stateless 與 legacy 路徑
https://laplusda.com/posts/cloudflare-agents-mcp-sdk-v2-migration/
作者
Zero
發佈於
2026-08-01
許可協議
CC BY-NC-SA 4.0
這篇文章有幫助嗎?

回報錯字、失效連結,或告訴我你想看的延伸主題。