Plugin 已經被 client 找到,其中一個 MCP server 卻在 handshake 時失敗。結果整包 skills 都從 Agent 的能力清單消失。這種處理看似保守,其實是把故障邊界畫錯了。
Agent Plugins 1.0 把 plugin.json、skills/、mcp.json 和 client 自訂內容放進同一個 package。封裝讓團隊能用一致的目錄整理交付內容,安裝與分發則不在規格範圍內。Package 裡的東西放在一起,不代表它們必須一起成功,也不代表它們共享同一種復原方式。
維運時需要追的是一個錯誤究竟會停在整包、單一元件,還是執行階段。
第一層是 package。Root manifest 無法解析、必要欄位無效,client 連這個 plugin 是什麼都無法確認,拒絕整包很合理。這類錯誤發生在 discovery 與 validation 階段,標準元件還不該進入載入流程。
第二層是 component。v1 的標準元件是 skills 與 MCP servers。單一 skill 格式錯誤,或某個 MCP entry 無效時,失敗範圍應盡量留在該元件;其他可用元件仍可繼續載入。某種元件目錄不存在,並不等於 plugin 無效。不是每個 plugin 都必須同時帶 skill 和 MCP server。
第三層是 runtime。MCP subprocess 啟動後怎麼隔離、憑證怎麼給、執行期間能碰哪些檔案,以及 client-specific hook 如何復原,都不會因為 package 符合規格就自動得到答案。這部分要由 client 與團隊自己的執行環境負責。
如果監控只留下一行「plugin load failed」,這三層就又被揉回同一團了。
在寫 retry 之前,我會先替 plugin 做這張矩陣:
| 故障 | 預期載入結果 | 還能相信的能力 | 至少要留下的證據 | 復原責任 |
|---|---|---|---|---|
plugin.json 致命驗證錯誤 |
拒絕整包 plugin | 無 | manifest 路徑、驗證錯誤、規格版本 | package 維護者 |
| 單一 skill 格式無效 | 略過該 skill,繼續處理其他元件 | 已成功載入的 skills/servers | skill 識別、解析錯誤、實際載入清單 | skill 維護者 |
| 單一 MCP entry 無效或 transport 不支援 | 略過該 entry,繼續處理其他 servers/components | 不依賴該 server 的能力 | entry 識別、失敗階段、其他 server 狀態 | MCP 設定維護者 |
| MCP 啟動、驗證或 handshake 失敗 | 該 server 不可用,其他元件仍可載入 | 需由測試確認降級後的任務路徑 | stderr/連線錯誤、handshake 階段、降級標記 | runtime 維護者 |
| client extension hook 失敗 | 依該 client 的規則處理 | portable core 不能代為承諾 | client 名稱與版本、extension log、fallback 結果 | client 整合維護者 |
「其他元件仍可載入」很常被誤讀。它只描述 loading 結果,不代表原本的使用者任務做得完。假如 skill 的最後一步必須呼叫壞掉的 preview server,介面上看得到 skill,不等於它能完整執行。
所以矩陣要同時記錄 loaded components 與 degraded capabilities。只記前者,會把半套能力誤報成健康。
假設團隊有一個 ui-prototype-review plugin:
ui-prototype-review/
├── plugin.json
├── skills/
│ ├── compare-genui/
│ │ └── SKILL.md
│ └── check-accessibility/
│ └── SKILL.md
├── mcp.json
└── com.example.ide/
└── preview.json
compare-genui 保存比較 UI pattern 的流程,也可以把 生成式 UI 資源合集 列為公開資料的查找起點。它是一份人為整理的多語系資源目錄,不是 Agent Plugin、MCP server,也不提供執行期安全保證。
mcp.json 另外宣告一個可選的預覽或截圖工具;com.example.ide/ 則是假想的 IDE 專用 preview hook。這三者服務同一個 review 工作流,故障語意卻不相同。
公開資源暫時連不上,skill 仍可能靠既有步驟與團隊內部資料完成比較。MCP preview server handshake 失敗時,文字檢查也許能繼續,畫面截圖則必須標成 unavailable。IDE hook 壞掉時,另一個 client 可能根本不會讀取那個 namespace。
「Plugin 還在」只能當載入狀態,不能當任務狀態。
Agent Plugins 規格要求 plugin 提供的相對路徑不能逃出 plugin root。這可以阻止 manifest 用 ../../ 指向 package 外的檔案,也是必要的解析邊界。
這條邊界不等於 subprocess sandbox。MCP server 啟動後收到的 runtime input、處理程序可讀取的檔案、環境變數裡的憑證與網路權限,仍受作業系統、容器、client 與部署設定控制。通過 path containment 測試,只能證明 package 內的檔案解析沒有越界;它證明不了執行中的處理程序被隔離。
如果安全檢查表把兩者合成一個「sandbox: pass」,那個勾勾很容易讓人過度信任。
這類 plugin 不需要一開始就建龐大的測試平台。先固定四個 case,就能抓出不少邊界錯置。
刪掉必要欄位,或放入 client 無法接受的規格版本。測試應確認整包被拒絕,任何標準元件都沒有進入可用清單,而且錯誤明確指向 manifest validation。
讓 check-accessibility/SKILL.md 格式無效,保留 compare-genui 正常。除了看到錯誤,還要確認第一個 skill 仍可被發現,壞掉的 skill 沒有悄悄混進能力清單。
使用錯誤的驗證資料或測試端點,刻意讓 preview server 在連線或 handshake 階段失敗。其他元件應繼續載入;進入 review 任務時,介面與 log 都要清楚標示「預覽能力降級」,不能等到最後一步才丟出模糊的 task failed。
在支援該 namespace 的 client 裡觸發 hook 錯誤,再到不讀取該 extension 的 client 重跑一次。兩邊的結果未必一致,這正是測試目的。Portable core 沒有替 client-specific 行為定義統一 fallback,團隊不能自行腦補。
每次 fault injection 建議至少保存以下欄位:
fault: mcp-preview-handshake-failed
expected_scope: component
observed_loaded_components:
- skill:compare-genui
- skill:check-accessibility
degraded_capabilities:
- visual-preview
reported_error: "authentication failed before handshake completed"
recovery_owner: devex-runtime
這不是 Agent Plugins 官方 schema,也不該塞回 plugin.json。它是團隊的測試紀錄,用來核對原本只應影響單一元件的故障,實際上有沒有拖垮整包,以及系統降級後還有哪些能力可信。
同一份紀錄也能避免 retry 掩蓋問題。若 client 重試三次後成功,最終狀態可以是 healthy,但前兩次失敗仍該保留。否則 intermittent failure 只會在使用者抱怨時才存在。
Plugin 的成熟度不該用「支援幾個 client」衡量。更有用的標準是:任一元件故障時,團隊能指出失敗停在哪一層、哪些能力仍然有效、哪一筆證據證明了這件事。
Agent Plugins 1.0 已經替 package 與標準元件畫出一部分邊界。Runtime 隔離、client extension、權限與任務降級仍是團隊自己的工作。把這些缺口寫進 fault matrix,並真的破壞元件跑一次,才算知道這包 plugin 壞起來會長什麼樣子。