Agents SDK:用 Workers 建 MCP Server | Cloudflare 完整教學

2026/09/10
Agents SDK:用 Workers 建 MCP Server | Cloudflare 完整教學

MCP(Model Context Protocol,模型情境協定) 是把「工具」標準化地暴露給 AI 的開放協定——被稱為「AI 世界的 USB-C」。上一篇我們搭好了有狀態 Agent 的骨架,這一篇要讓你的 Agent「被 AI 呼叫」:用 Cloudflare Agents SDKWorkers 上建一個遠端 MCP Server。你會認識 McpAgent / McpServer、用 this.server.tool(...) 定義 tools、釐清 authlessOAuth 的差別、了解 streamable HTTP / SSE 傳輸,並實際串接 Claude 等 MCP client、部署與測試。

前言

上一篇《Agents SDK 入門》,我們把 Agent 的「骨架」搭好了——它有專屬的持久記憶、有生命週期、能自己排程未來的任務。但那時候的 Agent,還是一個「自己會做事」的封閉系統:你得自己寫 HTTP 路由、自己定義前端怎麼呼叫它。

這一篇,我們要讓 Agent 進化到「被 AI 生態系呼叫來做事」。想像一下:你在 Claude 裡問「幫我查台北現在的天氣」,Claude 自己「知道」它有一個叫 getWeather 的工具可以用,於是它替你呼叫、拿到結果、再用自然語言回你。那個「天氣工具」是誰提供的?就是你——透過一個 MCP Server

先給一句話定義:

MCP(Model Context Protocol,模型情境協定)是一個開放標準,讓你把「工具(tools)、資源(resources)、提示(prompts)」以標準化的方式暴露給任何支援 MCP 的 AI 應用;而 Cloudflare Agents SDK 讓你能用 McpAgent 在 Workers 上,幾行程式就架起一個全球部署的「遠端 MCP Server」。

這裡有兩個關鍵詞。第一個是 MCP——它是「協定」,是一份大家講好的規格,規定了「AI 應用怎麼問你有哪些工具、怎麼呼叫工具、工具怎麼回傳結果」。第二個是 遠端(remote)——傳統 MCP Server 常常是跑在你自己電腦上的本地程式(用 stdio 溝通),但我們要建的是跑在 Cloudflare 邊緣、透過網路被連上的 server。

打個比方會更清楚。沒有 MCP 之前,每個 AI 應用想接你的服務,就像每個國家都用不同規格的插座——你得為 Claude 做一套整合、為某個 IDE 再做一套、為另一個 AI 產品又做一套,累死。MCP 就是那個統一的 USB-C 標準:你只做一個符合 MCP 的插頭(你的 Server 和它的 tools),所有支援 MCP 的裝置(client)都能直接插上來用。寫一次,到處被呼叫。

為什麼用 Cloudflare Workers 來當這個 Server 特別合適?因為 MCP Server 本質上就是一個「等著被連上、回應工具呼叫」的網路服務,而 Workers 天生就是全球部署、隨請求自動擴展、免維運的無伺服器平台。再加上 Agents SDK 幫你把 MCP 的協定細節、傳輸層都包好了,你幾乎只要專注在「我要提供哪些工具」。

讀完這篇你會掌握:

  • MCP 是什麼、遠端 MCP Server 的架構長什麼樣——以及它和上一篇「自己會做事的 Agent」的關係。
  • 怎麼用 McpAgent / McpServer 建一個 MCP Server——用 this.server.tool(...) 定義工具、McpAgent.serve("/mcp") 匯出。
  • authless 與 OAuth 怎麼選——什麼工具可以裸奔、什麼工具一定要驗證。
  • 怎麼串接 Claude 等 client、怎麼部署與測試——包含 MCP Inspector 與 mcp-remote

小提醒:MCP 規格與 Agents SDK 都在快速演進中,以下的套件、類別、方法名稱(如 McpAgentMcpServerthis.server.toolserve)以當下 Cloudflare 官方文件與 MCP 官方規格為準。若 API 名稱有出入,請以官方文件為主;本篇聚焦「觀念 + 最小可用範例」。

核心概念

一、MCP 協定:三種能力,一套標準

MCP 讓一個 Server 對外暴露三類東西,先認識它們:

MCP 能力是什麼生活比喻
Tools(工具)AI 可以「呼叫」的函式,會執行動作並回傳結果助理能替你做的動作(查天氣、算數、寄信)
Resources(資源)AI 可以「讀取」的資料(用 URI 定位)助理能翻閱的檔案/資料
Prompts(提示)預先定義好、可重用的提示範本助理常用的標準話術範本

本篇的主角是 Tools——這是 MCP 最常用、最直觀的部分。一個 tool 有三個要素:名字(AI 用它來指名要呼叫誰)、輸入結構(input schema)(告訴 AI 這個工具要吃什麼參數、型別是什麼)、執行邏輯(handler)(真正跑起來、回傳結果的函式)。

關鍵在於那個 input schema。AI 不會通靈——它怎麼知道 getWeather 要傳一個叫 city 的字串?就是靠你在定義工具時附上的 schema(通常用 zod 描述)。schema 定義得越清楚(包含每個欄位的 .describe(...) 說明),AI 越能正確地填參數、在對的時機呼叫對的工具。這一點是 MCP Server 品質的關鍵,後面「常見錯誤」會再強調。

二、Cloudflare 上的遠端 MCP Server 架構

把整條路徑串起來,一個部署在 Cloudflare 上的遠端 MCP Server 大致是這樣運作的:

┌──────────────┐    MCP over HTTP     ┌─────────────────────────────┐
│  MCP Client  │  (streamable HTTP    │   Cloudflare Worker (邊緣)   │
│  例如 Claude  │ ───/SSE 傳輸)──────▶ │                             │
│  / IDE 助理   │                      │   McpAgent.serve("/mcp")    │
│              │ ◀──工具清單 / 結果──── │      │                      │
└──────────────┘                      │      ▼                      │
                                      │   McpServer                 │
                                      │   ├─ tool("add", ...)       │
                                      │   ├─ tool("getWeather",...) │
                                      │   └─ (可選)state / SQLite   │
                                      └─────────────────────────────┘

流程拆解:

  1. Client 連上你的 Worker URL(例如 https://your-server.workers.dev/mcp),透過 HTTP 傳輸(streamable HTTP 或 SSE)建立連線。
  2. Client 問「你有哪些工具?」,你的 MCP Server 回一份工具清單(含每個工具的名字與 input schema)。
  3. AI 判斷需要用某個工具時,依 schema 填好參數,發出工具呼叫。
  4. 你的 handler 執行(可以呼叫外部 API、查 SQLite、動用其他 Cloudflare binding),把結果回傳。
  5. Client(AI)拿到結果,用自然語言呈現給使用者。

跟上一篇的關係:上一篇的 Agent 是「你自己定義入口、自己呼叫」;這一篇的 McpAgent(它其實也是建在 Durable Objects 上的 Agent)則是「用 MCP 這套標準協定當入口,讓 AI 應用來呼叫」。核心的「有狀態」能力還在——McpAgent 一樣可以有 this.statethis.sql,所以你的工具甚至能跨呼叫記住東西。

三、傳輸(Transport):為什麼是 streamable HTTP / SSE

MCP client 與 server 之間要有「傳輸層」來傳訊息:

  • stdio:本地 MCP Server 用的(server 是跑在你電腦上的子行程),我們這裡用不到。
  • SSE(Server-Sent Events):早期遠端 MCP 常用的傳輸,靠一條長連線讓 server 持續推訊息給 client。
  • streamable HTTP:MCP 規格後來推出的、更新更有彈性的傳輸方式,也是目前遠端 MCP 的推薦選項

好消息是,用 Agents SDK 的 McpAgent.serve("/mcp") 匯出時,SDK 會幫你處理傳輸細節,你通常不需要手刻 SSE 或 streamable HTTP。你只要知道:遠端 MCP 走的是網路長連線,而 Cloudflare 的邊緣能讓這條連線在一段閒置監控期間存活,工具呼叫不會因為連線稍有停頓就中斷。(傳輸的預設值與選項,以官方文件為準。)

四、關鍵術語一次記牢

術語是什麼
MCPModel Context Protocol,把工具/資源/提示暴露給 AI 的開放標準
MCP Server提供工具的一方(本篇要建的就是它)
MCP Client消費工具的一方(Claude、IDE 的 AI 助理等)
McpAgentAgents SDK 提供、建在 Durable Objects 上的 MCP Server 基底類別,來自 agents/mcp
McpServer來自 @modelcontextprotocol/sdk 的 server 物件,用來註冊 tools/resources
this.server.tool(...)McpServer 上定義一個工具:名字、說明、input schema、handler
serve("/mcp")McpAgent 匯出成 Worker handler,對外提供 MCP 端點
authless / OAuth前者無需驗證即可連;後者在前面掛一層授權流程

實作範例

觀念清楚後,我們動手建一個 authless(無授權)的遠端 MCP Server,提供兩個工具:add(兩數相加,展示最單純的工具)與 getWeather(查天氣,展示會呼叫外部 API 的工具)。

1. 安裝與設定

先裝好需要的套件:

# agents:Agents SDK 本體(含 agents/mcp)
# @modelcontextprotocol/sdk:MCP 官方 SDK(提供 McpServer)
# zod:定義工具的 input schema
npm install agents @modelcontextprotocol/sdk zod

wrangler.jsonc 的關鍵設定。因為 McpAgent 一樣建在 Durable Objects 上,所以要綁 DO binding、並用 migrations 宣告為 SQLite-backed 類別:

// wrangler.jsonc
{
  "name": "my-mcp-server",
  "main": "src/index.ts",
  "compatibility_date": "2026-09-01",
  "compatibility_flags": ["nodejs_compat"],

  // McpAgent 建在 Durable Objects 上,需要一個 DO binding
  "durable_objects": {
    "bindings": [
      { "name": "MyMCP", "class_name": "MyMCP" }
    ]
  },

  // 用 new_sqlite_classes 宣告為 SQLite-backed 的 Agent 類別
  // 提醒:舊的 migration 條目永遠不要修改,只新增帶新 tag 的條目
  "migrations": [
    { "tag": "v1", "new_sqlite_classes": ["MyMCP"] }
  ]
}

提醒:Agents SDK 的 TypeScript 設定通常繼承 agents/tsconfig,且不應開啟 experimentalDecorators。這些細節以官方 starter 範本與文件為準。

2. 定義一個 MCP Server 與它的 tools

這是核心。我們用 class MyMCP extends McpAgent 定義 server,在 init() 裡用 this.server.tool(...) 註冊工具:

// src/index.ts
import { McpAgent } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

type Env = {
  MyMCP: DurableObjectNamespace;
};

export class MyMCP extends McpAgent<Env> {
  // 建立 MCP server 實例:name/version 會回報給 client
  server = new McpServer({
    name: "benzhub-demo-mcp",
    version: "1.0.0",
  });

  // init() 在 server 啟動時執行,在這裡註冊所有 tools
  async init() {
    // 工具一:最單純的純運算工具
    // 參數依序:名字、說明、input schema(zod)、handler
    this.server.tool(
      "add",
      "將兩個數字相加並回傳結果",
      {
        a: z.number().describe("第一個加數"),
        b: z.number().describe("第二個加數"),
      },
      async ({ a, b }) => {
        // handler 回傳的 content 陣列,就是 AI 會看到的結果
        return {
          content: [{ type: "text", text: `結果是 ${a + b}` }],
        };
      }
    );

    // 工具二:會呼叫外部 API 的工具
    this.server.tool(
      "getWeather",
      "查詢指定城市目前的天氣",
      {
        // 清楚的 .describe() 會讓 AI 更容易正確填參數
        city: z.string().describe("城市名稱,例如「台北」或「Tokyo」"),
      },
      async ({ city }) => {
        try {
          const res = await fetch(
            `https://api.weather.example.com/${encodeURIComponent(city)}`
          );
          if (!res.ok) {
            // 出錯時回一段清楚的訊息,AI 能理解並轉述
            return {
              content: [{ type: "text", text: `查不到「${city}」的天氣` }],
            };
          }
          const data = (await res.json()) as { temp: number };
          return {
            content: [{ type: "text", text: `${city} 目前氣溫 ${data.temp}°C` }],
          };
        } catch (err) {
          return {
            content: [{ type: "text", text: `查詢失敗:${(err as Error).message}` }],
          };
        }
      }
    );
  }
}

// 把 McpAgent 匯出成 Worker handler,對外提供 /mcp 端點
// 這是 authless:任何拿到 URL 的 client 都能直接連
export default MyMCP.serve("/mcp");

這段程式碼有幾個重點值得停下來看:

  • this.server.tool(name, description, schema, handler) 是定義工具的核心。四個要素缺一不可:名字讓 AI 指名呼叫、description 讓 AI 判斷「什麼時候該用這個工具」、schema 讓 AI 知道要填什麼參數、handler 真正執行。
  • handler 的回傳是 { content: [...] }。最常見的是 { type: "text", text: "..." }——這段文字就是 AI 拿到的工具結果,它會據此組織自然語言回覆。
  • MyMCP.serve("/mcp") 把整個 MCP Server 匯出成 Worker 入口,對外開在 /mcp 路徑。這個版本是 authless——沒有任何驗證,適合公開、無敏感操作的工具。

3. (可選)有狀態的工具:讓工具跨呼叫記住東西

因為 McpAgent 建在 Durable Objects 上,它也能有狀態。舉個例子——一個「計數器工具」,add 會累加、get 會回傳目前值,而且跨多次呼叫記得:

import { McpAgent } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

type CounterState = { counter: number };

export class CounterMCP extends McpAgent<Env, CounterState> {
  server = new McpServer({ name: "counter-mcp", version: "1.0.0" });

  // 有狀態:初始計數
  initialState: CounterState = { counter: 0 };

  async init() {
    this.server.tool(
      "add",
      "把一個數字加進計數器,並回傳新的總計",
      { n: z.number().describe("要加上的數值") },
      async ({ n }) => {
        // 狀態變更一律走 setState(會持久化到 SQLite)
        this.setState({ counter: this.state.counter + n });
        return {
          content: [{ type: "text", text: `目前總計:${this.state.counter}` }],
        };
      }
    );

    this.server.tool(
      "get",
      "取得計數器目前的值",
      {},
      async () => ({
        content: [{ type: "text", text: `目前總計:${this.state.counter}` }],
      })
    );
  }
}

export default CounterMCP.serve("/mcp");

這正是 Cloudflare 版 MCP Server 的獨特之處:一般的 MCP Server 工具多半是無狀態的,但因為底層是 Durable Objects,你的工具可以擁有專屬記憶——這在「多步驟任務」「跟某位使用者綁定的工作階段」等場景很有用。(狀態相關 API 如 setStatethis.state 的行為,和上一篇《Agents SDK 入門》一致,以官方文件為準。)

4. authless vs OAuth:要不要加一層授權

上面兩個範例都是 authless——任何拿到 URL 的人都能連上呼叫。這對「純運算」「公開資料查詢」沒問題,但如果你的工具會動到使用者私有資料或執行敏感操作(讀信箱、動用付費 API、操作帳號),就該加上 OAuth

Cloudflare 提供 @cloudflare/workers-oauth-provider 這類套件,概念是用 OAuthProvider 把你的 MCP handler 包起來、掛上標準的授權端點:

// 概念示意:用 OAuthProvider 為 MCP Server 加上授權
// 實際套件名稱、端點與設定請以官方文件為準
import { OAuthProvider } from "@cloudflare/workers-oauth-provider";
import { MyMCP } from "./my-mcp";
import { AuthHandler } from "./auth-handler"; // 你自己實作的登入頁邏輯

export default new OAuthProvider({
  // 授權後,把 /mcp 的請求交給 MCP Server 處理
  apiHandlers: {
    "/mcp": MyMCP.serve("/mcp"),
  },
  authorizeEndpoint: "/authorize", // 使用者授權頁
  tokenEndpoint: "/token",         // 換發 token
  clientRegistrationEndpoint: "/register",
  defaultHandler: AuthHandler,     // 處理未授權請求(顯示登入)
});

判斷原則很簡單:公開、無害的工具用 authless 上手最快;只要牽涉到「誰的資料、誰的權限」,就上 OAuth。 別把「會讀私有資料」的工具裸奔在網路上。

5. 部署與測試

寫好後,先本地開發,再部署:

# 本地開發:server 通常會跑在類似 http://localhost:8787/mcp
npm run dev

# 部署到 Cloudflare 邊緣
npx wrangler deploy
# 部署後會得到一個 https://your-server.workers.dev/mcp 端點

測試 MCP Server 最方便的工具是官方的 MCP Inspector——它是一個能連上你的 server、列出工具、手動試呼叫的圖形介面:

# 啟動 MCP Inspector,連上你的 server 逐一測試工具
npx @modelcontextprotocol/inspector@latest

6. 串接 Claude 等 MCP client

最後一步:把它接進真正的 AI 應用。以 Claude Desktop 為例,遠端 MCP Server 通常需要透過 mcp-remote 這個代理,在設定檔裡這樣寫:

{
  "mcpServers": {
    "benzhub-demo": {
      "command": "npx",
      "args": ["mcp-remote", "https://your-server.workers.dev/mcp"]
    }
  }
}

設定完重啟 client,它就會連上你的遠端 MCP Server、抓到 addgetWeather 這些工具。之後你在 Claude 裡問「幫我算 128 加 256」或「查一下東京的天氣」,它就會自己判斷、呼叫你的工具、把結果轉成自然語言回你。(各 client 的設定格式與 mcp-remote 的用法會演進,以官方文件為準。)

常見錯誤與最佳實踐

MCP Server 的坑,大多不在「寫不出來」,而在「AI 用不好」或「該擋沒擋」。以下是最高頻的幾個。

坑一:tool 的 schema 與 description 寫得太模糊,AI 不會用或用錯。

MCP Server 最容易被忽略的真相是:你的工具好不好用,九成取決於 schema 與 description 寫得清不清楚。 AI 是靠 description 判斷「什麼時候該用這個工具」、靠 input schema 判斷「要填什麼參數」。如果你只寫 city: z.string() 而沒有 .describe(...),AI 可能不知道要填城市名還是城市代碼;description 若含糊,AI 甚至根本不會在對的時機呼叫它。正確做法:每個工具給清楚的 description(說明它做什麼、何時該用),每個欄位都加 .describe(...) 說明用途與格式範例。 把它當成「寫給 AI 看的 API 文件」。

坑二:把會讀私有資料/敏感操作的工具做成 authless。

authless 上手最快,但它的意思是「任何拿到 URL 的人都能呼叫」。如果你把「讀某位使用者的信箱」「發起一筆付款」這種工具裸奔成 authless,等於把後門開在公網上。正確做法:只有公開、無副作用、無敏感資料的工具才用 authless;只要牽涉到使用者身份、私有資料或會花錢/改狀態的操作,一律上 OAuth,讓 client 先通過授權才拿得到工具。

坑三:忘了遠端 MCP 走的是網路傳輸,拿本地 stdio 的設定去連。

有人把本地 MCP Server(stdio)的接法,直接套到我們這種部署在 workers.dev 的遠端 server 上,結果連不上。正確認知:遠端 MCP Server 走的是 HTTP 傳輸(streamable HTTP / SSE),不是 stdio。 client 端(如 Claude Desktop)連遠端 server 時,通常要透過 mcp-remote 這類代理來橋接。用 Agents SDK 的 serve() 匯出時,server 端的傳輸細節 SDK 會處理好,你主要要顧的是 client 端怎麼指向你的 URL。

坑四:handler 出錯時直接讓它拋例外、沒回有意義的訊息。

工具 handler 裡去 fetch 外部 API 是很常見的,但外部 API 會失敗。如果你放任例外往外拋,AI 拿到的可能是一團看不懂的錯誤。正確做法:在 handler 裡 try/catch,失敗時回一段人類/AI 都讀得懂的 content 文字(例如「查不到這個城市」),讓 AI 能優雅地把「查詢失敗」轉述給使用者,而不是整個對話卡住。

坑五:忘了 McpAgent 也建在 Durable Objects 上,漏掉 DO binding 與 migration。

因為 MCP Server 感覺很「無狀態」,有人會忘記它底層還是 Agent、還是要在 wrangler.jsonc 裡設 durable_objects binding 和 new_sqlite_classes migration,結果部署失敗。正確做法:比照上一篇的 Agent,把 DO binding 與 migration 補齊;每個 McpAgent 類別都要有對應的 binding 與遷移條目。

最佳實踐小結: schema 與 description 當成「寫給 AI 的文件」認真寫;公開工具用 authless、敏感工具上 OAuth;記得遠端走 HTTP 傳輸、client 常要 mcp-remote 橋接;handler 一律 try/catch 回有意義訊息;別漏了底層 Durable Objects 的 binding 與 migration。API 名稱與細節請以官方文件為準。

小結

上一篇《Agents SDK 入門》,我們搭好了「有狀態、會記憶、能排程」的 Agent 骨架,但它還是個「自己會做事」的封閉系統。這一篇《Agents SDK:MCP Server》,我們讓它跨進 AI 生態系,學會被 Claude 等應用呼叫:

  • MCP 是什麼——Model Context Protocol,把工具/資源/提示標準化暴露給 AI 的開放協定,人稱「AI 世界的 USB-C」;寫一次,到處被呼叫。
  • 怎麼用 Cloudflare 建遠端 MCP Server——class X extends McpAgent、在 init() 裡用 this.server.tool(name, description, schema, handler) 定義工具、X.serve("/mcp") 匯出成 Worker handler;底層一樣是 Durable Objects,工具甚至可以有狀態。
  • authless vs OAuth——公開無害的工具用 authless 最快;牽涉私有資料/敏感操作就用 OAuthProvider 上一層授權。
  • 傳輸與串接——遠端 MCP 走 streamable HTTP / SSE(SDK 幫你處理),client 端常透過 mcp-remote 橋接;用 MCP Inspector 測試、在 Claude Desktop 設定檔指向你的 URL。
  • 關鍵坑——schema/description 要清楚、敏感工具別裸奔、記得走 HTTP 傳輸、handler 要 try/catch、別漏 DO binding 與 migration。

現在你的 Agent 不只自己會做事,還能被整個 AI 生態系當成工具來呼叫了。但到目前為止,我們和 Agent 的互動大多還是「一來一往」的請求。下一篇《Agents SDK:即時 WebSocket 與 React》,我們要讓 Agent「活起來」——用 WebSocket 做雙向即時通訊、用 useAgent React Hook 讓前端和 Agent 的狀態即時同步,打造像多人協作、即時聊天那樣的體驗。我們下一篇見。

想深入 MCP 與 Cloudflare Agents 的完整 API,可以參考 Cloudflare Agents 官方文件Model Context Protocol 官方規格(套件、類別與方法名稱以官方文件為準)。

BenZ Software Developer

熱愛技術的軟體開發者,在這裡分享程式開發經驗與學習筆記。

本週主打

AI 自動化入門包

你每天手動在做的那些煩事,其實 AI 可以自己跑。這份給你 10 個照著做就會的自動化工作流 + 50 個複製即用的提示詞,不用會寫程式。

看看這個產品 →