主題: AI agents
MCP Tool Result:給決策,不要把 API 整包倒進 context
MCP tool 的價值不在把 REST JSON 原封不動搬給模型,而在交付可判斷的摘要、可驗證欄位與下一步查詢入口。
MCP server 最容易犯的錯,不是 transport 寫錯,也不是 schema 少一個 required,而是把既有 API 的 response 原封不動包進 tool result。
這看起來很忠實,實際上常常很懶。模型想判斷「哪三筆資料需要處理」,你卻回傳十萬字的 nested JSON;模型想確認一個狀態,卻得先猜哪個欄位才是最新版本。tool 能呼叫成功,不代表 agent 因此更可靠。
我會用一句話判斷:tool result 應該交付決策所需的 context,不是把後端資料庫的抽屜整個拉開。
動態迷因(展開/收合)
API response 不是 agent 的工作單位
傳統 API 常為人類前端或另一個程式服務而設計。它可能合理地回傳完整 object、內嵌關聯、presentation 欄位、所有 audit metadata,或給 UI 做下一頁用的資料。
但模型通常不是要「擁有整個 object」。它要做的是下一個判斷:
- 找出需要人工確認的三筆異常;
- 比較兩個設定的差異;
- 判斷能不能重試;
- 告訴使用者還缺什麼資訊。
所以別把 GET /orders/:id 直接改名成 get_order 就結案。先寫清楚 agent 的 job,再讓 tool 回傳剛好支持那個 job 的資料。最近的 Reddit 實作討論也點出同一件事:模型只要一個欄位時,整包 JSON 只是在污染後續 context。
好 result 先回答,再留下可追查的把手
我偏好把讀取型 tool 的 result 切成四層:
- 決策摘要:答案、計數、篩選條件與
observedAt。 - 少量證據:可辨識的 id、狀態、時間與最小必要欄位;不是把私密或無關欄位一併送出。
- 明確邊界:有沒有截斷、下一頁是否存在、結果是否過期或權限不足。
- 下一步:可用的 cursor、精確的 follow-up tool,或需要使用者確認的動作。
例如,agent 的任務是找出要先處理的 incidents。它需要的是排好序的少量候選、各自的理由、查詢時間與 nextCursor,不是 500 筆完整事件紀錄:
const result = {
summary: {
message: "找到 3 筆需要人工確認的 incident",
observedAt: "2026-08-17T11:00:00Z",
totalMatches: 27,
},
incidents: [
{ id: "inc_102", severity: "high", state: "open", updatedAt: "2026-08-17T10:42:00Z" },
{ id: "inc_081", severity: "high", state: "open", updatedAt: "2026-08-17T10:19:00Z" },
{ id: "inc_077", severity: "medium", state: "triage", updatedAt: "2026-08-17T09:58:00Z" },
],
nextCursor: "opaque-cursor",
};
return {
content: [{ type: "text", text: JSON.stringify(result) }],
structuredContent: result,
isError: false,
};
MCP Tools specification定義了 structuredContent 與可選的 outputSchema:有 schema 時,server 必須回傳符合它的 structured result,client 應驗證它。這很適合把「summary 一定有時間」、「每筆一定有 id 與 state」變成可檢查的契約,而不是留給 prompt 猜。
cursor 不是裝飾;它是拒絕資訊洪水的方法
資料多時,別只做一個 limit: 1000 然後祈禱模型看得懂。要讓 result 明說「這是前幾筆,還有更多」,並把 opaque cursor 帶回下一次查詢的 arguments。
MCP 的 pagination 文件明確把 cursor 用在列舉型操作,像 tools/list、resources/list 與 prompts/list;cursor 不該被 client 解析或改寫。這表示你的業務查詢 tool 也不會自動得到「萬用分頁」:要在自己的 input schema 設計 cursor/limit,並把 result 的截斷語意說清楚。
動態迷因(展開/收合)
實務上,query tool 可以有三個很平凡但很有用的參數:
{
query: "failed deploys since yesterday",
limit: 10,
cursor: "opaque-cursor-or-omitted"
}
limit 是上限,不是承諾;cursor 是 server 所發的能力票,不是 page number。若需要細節,讓 agent 用穩定 id 再呼叫 get_incident。這會多一次 tool call,但少了把不相干資料塞進每一輪 context 的成本。
error 也必須能讓 agent 下一步正確
讀取失敗時,最糟的 response 是 "Error"。模型無法知道該縮小查詢、等待、要求權限,還是停止。
MCP 區分 protocol error 與 tool execution error;後者應放在 result 裡並設 isError: true,讓模型看得到、能修正。官方規格也要求server 驗證輸入、實作 access control、rate limit 與 sanitize output,client 在交給 LLM 前驗證 result。
因此,error result 至少應保留:
- 可公開的原因碼,例如
RATE_LIMITED、SCOPE_REQUIRED、CURSOR_EXPIRED; - 是否可安全重試,以及建議等待多久;
- 可採取的安全下一步,而不是內部 stack trace;
- 不能自動執行的敏感操作,仍要由使用者明確確認。
這不是多寫一層文案。它是讓 agent 從失敗中恢復,且不把內部資料誤丟進 context 的最短路徑。
什麼時候才該把中間資料留在模型外?
小型、單一步驟 lookup 不必為了「context engineering」多建一個 execution sandbox。直接回傳小而完整的 result,比再加一層 orchestration 更可靠。
但任務需要大量 filter、aggregate、sort,或三個以上相依 tool call 時,應該在受控程式環境處理中間結果,只把最終、可解釋的結論交回模型。Anthropic 的 advanced tool use 說明給了一個很好的界線:大資料集的中間結果、重複查詢與平行操作適合交給程式;簡單 lookup 或模型本來就必須審閱每筆中間資料時,則不值得增加這層。
這不等於每個 MCP server 都要模仿某個 vendor 的 feature。原則更簡單:資料處理交給可驗證的程式;模型只看它需要推理與說明的部分。
發布前,我會用這五題檢查 result 契約
- agent 需要做的下一個判斷是什麼?result 有沒有直接支持它?
- result 能否被
outputSchema驗證,並在TextContent保留 serialized JSON 的 compatibility text? - 大量結果是否有明確
limit、截斷訊息與 opaque cursor? - 每筆 evidence 是否有穩定 id、時間與來源,而不是看起來很完整卻無法追查?
- 錯誤、權限與副作用是否讓 agent 知道該停止、重試或要求確認?
如果五題都答得出來,這個 tool 才是在替 agent 降低猜測。否則它只是把一個 API endpoint 搬進對話視窗,然後把資料量、權限和錯誤處理都交給模型賭運氣。