# 我為什麼自己建立 MCP 與 Plugin：從工具分散到真實驗收

作者：Ian
發布：2026-10-15
更新：2026-10-15
實作紀錄期間：2026-08-03 至 2026-10-14
正式網址：https://ianinfra.com/knowledge/building-mcp-plugins/

我開始建立 MCP，是因為一個很日常的問題：**要查工時時，我想不起來哪個 Agent 會做。** 有的工具只有 Claude Code 能用，Codex 沒有；Hermes 又有另一份操作方法。當三個 Agent 跨電腦使用，複製過去的 Skill 和 SOP 逐漸出現差異，修改一次並不代表每個入口都更新。

後來，需求延伸到個人知識庫與工作訊息：我希望在不同 AI 入口搜尋同一份資料，保留來源，也能限制哪些操作可以執行。這讓我同時處理 MCP 服務、Plugin 接入、OAuth、權限和驗收，而不只是寫出一個能回傳 JSON 的工具。

這篇整理我與 AI 開發工具協作的實作。需求、範圍與驗收取捨由我決定；程式修正、測試與排障由 AI 工具協作完成。數據是指定日期的歷史紀錄，不代表服務今天的即時狀態，也不是所有模型或外掛的通用表現。

## 起點：把能力與規則集中，減少每個入口各做一份

最初的能力中心把能力目錄、SOP、參數對照、核心執行與稽核放在共用服務。Agent 端保留薄的路由規則：先找到能力，再取得操作要求。改動核心行為時，盡量讓各入口共用同一份實作。

我對 MCP 和 Plugin 的分工，是從這個維護問題長出來的。

| 層級 | 我放進去的責任 | 原因 |
| --- | --- | --- |
| MCP 服務 | 查詢、原文讀取、受限寫入、來源範圍與權限檢查 | 不同 Agent 要取得一致的能力與結果 |
| Plugin／connector | 讓特定平台發現服務、完成授權與載入工具 | 各平台有自己的安裝與連線生命週期 |
| Skill／使用規則 | 何時呼叫、如何呈現來源、何時停下等使用者確認 | 讓操作流程符合實際工作需求 |
| Server 端強制檢查 | scope、允許的資料範圍、寫入預覽與執行約束 | 使用規則不能代替真正的授權檢查 |

如果 Plugin 只把一個 search 轉呼叫成另一個 search，沒有增加平台接入或使用規則，我不會為了多一層包裝而建立它。私人知識庫能直接接 MCP，就直接接；需要特定平台入口時，再做相應的封裝。

## 哪些是自己建立，哪些是重用

| 實作 | 自己處理的部分 | 重用的部分 |
| --- | --- | --- |
| 共用能力中心 | 能力目錄、操作規則、核心執行與驗收流程 | 既有業務系統 API |
| Vault RAG & Inbox MCP | search／fetch、受限筆記寫入、OAuth 與來源政策接合 | 既有 VaultRAG 檢索服務 |
| Google Chat Reader 外掛 | 外掛連線設定、OAuth 範圍、使用規則與實際讀取驗收 | Google 官方 remote MCP；沒有重寫 Google Chat 服務 |
| DSH 接入 | 自建 MCP 的桌面 OAuth 相容性、描述檔、問題修復與驗收 | 第三方 dsh-mcp-connector 0.2.70；這個 connector 並非我開發 |

Google Chat 的例子尤其能說明取捨：既然已有可接入的官方服務，我做的是讓它進入自己的工作流程。當時選了 **4 個 Chat 唯讀 scope**，另加登入身分所需範圍。但「唯讀」只能限制操作種類，並不自動限制成指定工作群組；那次實作尚無群組白名單技術隔離。

2026-10-02 的外掛修正版能完成 search_conversations → list_messages，讀回訊息；同時，授權是否能在一小時後持續刷新，仍列為待驗收。修正版加入 access_type=offline 與 prompt=consent，並重新授權；Google 文件說明離線存取需在授權要求設定 access_type=offline，才能取得更新權杖。**參數改對與長期運作通過，是兩項不同證據。** 參考：[Google OAuth 網路伺服器流程](https://developers.google.com/identity/protocols/oauth2/web-server)。

## 第一個實際故障：程式測試通過，瀏覽器 OAuth 卻失敗

2026-10-13，我把同一個 Vault MCP 接到 DSH。服務端先補上桌面 public client、PKCE 與受限制的 loopback callback。程式化探測已通過授權、search、fetch、refresh、revoke，真正的 Chrome 授權頁卻一直顯示「拒絕跨站請求」。

初期曾把方向放在批准碼錯誤或過期。後來追到服務端處理順序才發現：**Origin 檢查發生在批准碼檢查之前。** 請求根本還沒走到批准碼驗證。

| 同一授權頁的觀察 | 修復前 | 修復後 |
| --- | --- | --- |
| Referrer-Policy | no-referrer | same-origin |
| Chrome 表單 POST Origin | null | 正確的同站 Origin |
| 使用無效測試值送出 | 403，拒絕跨站 | 400，批准失敗或逾時 |
| 真實授權 | 無法完成 | 完成連線 |

上表的 400 不是授權成功；它表示請求已通過 Origin 檢查，進到預期的無效值驗證。之後另用真實流程完成授權，才算接通。

修正是調整這個服務授權頁的 Referrer-Policy，保留嚴格的同站檢查。這是本案瀏覽器與頁面設定的實測，不代表所有 OAuth 錯誤都應套同一修法。

為什麼先前測不到？因為 probe 自行帶了正確 Origin，繞過了瀏覽器實際產生標頭的那段行為。0.4.1 修復時，第一組 **29 項測試通過**；加入 Origin 回歸後，transport 組 **11 項通過**。兩組有重疊，不能相加成 40 項。

## 第二個實際故障：插件已安裝，host 還沒載入

另一個卡點發生在註冊設定。DSH 的 cordis.yml 是空清單佔位，註解指向 cordis.patch.yml；把條目寫進前者，套件雖然存在，工具仍不生效。後續修 patch 時，又曾用正規表示式改壞無關 YAML 區塊，必須還原再追加。

這次讓我把驗收拆成七關：**安裝 → host 載入 → 描述檔匯入 → OAuth → search → fetch → 重啟後複測**。每一關都要留下實際結果，不能靠模型說「完成」跨過下一關。

2026-10-13 最終驗收取得 1 個 server、**7 個工具**；實際 fetch 回 **2,144 字元、103 行**，has_more=false，帶來源路徑、雜湊與行號。重啟後不需重新授權，搜尋仍成功。這證明該次接入與重啟持久性，沒有涵蓋全部查詢品質或長期穩定性。

## 459 次工具呼叫，沒有換來等比例的進展

這次接入也留下不漂亮、但值得公開的數據。從安裝要求到重啟後複測，牆鐘約 **6 小時 10 分鐘**，包含人工介入、等待批准、停機與回合間隔，並非模型連續推理時間。

下面只統計當日 12:08 的第一份日誌快照，不是整場總量。

| 快照指標 | 數值 | 計算範圍 |
| --- | --- | --- |
| 自安裝要求起的牆鐘時間 | 4 小時 19 分 29 秒 | 到第一份快照 |
| 工具呼叫 | 459 次 | 其中 pwsh 439 次 |
| 完全相同呼叫的額外重複 | 152 次 | 同組第一筆之外的重複 |
| 已完成上下文壓縮 | 11 次 | 該快照內 |
| 額外重複占工具呼叫 | 約 33.1% | 152 ÷ 459，本文換算 |

約三分之一的呼叫是完全相同輸入的額外重複，但不能直接換算成三分之一時間可省：每次呼叫的執行與模型等待成本不同。日誌還顯示，後續至少 **28 次**同類瀏覽器歷史查詢，並沒有定位真正的 Origin 問題。

我後來把指導縮成有停止條件的關卡：同方法失敗兩次就交付證據；OAuth 只 connect 一次，逾時後查一次 status；不再反覆查歷史網址，也不讓 Agent 終止承載自己的主程序。限制流程之後，Agent 能停下來回報，但當時授權仍失敗。**停止無效探索是改善流程，修復 Origin 才是解決故障。**

這不是模型排名測試。當時模型標示為 strata-local／qwen3.8-flash-next-iq3_s，沒有固定任務換模型的對照組，因此無法把責任全部歸給模型、量化或 DSH。實際完成也有 Codex 修復與人工操作，不能宣稱 Agent 自主完成整段建置。

## 第三個實際故障：後端新增來源，MCP 還守著舊名單

2026-10-14，聯合搜尋出現 source_not_allowed。後端已擴大核准專案，但 MCP adapter 還寫死最初 **4 個專案**。候選結果一旦碰到新增專案，整次 search 就拋錯，連合法結果也不回傳。

這與 OAuth 是兩個不同問題。連線已成功，壞的是前後端重複保存的範圍政策。只改問某個舊專案會成功，也不能據此判斷其他搜尋全部正常。

0.4.2 將 adapter 的工作收斂為 ID 安全格式檢查；可查哪些專案由後端 published_projects() 決定。fetch 仍檢查 scope、search 登錄的不透明 ID、來源版本與核准範圍。部分候選不合法時保留有效結果，並回 coverage.partial 與 failures，讓呼叫端知道結果不完整。

| 2026-10-14 驗收 | 實際結果 | 可以支持的結論 |
| --- | --- | --- |
| adapter 回歸 | 16 項通過 | 覆蓋新來源、撤權後舊 ID、版本、分頁、惡意路徑與部分失敗等情境 |
| Inbox 回歸 | 26 項通過 | OAuth、安全、audit 與 transport 回歸通過 |
| 原本失敗的問句 | 回 8 筆；partial=false，failures=[] | 該問句不再整批失敗 |
| 另一個限定來源問句 | 回 7 筆，無部分失敗 | 原有來源的一次回歸成功 |
| 精確 ID 查詢 | 回 1 筆 | 該議題可召回 |
| 原文 fetch | 5 個證據區塊、1,358 字元；has_more=false | 該來源可讀完 |
| 部署回執 | completed=true，rollback=false；health 版本 0.4.2 | 該次正式部署通過 |

16 與 26 是這次兩組測試的項數，不能拿來當回答正確率。8 筆是該次搜尋結果數，不是完整資料總量。這次正式驗收使用 OAuth helper 與 connector 呼叫，沒有重新操作 DSH Desktop UI；不能借用前一天的 UI 驗收替代這一點。

## 給要做同類整合的人：先決定如何證明完成

這幾次實作之後，我會先把驗收表寫出來，再開始接線。下面是依本案整理的方法，並非另一輪已執行的測試結果。

| 要驗證的事 | 留下的證據 | 必須另外測的失敗情境 |
| --- | --- | --- |
| host 真的載入外掛 | 可呼叫的工具清單與版本 | 套件存在但未註冊、改錯 profile |
| 使用者真的能授權 | 真實瀏覽器表單、callback、連線狀態 | 錯誤 Origin、過期 request、取消授權 |
| 查詢走對資料來源 | search 的來源類型與可 fetch 的 ID | 新增來源、來源撤權、部分候選失敗 |
| 回答能追溯 | fetch 的原文、行號或證據區塊、版本 | 舊 ID、截斷內容、來源版本變動 |
| 寫入符合預期 | 預覽內容、使用者確認、執行後回讀 | 過期預覽、重複執行、未授權路徑 |
| 能持續使用 | 重啟後實際查詢、到期後 refresh 測試 | 憑證失效、服務中斷；各自記錄結果 |

對我而言，建立 MCP 和 Plugin 的成果，是讓原本分散的工作能力有共同入口，並能說清楚每個入口的權限、來源與失敗原因。這些事需要需求整理、架構取捨、實作和驗收一起做；工具數量只是其中很小的一部分。

## 數據來源與公開界線

本文依據三組自己的實作紀錄：2026-08-03 至 08-04 能力中心架構與執行紀錄、2026-10-01 至 10-02 Google Chat Reader 外掛設定與讀取紀錄、2026-10-13 至 10-14 DSH 軌跡分析與 MCP 0.4.1／0.4.2 部署驗收。量化數據來自日誌分析、測試結果與部署回執；公開稿已移除客戶名稱、內部單號、主機位置與憑證。

原始工作資料與 session 未公開，讀者無法由本文獨立重跑我的私人資料測試。本文提供的是明確的條件、結果與驗收方法，沒有宣稱節省多少人力、提高多少正確率或保證長期可用。Google 文件支持 OAuth 參數說明，並不替我的個案數據背書。

延伸閱讀：[DSH 接本地 AI：工具能用之後，瓶頸才開始浮現](/knowledge/dsh-local-ai-bottlenecks/)。
