「幫我查 order 123 的退款資格,如果可以就直接退款。」
這句話放進 Agent 之後,模型可能需要先連到訂單 MCP Server 查詢狀態,再連到付款 MCP Server 執行退款。執行期間,Server 可能剛好重新部署、工具清單可能已經更新,兩個 Server 甚至可能都提供同名的 get_order。因此,問題不再只是模型能不能產生正確的 JSON,而是:
Agent 此刻看見了哪些外部能力?它用的是哪個 Server、哪一版定義?呼叫回來之後,什麼證據才足以讓 State 前進?
前面建立的 Agent 已經有 Loop、State、Checkpoint 與 Memory。現在加入 MCP,等於把「下一步可以做什麼」從程式內固定的函式,擴展成執行期間可以協商與更新的能力目錄。這讓整合不同系統變容易,也讓 Runtime 必須多負責一層動態性。
MCP 不是把既有工具換一種傳輸格式,而是把外部能力變成一個動態控制面。
可靠的 MCP Agent 至少要把「協商、發現、篩選、路由、呼叫、驗證、提交」分開記錄與判斷。
在只有本地函式的 Agent 裡,工具通常在程式啟動時就註冊完成;模型從固定清單中挑一個名稱,再交給 Harness 執行。MCP 將這個邊界往外推:工具、資料與提示模板可以由不同團隊維護的 Server 提供,Client 在執行期間建立連線並取得能力。
MCP 的 Architecture 把責任分成三個角色:
| 角色 | 在 Agent 執行中的責任 |
|---|---|
| Host | 持有使用者工作階段、模型與整體 Harness;決定哪些 Server 可以被接上、哪些資料可以進入 Context,以及何時需要同意。 |
| Client | 通常一個 Server 一條隔離連線;負責 initialize、能力協商、訊息傳遞與斷線處理。 |
| Server | 提供 Tools、Resources、Prompts 等能力;可以獨立部署、更新或暫時不可用。 |
這個分工帶來一個很容易被忽略的結論:MCP 的工具清單不是靜態設定,而是 Agent 在某一個時間點看到的環境快照。 同一個 Agent,連到不同 Server、不同版本,模型可以做的事情就可能不同。
所以 MCP 解決的是「能力如何互通」,不是「這個能力此刻是否適合使用」。是否允許某個使用者退款、是否仍符合付款服務的前置條件,以及完成後要不要更新業務 State,仍然是應用程式 Runtime 的責任。這正是 MCP Agent 與前面單純的工具呼叫不同的地方。
一輪 MCP Agent 執行,不應直接從模型的 tools/call 開始。最小流程可以拆成七個交接點:
initialize,交換 protocol version 與雙方 capabilities;完成後才送 initialized notification。這是 Lifecycle 規格 定義的第一個生命週期邊界。tools/list、resources/list 或 prompts/list。清單可能分頁,不能假設一次就拿完。outputSchema 驗證 structuredContent,並區分協定層錯誤與工具執行錯誤。isError 是結果訊號,不是業務完成收據。Tools 規格也要求 Client 不要盲信工具的提示性 annotations。unknown。這七步的價值,在於把「連線當下看見什麼」和「這次呼叫最後造成什麼」分開。若把它們壓成一個 call_tool(),之後就很難回答模型為什麼看見某個工具、呼叫送到哪裡,以及 State 是依據哪一份結果改變。

長時間工具也會放大這個問題。2025-11-25 規格中的 Tasks 是實驗性機制,讓 tools/call 可以先回傳 task,再由 Client 取得延後結果。它解決的是「結果還沒回來時如何保存工作」,不代表付款服務已經完成;最後仍要用領域證據判斷能否提交。
當 Agent 只有三個工具,把完整描述放進 Prompt 看似簡單;接上十個 Server 後,這個做法會同時造成 token 成本、同名衝突與錯誤選擇。更危險的是,模型可能看見一個目前不可用、需要額外核准,或只適用於另一個租戶的工具。
因此,MCP Agent 要有一個「能力目錄層」,至少處理四件事:
server_name.tool_name 或等價的 namespace 保留來源。order.get_order 和 legacy_order.get_order 是兩個不同的能力,Trace 也必須保存完整路由。listChanged,收到通知後應讓目錄失效並重新取得,而不是繼續使用舊 Schema。連線失敗時也要隔離該 Server,不能讓一個壞掉的來源污染整個 Agent。這也是目前 Agent 實作開始出現「延後載入」與「工具搜尋」的原因:模型先看到少量能力或搜尋介面,需要時才載入完整定義。OpenAI Agents SDK 的 MCP 文件把多 Server 管理、工具篩選、分頁、快取與追蹤列成獨立設定;它的實作選擇可以當作工程參考,但核心原則不依賴特定框架:可見工具集合是 Runtime 的輸出,不是 Server 清單的原樣複製。
每次執行至少應在 Trace 留下:Server 身分、連線狀態、協商版本、可見工具集合、篩選原因與目錄版本。如此一來,日後才能重建「模型當時為什麼有這個選項」,而不是只看到最後一個工具呼叫。

MCP 的 tools/call 回應可以是文字內容,也可以在宣告 outputSchema 時回傳 structuredContent。這讓 Client 有機會驗證資料形狀,但形狀正確仍然不等於業務動作完成。
以退款為例,MCP Server 可能回傳:
{
"isError": false,
"structuredContent": {
"status": "accepted",
"refund_id": "rf_789",
"provider_receipt": null
}
}
isError: false 只表示工具沒有把這次執行標成錯誤;status: accepted 可能只是付款服務接受非同步請求。若應用程式需要「退款已完成」,就不能在這一步直接把訂單寫成 refunded,而要等待可查證的付款收據或重新查詢最終狀態。
因此,MCP Client 與業務 Runtime 之間要有自己的執行契約:
server: payment
tool: refund_order
mode: write
before_call:
- 重新讀取訂單目前狀態
- 確認訂單屬於目前使用者與任務
- 確認金額使用訂單原幣,且仍有可退餘額
commit_requires:
- refund_id
- provider_receipt
- observed_at
if_timeout: unknown

這裡的 rejected、failed、committed、unknown 是應用層的狀態,不是 MCP 標準列舉:
| 狀態 | Runtime 可以知道什麼 | 下一步 |
|---|---|---|
rejected |
呼叫前的形狀、權限或最新前置條件不成立,沒有送出副作用 | 修正、補問或停止 |
failed |
Server 明確回報執行失敗,且可判斷沒有完成 | 依錯誤處理,不盲目重試 |
committed |
有領域需要的識別碼、收據與觀察時間 | 更新 State,讓後續 Loop 使用 |
unknown |
請求可能已送出,但目前沒有足夠證據 | 先查證或等待,不宣告成功、不直接重播 |
這個閘門把 MCP 的互通契約和系統的完成定義接起來:MCP 負責把結果可靠地傳回來,Runtime 負責決定這份結果是否足以改變世界的描述。

把 MCP 簡化成「遠端工具呼叫」會漏掉 Agent 實際會接觸的另外兩種能力。規格將它們分開,是因為控制權與風險不同:
| MCP 能力 | 誰決定何時使用 | 進入 Agent 後的處理 |
|---|---|---|
| Tools | 模型提出使用意圖,Client/Host 執行 | 進入工具篩選、權限、核准、呼叫與結果提交流程 |
| Resources | 應用程式或 Host 決定要不要放入 Context | 先依 URI、租戶與新鮮度讀取;不能把 Server 的所有資源自動倒進 Prompt。見 Resources 規格。 |
| Prompts | 通常由使用者或 Host 選擇模板 | 視為外部指令來源,必須檢查信任、版本與可見範圍。見 Prompts 規格。 |
這三條路徑在 Trace 中也不應混成同一種事件。一次 Resource 讀取,可能只是補充證據;一次 Prompt 取得,可能改變模型的指令上下文;一次 Tool 呼叫,則可能產生外部副作用。把來源與控制者記清楚,之後才能在事故中分辨「模型做了錯誤決策」和「Host 把不該看見的資料放進 Context」。
有些 MCP Server 還會透過 Sampling 請 Client 代為呼叫模型,形成 Server 內的子迴圈。這不會把模型存取權交給 Server:Client 仍應掌握模型、工具權限與人工審核。對 Agent Harness 而言,它只是另一種需要設定深度、預算與停止條件的巢狀執行。
MCP Agent 的測試不該只驗證「JSON 能不能通過」或「呼叫次數是不是一次」。真正要測的是:在能力會變動、Server 會失敗、結果可能延後的環境裡,State 是否仍然正確。
| 測試情境 | 應觀察的 Runtime 行為 |
|---|---|
initialize 版本或 capability 不相容 |
在協商階段停止,不呼叫未支援的方法;Trace 記錄原因。 |
Server 發出 listChanged |
讓工具快取失效,重新取得目錄,再決定是否重建模型可見集合。 |
| 多個 Server 有同名工具 | 以完整 namespace 路由;Trace 能指出實際 Server,不接受模糊名稱。 |
| Schema 合法但訂單已退款 | 在副作用前拒絕,重新讀取 State 或要求補充資訊。 |
isError: false 但沒有付款收據 |
不提交 committed;標成待查或 unknown。 |
| 呼叫逾時,結果不明 | 保存 request id 與 checkpoint,先做查證,不直接重播寫入動作。 |
| Prompt 或工具 annotations 要求提高權限 | 視為不可信提示,交給政策與核准流程,不讓文字描述自行放行。 |
這些測試可以從研究工作中找到共同方向。 ToolSandbox 用「必須發生的 milestone」與「絕不能發生的 minefield」評估有狀態工具;τ-bench 把政策與動態 API 放進多輪任務,最後比對資料庫狀態;較新的 E-Bench 則以狀態差異做可重現的寫入型任務評估。它們共同指向同一個工程結論:呼叫軌跡只能說明 Agent 做過什麼,最終狀態與禁止事件才說明它做對了什麼。
如果要先做一個最小的 MCP Agent Baseline,我會要求它至少保存四份資料:
server identity + protocol version + capabilities:當時連到誰、談成什麼。visible catalog:模型看到哪些工具、為什麼被篩進來,以及使用哪一版 Schema。call record:模型提出的意圖、實際路由、核准結果與原始回應。commit evidence:State 為什麼前進;若證據不足,明確留下 unknown 與後續查證工作。做到這裡,MCP 才不只是「讓 Agent 呼叫外部 API」的接線工作,而是讓 Agent 可以在動態環境裡取得能力,同時保留可重建、可拒絕、可恢復的執行邊界。
下一個要接上的問題會是:即使工具已被發現、路由也正確,誰有權使用它?在什麼條件下需要人工核准? 那會把 Identity、Permission、Policy 與 Approval 接到這條 MCP 執行鏈上;unknown 之後的查證、冪等與補償,則留給後續的可靠執行主題。
挑一個你正在使用的 MCP Server,試著回答三件事:Agent 看見的是哪一版能力目錄?一次寫入型呼叫的完成證據是什麼?如果 Server 在途中更新工具定義或回傳未知結果,Runtime 會在哪一個閘門停下來?
