跳到主要內容

【2026 最新】MCP Production 實戰:API/CLI 決策、OAuth、CORS 與 DCR 7 關

最後更新: ·
MCP Production 實戰教學首圖:OAuth、CORS 與跨 client 驗收

MCP Production 的真正難題,不是把一個「Hello World」工具接上模型,而是回答三件事:它是否比既有 API/CLI 更值得維護、不同 client 是否能完成同一條授權流程,以及 token 過期、重複呼叫、撤銷與版本錯配時,系統會不會安全地失敗。2026 年 9 月回頭看,MCP 已有 2026-07-28 正式協定、穩定 TypeScript SDK 與多個產品入口,但「支援 MCP」仍不等於「可以直接上線」。

這篇不做另一個只能展示工具清單的 demo。我們會先決定何時根本不該用 MCP,再用 2026-07-28 協定與 TypeScript SDK v2 建立最小的無狀態 HTTP handler,最後用 OAuth、CORS、權限、重試與跨 client 故障矩陣完成七道驗收。本文可重現的執行證據限於本機 SDK smoke test;Claude、ChatGPT 與瀏覽器列的是依各家官方文件設計的驗收步驟,必須在你的帳號與環境重新跑過,不能把「官方說支援」當成通過紀錄。

先別寫程式:API、CLI、function calling、MCP 怎麼選?

把它想成一間餐廳。function calling 是同一個廚房裡,主廚直接叫助手做事;API 是有固定格式的外送窗口;CLI 是員工在後場用指令操作;MCP 則像一套讓多家點餐平台都能讀懂菜單、呼叫服務、取得資源的共同接頭。共同接頭很有價值,但它同時多出版本協商、工具 discovery、授權 metadata 與 client 差異,並非免費抽象層。

API、CLI、function calling 與 MCP 的採用決策矩陣
先看 ownership、部署邊界、client 數量與工具風險;原圖為 1200×630,可另存使用。
  • 同一團隊、同一 runtime:優先用 function calling 或普通函式。你不需要為內部函式加一層網路協定。
  • 服務要給多種程式使用:先做邊界清楚的 REST/RPC API。它通常仍是資料與商業邏輯的 source of truth。
  • 主要使用者是工程師與自動化工作:CLI 很適合可組合、可 diff、可在 CI 執行的任務;若只有一個操作介面,CLI 往往更直接。
  • 同一組工具要被多個 AI client 探索與呼叫:MCP 才開始有優勢。它可以放在既有 API 前面當 adapter,不必重寫核心服務。
  • 只有一個 client、工具又會改資料:先停一下。此時新增協定面、OAuth 面與模型誤用面,可能比可攜性收益更大。

2026 年 9 月 5 日 07:48(台北時間)重查 Ask HN 討論時有 160 points,官方 APIdescendants 欄位為 180;其中同時出現 production 使用案例,以及 CLI 較省事、schema 吃掉 context、瀏覽器 CORS 和客製 OAuth 卡住等反例。它是很好的需求訊號,但留言是個人經驗,不是協定證據。若你的主問題仍是 token 成本,先讀 AlphaLab 的 MCP vs CLI 配對 A/B 教學;這篇只把 schema 成本當成七關之一。

MCP Production 第 1 關:先把協定年代釘死

2026-07-28 版把核心 HTTP 模式改成「每個 request 自帶資訊」:client 可以先呼叫 server/discover,之後每個 JSON-RPC request POST 都攜帶 protocol version、method、需要時的 name,以及 envelope metadata,不再依賴舊版的 initializeMcp-Session-Id。官方仍保留 legacy 相容路徑,因此 server 回得出結果,不能證明它走的是新版。

第一張 receipt 應保存「實際協定版本、discovery 結果、client 名稱/版本、server build SHA、測試時間」。官方 TypeScript SDK v2 協定版本指南提供 versionNegotiation: { mode: 'auto' } 探測新版並在必要時回退;client 預設仍是 legacy,spawn-per-invocation 的 stdio CLI 也不適合盲目開 auto。若 production 要求新版語意,就應明確 pin 支援版本並拒絕悄悄降級,而不是看套件版本猜線上協定。

驗收問題不是「server 活著嗎?」而是「這個 client、這次 request,究竟用哪個年代的規則完成?」

第 2 關:建立最小 stateless handler,先跑本機 smoke test

先把網路、OAuth 與雲端部署拿掉,只測你真的會出貨的 handler。以下範例使用 Node.js 20 以上與官方 TypeScript SDK v2@modelcontextprotocol/server@modelcontextprotocol/client 與 Zod;安裝後把 lockfile 一起提交,避免「昨天的最新版」變成不可重現條件。

mkdir mcp-production-lab && cd mcp-production-lab
npm init -y
npm install @modelcontextprotocol/server@2 @modelcontextprotocol/client@2 zod

建立 smoke.mjs。工具故意是 read-only 的價格計算,讓第一輪不必處理外部副作用:

import assert from "node:assert/strict";
import { Client, StreamableHTTPClientTransport }
  from "@modelcontextprotocol/client";
import { createMcpHandler, McpServer }
  from "@modelcontextprotocol/server";
import * as z from "zod/v4";

function buildServer() {
  const server = new McpServer({ name: "price-lab", version: "1.0.0" });
  server.registerTool("apply-discount", {
    description: "計算折扣後價格;不會寫入任何資料",
    inputSchema: z.object({
      price: z.number().nonnegative(),
      percent: z.number().min(0).max(100)
    }),
    outputSchema: z.object({ total: z.number() })
  }, async ({ price, percent }) => {
    const total = price * (1 - percent / 100);
    return {
      content: [{ type: "text", text: String(total) }],
      structuredContent: { total }
    };
  });
  return server;
}

const handler = createMcpHandler(buildServer, { legacy: "reject" });
const transport = new StreamableHTTPClientTransport(
  new URL("http://test.local/mcp"),
  { fetch: (url, init) => handler.fetch(new Request(url, init)) }
);
const client = new Client(
  { name: "acceptance", version: "1.0.0" },
  { versionNegotiation: { mode: "auto" } }
);

await client.connect(transport);
const tools = await client.listTools();
assert.equal(tools.tools[0].name, "apply-discount");
const result = await client.callTool({
  name: "apply-discount",
  arguments: { price: 80, percent: 25 }
});
assert.deepEqual(result.structuredContent, { total: 60 });
console.log(JSON.stringify({ ok: true, total: 60 }));
await client.close();
await handler.close();

執行 node smoke.mjs 後,範例只會印出 {"ok":true,"total":60};這一行代表程式內的 tool list 與 call assertions 都通過,不是三段網路 trace,也沒有把協定年代寫進輸出。這種 in-process transport 沒有開 port,但仍穿過同一個 createMcpHandler;它適合測 handler 與 schema,不代表 DNS、TLS、proxy、CORS 或 OAuth 已通過。正式掛到 HTTPS 前,要依官方 HTTP serving 指南在 handler 前驗證 Host、Origin 與 token,再對真實 endpoint 跑一次測試。

AlphaLab 本機 receipt(2026 年 9 月 5 日):使用 Node.js 24.15.0、server/client SDK 2.0.0,無 listening socket 的 acceptance harness 有 7 個範圍化 assertions 通過。它以 server/discover 協商到 2026-07-28 modern era,完成 read-only tools/listtools/call;故意讓 Mcp-MethodMcp-Name 與 body 不一致時,兩者都在 McpServer factory/tool dispatch 前收到 HTTP 400 與 JSON-RPC -32020;陌生 Origin 也在 factory 前收到 403。另有第 8 個缺少版本 header 的觀察揭露下一段的 release blocker,所以不能把「7 個 assertion 通過」解讀成整體可上線。這份 receipt 沒有測 OAuth、CORS response policy、真實 proxy、效能或任何 Claude/ChatGPT client,不能外推成跨 client 通過。

目前版本的反例也要保存:同一套 harness 刪掉 modern request 的 MCP-Protocol-Version header 後,npm 最新的 server SDK 2.0.0仍回 HTTP 200 並進入 factory,而不是規格要求的 400/-32020。官方 issue #2589已有重現,修正也已透過 PR #2590合併,但 2026 年 9 月 5 日尚未發布成新的 npm stable。上線前要重查套件版本;修正進版前,在 edge/gateway 先驗 required headers,並把「缺 header」保留在 regression suite,不能只依賴 SDK 當 policy enforcement。

第 3 關:把 CORS 與 Origin 驗證拆成兩件事

很多「Claude 能連、瀏覽器不能連」不是 MCP 壞掉,而是 browser 在 POST 前先送 preflight。CORS 決定瀏覽器是否准許前端讀取回應;Origin 驗證則是 server 主動拒絕陌生來源,降低跨站請求與 DNS rebinding 風險。只加 Access-Control-Allow-Origin: * 並沒有完成第二件事,帶 credential 時也不應使用萬用來源。

  • CORS 允許清單寫完整 serialized origin,例如 https://app.example.com;不要用字串包含判斷。若 server 動態回映合法 Origin,也要加 Vary: Origin
  • OPTIONS 要允許 Access-Control-Request-Headers 實際列出的 header;Authorization 必須明列,萬用值不會替 credential header 放行。Accept 通常是 safelisted,未必出現在 preflight。若 browser 要讀 401 的 discovery challenge,response 還要對合法 origin 加 Access-Control-Expose-Headers: WWW-Authenticate
  • TypeScript SDK 在非 browser 環境可依 schema 的 x-mcp-header 映射 Mcp-Param-*;它的 browser build 會略過這種動態 header,因為 credentialed CORS 無法事先列出任意名稱。這形成互通限制:帶有該 annotation 的參數若出現在 body、header 卻缺席,conforming server 必須回 400/-32020,所以 browser-facing 工具不要在未逐 client 驗證前依賴它。
  • 非瀏覽器 client 可能沒有 Origin。這不代表自動可信,仍要靠 TLS、token audience、scope 與網路邊界。
  • localhost 服務綁 127.0.0.1,production remote endpoint 使用 HTTPS;反向代理要保留必要 header,並在 handler 前同時驗 Host 與 Origin。Origin 檢查不能擋住 same-origin DNS rebinding,官方 SDK 因此把 Host validation 列成獨立防線。

先用 browser DevTools 保存兩張 receipt:preflight 的 status/allow headers,以及真正 POST(包含未授權 401)的 request/response headers。官方 SDK 的 Origin validation middleware是另一層:例如 localhostAllowedOrigins() 按 hostname 判斷、port 不影響結果;它不會自動替跨域 route 產生所有 CORS response header。還要注意:TypeScript SDK v2 browser client 使用 auto mode 時,新版 probe 若被舊 CORS allowlist 擋成不透明 TypeError,可回退 legacy,因此最後一定要保存實際協定年代。

第 4 關:OAuth 先發現 resource,再決定 CIMD 或 DCR

OAuth 對 MCP 整體是 optional;但受保護的 remote HTTP endpoint 一旦採用 OAuth,MCP server 在邏輯角色上就是 resource server,發 token 的 authorization server 通常交給專門 IdP。無論是否同一組織營運,都不能把 client 給 MCP server 的 token 當成「拿到任何 token 就轉傳給上游 API」的通行證。正確順序是:未授權 request 收到 401;challenge 有 resource_metadata 時跟隨該 URL,沒有時依規格建立 well-known Protected Resource Metadata URL。resource metadata 必須至少列一個 authorization_servers;若有多個,client 要把它們視為彼此獨立的候選授權伺服器。

接著,client 必須依規格順序嘗試 RFC 8414 與 OIDC metadata endpoint,並拒絕 metadata 內 issuer 與建構 URL 所用 issuer 不完全相同的結果。決定 client registration、完成 PKCE 後,authorization request 與 token request 都必須帶 resource=<canonical MCP server URI>,即使 authorization server 沒宣告支援也一樣;最後才用對應 scope/audience 的 token 重試原 MCP request。

這裡還有一個容易漏掉的 SSRF 關卡:resource_metadata、authorization-server metadata 與 CIMD 文件 URL 都可能受攻擊者影響。Production fetcher 只接受 HTTPS,除明確的本機開發例外外,要阻擋 private/reserved/loopback/link-local/cloud metadata 位址,對 DNS 做重驗證或固定解析結果/受控 egress,並逐跳驗證 redirect;authorization server 取得 CIMD 文件時也不能盲目跟隨 redirect。具體邊界可依官方 MCP Security Best Practices實作。

MCP OAuth 授權 discovery、CIMD 與 DCR 流程圖
Production OAuth 的核心是可發現、可驗證、可拒絕;不是把登入頁打開就算完成。

2026-07-28 client registration 規範,實務優先序可以記成:

  1. Pre-registration:固定、受控的 client 先配好 client_id,最容易治理。
  2. CIMD:若 authorization server 宣告支援,client 用 HTTPS metadata document URL 作為身分;authorization server 必須驗證文件內容,並精確比對 redirect URI。2026 年 9 月 CIMD 仍是 Internet-Draft,不要把它當成每家都已支援的 RFC。
  3. DCR:為尚未支援 CIMD 的 authorization server 與早期 MCP auth 實作保留 backward-compatible fallback。authorization server 必須宣告 registration_endpoint;client 要選正確的 application_type,並把保存的 credential 綁到該 authorization-server issuer。新版 MCP 已把 Dynamic Client Registration 標成 deprecated,不應把它當唯一 production 路徑。
  4. 人工設定:都不支援時,清楚提示使用者輸入既有 client credential,不要暗中自建註冊流程。

驗收時把兩個邊界分開。在 resource server 端,過期/無效 token 或錯誤 intended audience 應回 401;scope 不足應回 403,並以 WWW-Authenticate 說明 insufficient_scope,而不是讓工具執行後才報錯。在 authorization callback 端,只要 response 帶 iss,client 就必須與預期 issuer 精確比對;若 metadata 宣告 authorization_response_iss_parameter_supported=true,response 卻沒有 iss,也必須在換 token 前拒絕。Bearer token 只能放在 Authorization header。完整 MUST/SHOULD 以官方 Authorization 規範為準。

第 5 關:最小權限、重複副作用與撤銷要一起測

依官方 Tools 規範,tool annotation 應視為不可信提示;工具 description 與 read-only/destructive annotation 不是 authorization policy。真正的允許判斷必須在 server 端,綁定 principal、tenant、tool、resource 與 scope。把「查訂單」和「取消訂單」拆成兩個工具,也比在一個巨大工具裡放 action=delete 更容易審查。

  • Read 工具:用最窄 scope;結果做欄位級過濾,避免同一 tenant 的 token 讀到別人的資料。
  • Write 工具:加入明確確認、審批或 allowlist;高風險操作預設拒絕。
  • 重複呼叫:現行 tools/call schema沒有通用 idempotency key;JSON-RPC id只負責關聯 request/response,idempotentHint也只是 annotation。要在工具參數或商業 API 設計 durable key 與唯一約束,重試時沿用 business key;同一 key、同一參數回原結果,同一 key、不同參數直接拒絕。
  • 撤銷:使用 authorization server 提供的 OAuth revocation/session 管理能力,並定義 verifier 與快取失效 SLA;2026-07-28 MCP authorization 規範本身沒有定義「按下撤銷後幾秒生效」。
  • Token passthrough:不要把 client 給 MCP server 的 token 原封不動交給下游 API;下游 credential 要獨立取得並限制 audience。

故障測試要真的送兩次相同 write request,再撤銷 token 後重送。合格結果是「只產生一次副作用、第二次可稽核地命中舊結果、撤銷後不再執行」。如果你正在建立整個 Agent 的 admission control、預算與 kill switch,可接著看 AI Agent Runtime Controls 實作

第 6 關:觀測 schema、成本與失敗,不記錄秘密

「200 OK」太粗糙。每次呼叫至少記錄 correlation ID、協定版本、server build、client 類型、匿名化 principal、tool 名稱、schema hash、scope verdict、耗時、結果類別、重試次數、idempotency verdict 與副作用 receipt。token、authorization code、完整提示、個資與工具回傳原文不應直接進 log;需要除錯時,以欄位 allowlist、雜湊或短期受控取樣處理。

schema 也是 production 預算。每個工具多一段名稱、描述與參數,client 的載入策略又不完全相同。設定三個上限:預設可見工具數、每個 schema 的序列化 bytes、一次任務實際載入的總 bytes;超標就拆 server、縮短描述或使用 client 支援的延遲 discovery。不要宣稱「用了 MCP 就自動省 token」,應把相同任務、相同模型、相同輸出品質做配對測試。AlphaLab 的 MCP Tool Search 與 lazy schema 教學專門處理這一題。

MCP Production 第 7 關:跨 client 故障矩陣才是上線門票

最後不要做一個「各 client 都連得上」的勾勾表,而要讓每個 client 跑同一套正常與故障案例。先以官方 SDK harness/MCP Inspector 建立協定基準,再接真實產品:

  • Protocol harness:保存 discovery、實際版本、tool list、schema hash、正常 call 與錯誤 code;它是最容易自動化的基準。
  • Claude Code:官方目前提供 claude mcp add --transport http 名稱 URL/mcp 授權流程。以 Claude Code MCP 文件為你的版本基線,記錄實際走 pre-registration、CIMD 或 DCR 哪一條。
  • ChatGPT:在具備權限的 workspace 建立 custom app,填 remote endpoint 與 OAuth,掃描工具後再測。2026 年 9 月 5 日查閱時,OpenAI 的 Developer Mode 文件與較新的 Help Center對方案資格仍有差異;以你的帳號/workspace admin 畫面為準,不要把別人的 plan 截圖當能力證明。
  • 瀏覽器 client:從指定 production origin 執行 preflight、登入、callback、list、read 與 write;這一列專門抓 CORS、redirect URI 與 cookie/token 儲存問題。

每列依序注入:缺少或不符的 protocol header、陌生 Host/Origin、metadata SSRF URL、過期 token、錯誤 authorization-response iss/token audience、scope 不足、被撤銷 token、工具 schema 變更、同一 write 重送兩次、上游 timeout。記錄「預期 status/error、實際結果、是否產生副作用、receipt 連結」。某 client 若只支援舊版,這不是自動 fail;你要明確決定維持 legacy 相容層、隔離 endpoint,或把它排除在 support matrix 外。

MCP Production 七關驗收清單與放行標準
MCP Production 七關驗收表:原圖為 1200×630,可另存後附進 release ticket。

可直接貼進 release ticket 的放行條件

  • 每個受支援 client 都有日期、版本、協定年代與 build SHA receipt。
  • read 工具在正常 token 下通過;越 tenant、錯誤 audience 與過期 token 全部被拒絕。
  • write 工具經確認後只產生一次副作用,重送不會重複扣款、寄信或刪除。
  • 撤銷後在既定 SLA 內失效;cache、長連線與 refresh token 都納入測試。
  • 陌生 Host/Origin、不合法 redirect URI 與 metadata SSRF URL fail closed;合法 browser preflight 與 401 challenge 可讀。
  • schema hash 或工具集合變動會觸發審查,新增 write 工具不會自動暴露。
  • timeout、限流、上游 5xx 與 client 中止可觀測,且不洩漏 token 或個資。
  • 工具數、schema bytes、延遲與失敗率低於團隊設定的預算;沒有用「業界標準」取代數字。

八項都能附上 receipt 才放量。先給內部測試者與 read-only 工具,接著 canary 一小群使用者,最後才逐步開 write;任何一個 authorization 或重複副作用案例失敗,就回到 staging。若需要更完整的 shadow mode、canary 與 kill-switch 設計,可參考 EarlyEval 的可逆上線方法Agent 可觀測性教學

常見問題

1. 有 API 之後,還需要 MCP 嗎?

不一定。API 可以繼續當核心服務,MCP 只在外面做 AI client adapter。若只有一個 client,維持 API 往往更簡單。

2. MCP 可以取代 function calling 嗎?

兩者層級不同。function calling 是模型或應用內的呼叫機制;MCP 定義 client 與外部 server 如何 discovery、交換工具/資源與授權。應用仍可能用 function calling 來驅動 MCP 工具。

3. Stateless 是否代表 server 不能有資料庫或快取?

不是。它表示 protocol request 不依賴舊 session 狀態;資料庫、連線池與共享 cache 仍可放在 handler 外部,但授權與 tenant 邊界要按 request 重建。若你發出 state handle,仍要把它當成不可信、近似 bearer 的輸入:做完整性保護、設定期限,並在 server 端綁定已驗證 principal 與原始操作。

4. 為什麼 CLI 能用,browser 卻被 CORS 擋住?

CORS 是瀏覽器安全模型,CLI 通常不執行同樣的 preflight。這也是為什麼跨 client 驗收不能只跑命令列。

5. CIMD 與 DCR 要選哪一個?

受控 client 先用 pre-registration;authorization server 與 client 都支援時優先 CIMD;DCR 作為 authorization server 尚未支援 CIMD 或早期 auth 實作的相容 fallback。不要假設每個 authorization server 都支援同一條路。

6. Claude 與 ChatGPT 都顯示工具,就代表相容嗎?

不代表。顯示 tool list 只通過 discovery;還要測版本 receipt、授權、錯誤碼、撤銷、write approval 與重複副作用。

7. Token 撤銷後應該立刻失效嗎?

取決於 authorization server、token 類型與你的 cache。團隊要明訂可接受 SLA 並實測,而不是假設 MCP 自動提供即時撤銷。

8. 工具越多,MCP 的價值越高嗎?

不一定。更多工具可能增加 discovery 與 schema context 成本,也讓權限面變大。依 domain 拆 server、量 schema bytes,再用實際任務評估品質與成本。

接著閱讀

左右滑動查看更多推薦

結論:MCP 值不值得,用失敗時的行為回答

MCP 的 production 價值不是「比 API 新」,而是同一組能力確實需要跨 client discovery、可攜與一致治理。最小 handler 跑起來只是第 2 關;真正的門檻是版本不悄悄漂移、陌生 Host/Origin 進不來、錯誤 token 沒有副作用、重送只執行一次、撤銷在 SLA 內生效,而且每一項都有 receipt。若七關的維護成本高於多 client 的收益,選 API 或 CLI 不是退步,而是正確工程決策。

想把這套驗收延伸成完整 AI Agent 的開發流程,可先回到 AlphaLab AI 專區整理學習地圖,再從 AI 與投資實作課程挑選下一個主題,並把本文 PNG 清單直接附到自己的 release ticket。

ALPHALAB 社群

有問題?來 Telegram 聊

和 Terry、編輯、其他網友一起討論這篇文章。提問、分享觀點,回覆更即時。

加入 Telegram 討論

📩 訂閱 AlphaLab 電子報

每週最多三封:一封 Weekly 週報與最多兩封關鍵 Alpha Signal。

我們不會 spam,隨時可退訂。