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 差異,並非免費抽象層。

- 同一團隊、同一 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,官方 API 的 descendants 欄位為 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,不再依賴舊版的 initialize 與 Mcp-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/list/tools/call;故意讓 Mcp-Method 或 Mcp-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實作。

依 2026-07-28 client registration 規範,實務優先序可以記成:
- Pre-registration:固定、受控的 client 先配好
client_id,最容易治理。 - CIMD:若 authorization server 宣告支援,client 用 HTTPS metadata document URL 作為身分;authorization server 必須驗證文件內容,並精確比對 redirect URI。2026 年 9 月 CIMD 仍是 Internet-Draft,不要把它當成每家都已支援的 RFC。
- 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 路徑。 - 人工設定:都不支援時,清楚提示使用者輸入既有 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/callschema沒有通用 idempotency key;JSON-RPCid只負責關聯 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 外。

可直接貼進 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。






