MCPAgentArchitecture

過去寫 API 的好習慣,可能正在把我們的 Agent 搞糊塗

把後臺介面一對一翻譯成 MCP 工具,Agent 拿到的往往是一堵沒有畫面的按鈕牆。從畫面約束、有狀態的業務 Action 到 MCP 2026-07 新規範,探討面向 Agent 的工具應該怎樣設計。

16 分鐘讀完

也可以讀English简体中文

我們可能從一開始就把 MCP 接錯了。

當行業真正開始把 Agent 接進核心業務時,一個反直覺的現象越來越明顯:困住 Agent 的,常常不在模型智商,而在過去十年給前端寫介面留下的肌肉記憶。

工程團隊太擅長把每一步流程寫死,卻很少給 AI 留出一個表達“我想完成什麼”的入口。把後臺介面一對一翻譯成 MCP 工具,Agent 最後拿到的,往往是一堵沒有畫面的按鈕牆。

MCP 剛出來時,大家的第一反應都順理成章:家裡現成有幾百個給網頁和 App 用的後臺介面,乾脆寫個指令碼,一對一翻譯成幾百個 MCP 工具,全部掛上去。以前給前端供介面,講究拆得細、職責單一,兩百個介面,就整整齊齊擺出兩百個開關。

但在訂單、退款、審批這類業務裡,大模型更需要的,是幾個基於場景觸發的業務入口。

當本地 Agent 真正開始接管工作流時,這套沿用多年的“好習慣”暴露了問題:沒接後臺之前,模型的對話和推理都正常;掛上兩百個細粒度工具後,連查單、改狀態這樣的基礎操作都開始頻繁出錯、反覆重試。

模型並沒有突然退化。我們只是忘了:大模型不是前端。

為什麼原本在 App 裡執行順暢的介面,原樣交給 AI 就出了問題?因為中間少了一層看不見的約束:畫面。

畫面一直在替我們做狀態過濾

拿最常見的售後來說。順著頁面邏輯,後端會自然拆出:

  • 未發貨僅退款
  • 已收貨退貨退款
  • 申請換貨
  • 補退運費
  • 修改收貨地址
  • 查詢物流

真實使用者在手機上很少點錯,因為當訂單處於“已發貨”狀態時,App 前端早就把“未發貨僅退款”的按鈕隱藏或置灰了。

前端畫面,在無形中承擔了狀態過濾與防呆校驗的職責。

一旦把這七八個介面直接翻譯成 MCP 工具,藏按鈕的畫面就消失了。命名近似、職責相鄰的介面同時擺在上下文裡,模型要在沒有任何狀態引導的前提下,自己判斷先調哪個、後調哪個。

大模型本質上是機率模型。以前前端替業務過濾掉了非法狀態,現在這些隱性規則全部交給模型的推理去兜底。上下文乾淨時,它多半能選對,讓人以為系統已經跑通;一旦對話變長、噪聲增多,它就可能拿著一筆在途訂單,去呼叫“未發貨僅退款”。

演示專案裡,這是一次偶發的幻覺;真實業務裡,這是一筆資損。

這不只是個人體感。一項針對 116 個官方 MCP Server 的實證研究(From REST to MCP)發現,88.6% 的服務完全或部分依賴 REST API,92% 的服務把工具實現為對原有介面的簡單包裝。另一項 LongFuncEval 實驗顯示,隨著可用工具數量增加,長上下文模型的函式呼叫表現會下降,在部分模型上降幅高達 85%(觀察到的區間為 7% 到 85%)。

問題當然不在 REST 本身,細粒度介面在微服務架構裡仍有不可替代的價值。錯位發生在抽象層:細粒度介面適合交給前端,由確定性的頁面程式碼組合出完整流程;而在多步驟的核心業務裡,面向 Agent 的工具更應該直接表達當前可用的業務能力、狀態約束和下一步動作。

這一點在業界正逐漸成為共識。Anthropic 的工程部落格建議把常被連續呼叫的工具合併成一個,讓單個工具在內部完成多次 API 呼叫;Block 基於 60 多個 MCP Server 的實踐,也主張從工作流自頂向下設計,不要直接暴露細粒度介面。

但“合併工具”只回答了一半問題。對於下單、退款、審批這類有狀態、有副作用的流程,合併之後的工具應該長什麼樣?流程中途缺資訊怎麼辦?誰來攔住扣款?超時之後怎麼恢復?這是本文想繼續探討的部分。


第一層:先別把整張介面表擺出來

工程界已經開始為“把所有介面平鋪給 Agent”這件事做減法。

OpenAI 的 Tool Search 讓模型先拿到輕量的工具目錄,需要時再把相關工具的完整定義動態載入進上下文,並建議把延遲載入的函式按 Namespace 或 MCP Server 分組。

Grok Bot 和 Muse 的公開方案則更側重執行環境。Grok Bot 執行在持久化的雲端計算機上,通過基於 MCP 的聯結器訪問外部服務,在本地命令、團隊賬號、支付或身份驗證等環節交還給使用者批准或接管;Muse 執行在獨立的雲端安全環境中,憑據存放在安全儲存裡、模型不可見,支付使用一次性卡號,購買前會先詢問使用者。

這些方案切入點各不相同,但指向同一個共識:Agent 不應該在一開始就看到整張後臺介面表。

不過,Tool Search 主要解決工具的發現與載入,聯結器與受控執行時主要解決操作的執行邊界。對於下單、退款、審批、付款這類有狀態、有副作用的核心業務,還可以再往前走一步:重新定義 Agent 看到的業務最小單元。


第二層:把 API 串燒變成有狀態的 Action

給前端寫介面,和給 AI 設計工具,面對的是兩種不同的契約。前者把動作拆碎,讓頁面去編排流程;後者需要把散落在頁面裡的狀態機收回來,圍繞一次完整的業務意圖,封裝成可以推進、暫停、確認和恢復的業務動作(Action)。

以“下訂單”為例。在 REST 包裝模式下,Agent 需要自己在上下文裡串起整條鏈路:

查詢商品 → 選擇商品 → 查詢地址 → 選擇地址 → 查詢庫存 → 計算運費 → 查詢優惠券 → 選擇支付方式 → 建立訂單 → 建立支付 → 提交支付 → 查詢訂單

這條鏈路很脆弱,任何一環的引數偏差或狀態不一致,都會讓整條鏈路中斷。

如果把它收斂為一個有狀態的 CheckoutAction,模型啟動動作後,拿到的是一個自包含的業務上下文,其中包括整個流程需要從使用者那裡獲取的資訊清單:

{
  "action_id": "checkout-123",
  "entity": "Order",
  "node": "ITEMS",
  "items": [
    { "id": "item-1", "name": "商品 A", "quantity": 1 }
  ],
  "addresses": [
    { "id": "address-1", "label": "公司" },
    { "id": "address-2", "label": "家" }
  ],
  "payment_methods": [
    { "type": "balance", "label": "餘額" },
    { "type": "card", "label": "銀行卡" }
  ],
  "inputs": [
    { "field": "items[].quantity", "node": "ITEMS", "required": true },
    { "field": "address_id", "node": "ADDRESS", "required": true },
    { "field": "coupon_id", "node": "COUPON", "required": false, "depends_on": ["items", "address_id"] },
    { "field": "payment_type", "node": "PAYMENT", "required": true }
  ],
  "allowed_patch": ["items[].quantity", "address_id", "coupon_id", "payment_type"],
  "requires_confirmation": false,
  "next_actions": [
    { "tool": "advance_action", "description": "補齊或修改 allowed_patch 中的下單資訊" }
  ],
  "version": 3
}

這裡有一條貫穿全文的約定:每一個響應都必須帶上 next_actions,無論是搜尋、啟動、推進、確認、驗證還是恢復。它明確告訴模型下一步可以呼叫哪些工具、各自是做什麼的。next_actions 為空,表示流程已經結束;不在列表裡的呼叫,服務端直接拒絕。模型不需要去工具列表裡猜下一步,只需要在服務端給出的選項裡做選擇。

inputs 描述的是整條工作流的上下游:每個節點需要什麼資訊、哪些必填、哪些依賴上游的計算結果。Agent 不需要知道背後有多少個微服務,也不需要像網頁表單那樣一頁問一個欄位。使用者說一句“買兩件,寄到公司,用餘額付”,模型就能一次抽出所有欄位,一次提交:

{
  "action_id": "checkout-123",
  "version": 3,
  "patch": {
    "items": [{ "id": "item-1", "quantity": 2 }],
    "address_id": "address-1",
    "payment_type": "balance"
  }
}

服務端收到 Patch 後,按節點順序依次處理:先改數量、檢查庫存,再校驗地址、計算運費,然後根據新的金額匹配可用優惠券,最後校驗支付方式。每一步都由確定性程式碼完成,能推進多遠就推進多遠:

{
  "action_id": "checkout-123",
  "node": "CONFIRM_ORDER",
  "applied": ["items", "address_id", "payment_type"],
  "missing": [],
  "available_coupons": [
    { "id": "coupon-1", "label": "滿 300 減 30" }
  ],
  "requires_confirmation": true,
  "summary": {
    "amount": 398,
    "address": "公司",
    "payment_type": "balance",
    "coupon": null
  },
  "next_actions": [
    { "tool": "advance_action", "description": "使用優惠券或修改下單資訊" },
    { "tool": "confirm_action", "description": "提交訂單並扣款", "requires_user_approval": true }
  ],
  "version": 4
}

一次呼叫,就從起點走到了真正產生資金副作用的邊界。available_coupons 是隻有算出金額之後才能知道的下游資訊,Agent 可以在確認前順帶問使用者要不要用;如果使用者要用,再提交一次 coupon_id,服務端重新計算後依然停在確認節點。

如果某個欄位校驗失敗,比如庫存不足,服務端就停在出錯的節點:applied 列出已經生效的欄位,validation_errors 說明原因,下游欄位保留為待校驗,等上游修正後再重新計算。此時 next_actions 裡只有 advance_action,confirm_action 不會出現。

這裡有兩條邊界不能破:

  • 依賴上游的欄位,選項由服務端給出。 運費方式、可用優惠券這類資訊依賴商品、數量和地址,模型可以提前提交意向,但最終是否有效,以服務端計算結果為準。
  • 無論一次提交多少欄位,都不會越過確認節點。 requires_confirmation 是硬邊界,不會因為資訊齊全就被自動跳過。

模型此時還沒有觸發扣款,它只是把訂單推進到了必須由人確認的節點。使用者在畫面上確認後,Agent 才呼叫 confirm_action,攜帶冪等鍵完成最終提交:

{
  "action_id": "checkout-123",
  "approval": "approved",
  "idempotency_key": "req-20261026-001"
}

提交之後,響應同樣告訴模型下一步該做什麼:

{
  "action_id": "checkout-123",
  "node": "PAYMENT_SUBMITTED",
  "external_id": "payment-123",
  "next_actions": [
    { "tool": "verify_action", "description": "核實支付是否真實到賬" }
  ]
}

關鍵在於:服務端知道整個工作流的上下游,所以能提前告訴模型總共需要從使用者那裡拿到什麼;模型擅長從自然語言裡一次抽出多個欄位,所以沒有必要照搬網頁表單的分步節奏。逐節點推進是服務端內部的校驗順序,而不是模型與使用者的對話節奏。

Agent 不必記住流程順序,只需根據 inputs 一次問齊資訊、一次提交,再從每個響應的 next_actions 裡選擇下一步。整個互動週期可以抽象為:

Search → Start Action → Read Context → Submit Patch → Advance → Confirm → Verify → Recover

這裡有一個值得留意的邊界:next_actions 告訴模型可以呼叫哪些平臺原語,但底層微服務的 handler 不宜直接暴露給模型。下面這種結構適合留在服務端,作為內部流程定義:

{
  "handler": "CONFIRM_ORDER",
  "params": ["address", "payment_type", "coupon"]
}

面向模型的介面,則收斂為純粹的業務狀態:

{
  "node": "CONFIRM_ORDER",
  "missing": ["payment_type"],
  "allowed_patch": ["payment_type"],
  "next_actions": [
    { "tool": "advance_action", "description": "補齊支付方式" }
  ]
}

handler 是內部實現細節;node、missing、allowed_patch 和 next_actions 才是模型決策需要的路標。把 handler 透傳出去,只是給 Endpoint 換了個名字,模型依然在猜後端該怎麼調。


與 MCP 協議演進的關係

寫到這裡,熟悉 MCP 的讀者可能已經注意到:協議本身也在往這個方向走。

2026-07-28 版 MCP 規範是協議釋出以來最大的一次修訂,其中幾項改動和上面的設計直接相關:

  • 無狀態核心:協議層取消了會話,每個請求自帶版本、身份和能力宣告。需要跨請求保留的狀態,由工具自己生成顯式控制代碼來承載,這正是 action_id 和 version 的角色。
  • 多輪請求(Multi Round-Trip Requests):工具呼叫可以返回 input_required,告訴客戶端還需要哪些輸入,客戶端補齊後攜帶 inputResponses 重試。這可以看作 missing 與 patch 在協議層的通用版本。
  • Tasks 擴充套件:長時間執行的工具呼叫可以作為任務執行,客戶端通過 tasks/get 輪詢狀態、通過 tasks/update 推進。支付閘道器超時、外部審批等待這類場景,天然適合放在這裡。
  • MCP Apps:服務端可以向客戶端提供自己渲染的畫面。前文說“畫面替人做了狀態過濾”,在需要人確認的節點,這層畫面現在可以由業務方自己提供。

所以,有狀態的業務 Action 並不是要繞開協議另起爐灶,更合理的分工是:

  • 協議負責傳輸、暫停與恢復、向用戶索取輸入、承載長任務;
  • 業務 Action 負責業務語義:當前節點在這筆訂單的狀態下允許修改哪些欄位、哪一步會產生資金副作用、校驗失敗的原因是什麼。

協議能告訴模型“這裡還需要輸入”,但無法替業務回答“這筆已發貨的訂單,現在合法的操作有哪些”。後者依然需要業務系統用確定性程式碼給出答案。


重新設計 MCP 工具的四條原則

1. 從意圖找到流程的發起點

Agent 想完成“下單”,不應該在幾百個 Endpoint 裡猜哪個叫 create_order。平臺需要為每條業務流程提供一個觸發入口,讓 Agent 能從使用者的原話出發,找到這條流程從哪裡開始。

搜尋入口接收的是自然語言意圖,而不是 Agent 事先已經知道的能力名:

{
  "query": "把購物車裡那件商品買兩件,寄到公司"
}

平臺的能力目錄為每條流程維護一段觸發描述:什麼情況下該用它、適用於哪些業務物件、不適用於哪些情況。匹配可以用 Embedding 檢索,也可以結合關鍵詞和模型重排。命中之後,返回的不只是一個名字,還有這條流程的上下游概覽:

{
  "capability": "checkout",
  "trigger": "使用者想購買商品並完成支付",
  "prerequisites": ["已登入", "購物車或指定商品不為空"],
  "inputs": ["items[].quantity", "address_id", "coupon_id", "payment_type"],
  "side_effects": ["扣款", "鎖定庫存"],
  "requires_approval": true,
  "next_actions": [
    { "tool": "start_action", "args": { "capability": "checkout" }, "description": "啟動下單流程" }
  ]
}

Agent 由此知道:這條流程從哪裡發起、需要向用戶要哪些資訊、哪一步會產生副作用。後面的節點流轉,交給 start_action 啟動的狀態機。

這個結構和 Agent Skill 的漸進載入很像:平時只把一段簡短的描述放在模型視野裡,命中之後才展開完整內容。換句話說,是把 Skill 的發現邏輯內建進了工具設計。

區別在於展開之後由誰執行。Skill 展開的是寫給模型看的操作說明,由模型照著做;業務能力展開的是流程概覽,具體的節點推進、校驗和副作用由服務端的狀態機執行。模型只需要知道全貌,不需要親手走每一步。

2. 一次返回完整的業務上下文

讀取一筆訂單時,只返回主表的幾行欄位往往不夠。商品、支付、履約進度、售後資格、當前可執行的操作以及不可操作的原因,更適合在一次讀取中交代清楚。

這不是把整張資料庫表塞給模型,而是把單次業務判斷所需的關係與約束,組裝成一個內聚的檢視:

{
  "entity": "Order",
  "status": "SHIPPED",
  "refund": { "eligible": false, "reason": "order_has_shipped" },
  "requires_approval": false,
  "next_actions": [
    { "tool": "start_action", "args": { "capability": "return_refund" }, "description": "申請退貨退款" },
    { "tool": "start_action", "args": { "capability": "exchange" }, "description": "申請換貨" }
  ]
}

注意這裡的 next_actions 直接指向另外兩條流程的啟動入口:讀取一筆訂單的結果,本身就告訴了模型可以從這裡進入哪些業務流程。業務系統內部已經有明確的規則,就應該把確定性的判斷結果直接交給模型,而不是讓它自己關聯訂單表、支付表和履約表,再靠機率推斷能否退款。

3. 圍繞意圖收斂入口

無論是退錢、退貨還是改資料,圍繞同一個業務物件,對外可以儘量保留一個表達意圖的入口。Agent 只需要說清楚:“為這筆訂單申請售後。”

訂單是否符合條件、走僅退款還是退貨退款、是否需要憑證、是否觸發人工審批、後面編排哪些微服務,都由底層的 RefundAction 狀態機通過確定性程式碼分發。

4. 把確認、驗證和恢復放進同一個動作生命週期

生產環境裡風險最高的操作,集中在扣款、退款、刪除資料、變更許可權、提交訂單、外發檔案。這類操作不能止步於“請求已傳送”,需要返回可驗證的閉環狀態:

{
  "status": "submitted",
  "external_id": "payment-123",
  "verification": { "status": "paid", "amount": 199 },
  "next_actions": []
}

遇到閘道器超時這類未決狀態,也不該讓模型盲目重試,而應給出明確的恢復指引:

{
  "status": "unknown",
  "recoverable": true,
  "action_id": "checkout-123",
  "next_actions": [
    { "tool": "verify_action", "description": "先向支付閘道器核實結果,不要重新提交" },
    { "tool": "recover_action", "description": "核實確認未支付後,按原冪等鍵重試" }
  ]
}

一個面向生產環境的業務 Action,通常至少要維護這些欄位:action_id、current_node、version、inputs、missing、allowed_patch、applied、next_actions、requires_confirmation、verification、recoverable_errors。它本質上是一個輕量級狀態機:業務系統掌握狀態流轉的控制權,Agent 負責當前節點的資訊補全與互動決策。


給 Agent 換一副骨架

底層按這套思路重構後,平臺向 Agent 暴露的工具集可以收斂為少數穩定的原語:

  • search_capabilities:定位業務能力入口
  • start_action:初始化業務動作例項
  • get_context:讀取業務實體檢視,或在中途恢復會話時重新拉取當前節點狀態與約束
  • advance_action:提交 Patch,推動節點流轉
  • confirm_action:攔住產生副作用的審批關口
  • verify_action:核實外部系統是否真實完成
  • recover_action:處理超時、懸掛與冪等重試

後臺依然可以保留幾百個 REST、RPC 和微服務介面,只是不再直接出現在模型的決策視野裡。模型看到的是業務動作,水下執行的是確定性的流程程式碼。

這裡有一個繞不開的質疑:advance_action(action_id, patch) 本身也是一個通用介面,模型要到執行時讀 allowed_patch 才知道能填什麼,這和把兩百個工具塞進一個萬能的 execute() 有什麼區別?

區別在於約束從哪裡來。execute() 把引數拼裝和流程編排都留給模型,介面本身不提供“現在能做什麼”的任何資訊;advance_action 的每一步都由服務端返回當前節點、缺失欄位、允許修改的範圍和下一步可呼叫的動作,非法的 Patch 會被確定性程式碼拒絕,並附帶原因。

代價也確實存在:工具定義本身失去了靜態的型別化 Schema,從工具列表裡看不出每個 Action 需要哪些欄位。一個折中做法是讓 get_context 同時返回當前節點 Patch 的 JSON Schema,把型別約束從工具定義挪到執行時上下文裡。

真正需要壓縮的,是模型在每一步需要理解的決策空間。

回看近期的探索,幾條脈絡正在交匯:

  • Tool Search 讓工具定義按需載入;
  • Connector 與受控執行環境 讓外部操作在安全邊界內執行;
  • MCP 協議本身開始提供多輪輸入、長任務和服務端畫面;
  • 有狀態的業務 Action 把交易、審批變成可推進、可暫停的狀態機。

這幾層疊加起來,或許更接近一套生產級的 Agent 接入架構。

如果只看“Agent 怎樣從一句話走到一筆完成的訂單”,這幾層其實是一條遞進的鏈路,每一層回答一個問題:

  • 找到工具:Tool Search 讓模型在大量工具裡按需發現、按需載入;
  • 找到流程,看清上下游:觸發入口讓模型從使用者意圖出發,定位流程的發起點,並提前知道需要哪些資訊、哪一步有副作用;
  • 把流程走完:狀態機負責節點推進、校驗、確認、驗證和恢復。

第一層是工具層面的檢索,第三層是業務層面的執行。中間這一層最容易被忽略,它決定了模型拿到工具之後,是在猜流程,還是在看著流程圖做事。

順著這條線,還有一個我沒有完全想清楚、但值得討論的問題:流程知識應該放在哪一側?

一種做法是放在模型側,寫成 Skill:用自然語言描述“下單時先確認地址、再選支付方式”,由模型照著呼叫工具。另一種是放在服務端,做成業務能力:流程定義和狀態機都在平臺內部,模型只讀概覽、提交資訊。

兩者各有適用的地方。跨多個系統、沒有單一業務方負責、步驟以讀取和整理為主的流程,寫成 Skill 更靈活,改起來也快。單一業務系統內部、有資金或資料副作用、規則需要強制執行的流程,放在服務端更穩妥,因為 Skill 裡的規則只是建議,狀態機裡的規則是約束。

現實中可能兩者並存:Skill 負責跨系統的編排和對話策略,業務能力負責每個系統內部不能出錯的那一段。兩者的邊界劃在哪裡,或許會是 MCP 走入核心業務之後,工程團隊需要反覆權衡的問題。

當模型能力持續提升,介面數量不再是壁壘。真正的工程壁壘,在於能否把複雜業務抽象成少量穩定的動作原語,同時在底層兜住狀態、許可權、審批、驗證和異常恢復。

不妨用這個問題重新審視現有的程式碼庫:如果把所有依賴前端畫面遮擋才不會出錯的碎介面收回平臺內部,真正需要暴露給 Agent 的業務開關,還剩幾個?

也許最後留在模型視野裡的,不再是兩百個 Endpoint,而是七個清晰的階段:

查清能力、發起動作、補齊資訊、推進節點、確認提交、驗證結果、恢復異常。

回頭看,這個方向其實有點回到了 GraphQL 的思路:不再圍繞一個個 Endpoint 組織能力,而是圍繞業務實體和它們之間的關聯建模,讓呼叫方——不管是前端還是 LLM——按自己的意圖,在服務端劃定的邊界裡自由組合。不同的是,GraphQL 主要回答“要哪些資料”,而 Agent 還需要服務端告訴它“現在能做什麼、下一步做什麼”。實體檢視之上,還要疊一層狀態與動作的約束。

少給 Agent 塞一點碎介面,讓它直接看到業務本身。這也許是 MCP 走入深水區之後,值得繼續探索的方向。


參考資料

  1. OpenAI, Tool search: https://developers.openai.com/api/docs/guides/tools-tool-search
  2. Anthropic, Writing effective tools for agents — with agents: https://www.anthropic.com/engineering/writing-tools-for-agents
  3. Block, Block's Playbook for Designing MCP Servers: https://engineering.block.xyz/blog/blocks-playbook-for-designing-mcp-servers
  4. Model Context Protocol, The 2026-07-28 Specification: https://blog.modelcontextprotocol.io/posts/2026-07-28/
  5. xAI, Grok Bot: Computer and Apps: https://docs.x.ai/grok-bot/computer-and-apps
  6. Meta, Introducing Muse: https://about.fb.com/news/2026/09/introducing-muse-personal-ai-agent/
  7. Mastouri et al., From REST to MCP: An Empirical Study of API Wrapping and Automated Server Generation for LLM Agents: https://arxiv.org/abs/2507.16044
  8. Kate et al., LongFuncEval: Measuring the Effectiveness of Long Context Models for Function Calling: https://arxiv.org/abs/2505.10570

(完)