MCP、外掛與可觀測性:讓 agent 接上真實世界,還看得見它在幹嘛(AI Agent 架構全解析 7/8)
前六篇,我們把一隻 agent 從單一迴圈慢慢養大:先學會核心迴圈,再會拆解複雜任務,接著有了記憶與崩潰重開的韌性,能長時間非同步地跑,最後還能分裂成一群代理分工協作。但這隻 agent 一直有兩個沒被戳破的天花板:第一,它只能碰到自己 harness 出廠時內建、而且每一把都得工程師手寫的工具;第二,它跑了老半天,其實沒有人真的知道它做得好不好。這一篇就把這兩塊補上——agent 怎麼把手伸出 harness、接上真實世界,又怎麼裝上儀表板,讓你看得見、也量得出它到底有沒有把事情做對。
先講清楚問題:只會用內建工具的 agent,天花板在哪
回想第 2 篇的核心迴圈:模型每輪決定要不要呼叫工具,harness 把工具跑起來、把結果餵回去。這裡藏了一個限制——那份工具清單是寫死的,每一把內建工具都是人手刻的,input schema、執行邏輯、錯誤處理一項不少。但使用者要的服務多到寫不完:Jira、部署系統、內部知識庫、監控後台,各有各的 API、認證方式、甚至語言。你不可能為每個服務都手刻一把工具,更別說每加一個就要動 harness、重發一次版本。
MCP(Model Context Protocol)就是來補這道縫的開放合約:外部服務自己宣告「我有哪些工具、每把吃什麼參數」,agent 只管閉著眼睛呼叫,不需要知道是誰寫的、用什麼語言寫的。於是不必動 harness 一根寒毛,agent 就多了一整組 Jira 工具或部署工具。少了 MCP,它的能力就凍結在安裝當下那一套。
MCP:給 agent 的一個 USB-C 插座
我最喜歡的比喻是 USB-C。以前每個裝置一種插頭,換裝置就換線;USB-C 統一插座後,你插什麼進去,電腦自己認出對方是誰、能幹嘛。MCP 對 agent 就是這個角色:不管對面是本地資料庫、遠端 SaaS,還是同事寫的小服務,只要它講 MCP,就能插進 agent 的工具池被模型使用。
機制很乾淨。harness 連上一台 MCP server 後,先問它一句 tools/list:「你有哪些工具?」server 回一份規格清單,harness 把每一筆包裝成一個 runtime 的 Tool(就是第 2 篇那個 Tool),丟進跟內建工具同一個池子裡。從此模型看到 MCP 工具,跟看到內建工具長得一模一樣,迴圈的 dispatch 邏輯一個字都不用改。
唯一要小心的是撞名:兩台 server 都把搜尋工具叫 search 怎麼辦?答案是命名空間,每把 MCP 工具都改名成 mcp__<server>__<tool> 的格式,永遠撞不到一起。下面就是「把探索到的工具包成一個 Tool」的最小骨架:
1 | def tool_name(server, tool): |
效果是:包裝完的 Tool 跟內建工具擠在同一個 registry 裡,模型完全分不出誰是誰,但它的 run() 一被呼叫,就會透過 transport(本地 stdio、遠端 http/sse/ws)打到真正的 server 上。這裡有個關鍵細節:readOnlyHint 這類 annotation 是 server 自己貼的標籤,會直接餵給第 3 篇的權限 gate——唯讀工具不囉嗦放行,會刪東西、會部署的則跳出來問你。權限判斷等於被下放到了 server 的自我宣告上。
plugins 與 channels:把外掛裝上去,再開一道對外的門
plugin 就像瀏覽器外掛。Chrome 本體只做核心的事,你裝一個外掛才一次把某功能的全套東西塞進來。agent 的 plugin 一樣,它把一台 MCP server、幾個 hook、幾個 skill 打包成一包,裝上去就一次補齊某個場景要的所有配件。多個 plugin 若都想定義同一台 server,就靠一套優先序疊加決定誰說了算:大致是 plugin < user < project < local,越靠近你手邊的設定,覆蓋力越強。
channel 則是反方向的東西。前面都是 agent 主動去呼叫 server,channel 反過來讓外面的訊息主動推進來,你可以把它想成給 agent 接上 Slack、LINE、Discord 或簡訊的入口:有人在 Slack 丟一句話,harness 把這段文字包上一個 <channel> 標籤,接在 agent 下一輪輸入的前面,模型就讀到了;連語音都走同一條路,先轉文字再包進來。
但這裡有個很實際的資安問題:channel 進來的訊息誰都能發,那句話不一定出自你本人,可能是垃圾訊息,甚至是想操縱 agent 的惡意指令。所以每一則訊息在變成一個真正的 turn 之前,都得先過一道 gate 安檢——它可以直接 drop(丟掉垃圾與不明來源),也可以 rewrite(把訊息裡的機密遮掉再放行)。丟掉的訊息模型根本不會讀到,這對垃圾輸入是最省成本的結局。
MCP 最常見的兩個坑。 第一,工具清單膨脹:每連一台 server 就往清單裡塞一批工具,連個五、六台,模型面對的清單又長又雜,不只吃 token,還會干擾它挑工具的判斷(呼應第 2 篇——工具太多,選擇品質反而下降)。緩解方式是截斷工具描述、延後載入用不到的 server。第二,被過度信任的 annotation:權限 gate 讀的是 server 自己貼的
readOnlyHint,萬一有台 server 把會刪資料的工具謊報成唯讀,就能繞過詢問直接執行。對來路不明的 server 別全盤採信,該用完整工具名設一條規則照樣把它 gate 住。
換個角度:agent 是一個會花錢、有副作用的黑盒子
工具接上去、對外的門也開了,agent 能做的事大到有點嚇人:它常常無人看管地跑,每一步都可能改檔案、發部署、寄信,每次模型呼叫又都在燒你的錢。而一次模型呼叫本質上是個黑盒子——沒有儀表,你連它做了什麼、某個工具失敗幾次、這個 session 花了多少錢、上禮拜改的 prompt 讓品質變好還變差,通通答不出來。
補這個洞要靠兩件分開的工作。observability 監看正式環境:每一步發一個 event、追蹤花費、留下能重建的紀錄。evaluation 回答另一個問題:這次改動讓品質變好還是變差。兩者都不做,退步就無聲上線、成本暴衝變驚喜、每份 bug 回報都無法重現。
observability:像 APM 一樣,把每一步都攤在陽光下
做過後端的你對這套其實很熟——它就是 agent 版的 APM 與分散式追蹤:在每個關鍵環節埋一個探針,把「發生了什麼、花了多久、花了多少」串成一條能回放的軌跡。
關鍵在於它怎麼埋才不會拖垮主流程。做法是「發射即忘」(fire-and-forget):迴圈每走一步就丟一個 event 給 logger,這個 emit 永遠不會阻塞、永遠不會拋例外。它先把 event 排進佇列,等某個 sink(Datadog、logger 之類的後端)接上再一次排空,接著採樣、洗過敏感資料,才扇出去。就算 telemetry 還沒就緒、或某個後端整個掛掉,主迴圈照樣一步都不會卡——觀測是旁觀者,不是迴圈裡多出來的一步。
兩個細節值得記住。一是 scrub:扇出前只保留白名單裡的欄位,所以程式碼、檔案路徑、prompt 這些不確定安不安全的東西,永遠不會流到後端去。二是成本追蹤:每次模型呼叫的 input/output token 數,按各模型單價累加成一個滾動的 USD 總額,即時顯示、退出時再印一次。這個數字就是你抓「成本漂移」的哨兵——哪天有人把便宜模型換成貴的、或迴圈失控狂燒 token,你當場就看得到。
evaluation:agent 為什麼特別難評,工程上怎麼補
observability 能告訴你「發生了什麼」,卻答不出最要命的那題:這答案到底好不好。
傳統程式好測,因為輸出確定:同樣的 input 該得同樣的 output,assert equals 一比就知道對錯。agent 不是這樣。它的輸出是自然語言、是一連串工具呼叫的軌跡,同一個問題跑兩次可能走不同的路、給不同的措辭,兩種都可能對,你沒辦法用一行 == 判生死。更陰險的是退步無聲:你改了一句 prompt,某類任務的成功率從 90% 掉到 75%,程式不報錯、telemetry 也不亮紅燈,它就這麼默默上線,直到使用者踩到。
工程上的解法是把它當成回歸測試加驗收。你維護一組固定、有代表性的 task 集,每個 task 配一個自己的評分準則(grader,可能是規則比對,也可能是拿另一個模型當裁判)。每出一個候選 build,就把整組 task 重播、逐一評分,算出一個 pass rate。骨架簡單到你會懷疑是不是漏了什麼:
1 | def run_eval(build, tasks): |
效果是:一個退步的 build 分數會掉下來,這就是你的發佈閘門。它跑在熱路徑之外、離線執行,所以一次悄悄的品質下滑,會在使用者遇到之前先被這組 task 集攔下來。沒有它,你每次改 prompt 都是在對正式環境賭運氣。下面這張表把兩件事的分工擺在一起:
| 面向 | observability(可觀測) | evaluation(評估) |
|---|---|---|
| 回答的問題 | 它做了什麼、花了多少、哪裡爆了 | 這次改動讓答案品質變好還是變差 |
| 執行位置 | 內聯在正式環境,每步即時發 event | 離線在熱路徑之外,對固定 task 集重播 |
| 抓得到 | 當機、工具失敗、成本暴衝 | 無聲的品質 regression |
| 抓不到 | 答案到底對不對、好不好 | 正式環境的即時故障與花費 |
沒 eval 的 agent,等於沒有品質底線。 你可以一直改 prompt、換模型、加工具,但只要沒有一組會評分的 task 集當閘門,就永遠不知道這次改動是進步還是退步。另外兩個常被忽略的坑:其一,log 不含 trace——每個 event 各自為政、沒有共同的 trace id 把它們串起來,你就無法把一次完整任務的來龍去脈拼回來,出事時只能瞎猜。其二,eval 與正式環境脫節——離線 task 集若漏掉真實用法,就會「測試全過、使用者照樣失敗」;務實做法是從 scrub 過的正式 trace 反過來播種 task,讓兩邊共用同一個資料分布。
下一篇:把所有零件組成一台會自己轉的機器
到這裡,這隻 agent 該有的都有了:核心迴圈、任務規劃、記憶與韌性、長時間非同步、多代理協作,加上這一篇的對外整合與可觀測。零件全到齊,但現在還是一盒散裝的樂高。
第 8 篇是整個系列的收束——迴圈工程。我們會退一步,看這些零件怎麼被組裝成一台能自我運轉的系統:迴圈怎麼收斂、什麼時候該停、各層之間如何咬合成一個整體,而不只是七層各跑各的。這也是全系列核心命題的最後一塊拼圖:模型負責推理與決策,harness 提供工具、狀態與限制;當這兩者真正咬合,你手上就不再是一個會回話的模型,而是能自己把事情做完的系統。下一篇見。















