iT邦幫忙

2026 iThome 鐵人賽

DAY 4
0
自我挑戰組

AI Agent 不該有萬能鑰匙:打造可稽核的 MCP 工具權限閘道系列 第 4

Day 04|MCP 握手成功之後,權限檢查才要開始

  • 分享至 

  • xImage
  •  

昨天接通了 Pi、橋接程式與 Python 閘道。接著很容易出現一個誤會:既然能連線、能列出五個工具,權限應該也沒問題了。今天要用兩種錯誤請求拆開這件事:讀取未授權文件,以及替工具偷偷多加一個核准欄位。前者要交給政策拒絕,後者連業務邏輯都不應進入。

本機 stdio 測試先完成 initialize 握手,再用 tools/list 取得工具目錄。這只證明雙方能交換協定訊息;接下來每次呼叫的參數形狀與資料權限,都還要各自檢查。

同一條連線可以成功讀到 public/handbook.txt,也可以正常收到私人文件的拒絕結果。這兩次往返會是今天的主例子:連線本身都成功,業務決定卻不同。接著再放入多餘參數,確認那是另一種結束方式。

握手通過,只代表兩邊講同一種話

initialize 交換的是協定版本與能力,tools/list 送出的是這個 server 願意被呼叫的工具與參數 schema。它們合起來是一份契約,說明有哪些門可以敲,沒有說明誰可以敲哪一扇。MCP 官方 2026-07-28 的安全最佳實務把權限代理、token passthrough、local MCP、最小權限列為不同的安全邊界(MCP 安全最佳實務)。這個區分放到實作上很好用:握手屬於傳輸與能力協商,授權屬於另一個決策點,兩者寫在同一段判斷裡,之後就沒辦法分別檢查。

這一版的五個工具固定在 Pi 0.73.0 的 extension 裡(Pi v0.73.0 原始碼):docs_readdocs_updatetickets_gettickets_add_noteexport。工具目錄不是口頭承諾,test_mcp_stdio.py 直接對它斷言:

schema = {t.name: (t.input_schema or {}) for t in tools.tools}
self.assertEqual(sorted(schema["docs_update"]["properties"]), ["content", "path"])
self.assertNotIn("approved", schema["export"]["properties"])

第三行是刻意的:export 的 schema 裡沒有 approved,因為工具介面是模型看得到的部分,模型自填的身分或核准宣稱不能成為可信授權證明;path、ticket_id、destination 等資源參數仍要由伺服器依可信身分和政策驗證。後面幾天做待核准外送時,這條線會反覆出現。

MCP協商與業務授權的分工表;設計示意

圖表列出工具呼叫經過的元件與各自的檢查責任。這是程式設計的順序表,不是一次實驗量出的結果。

沿著一條真實連線走到回應

以下節錄本機整合測試的核心呼叫,省略 unittest 類別和斷言。ROOT 指專案根目錄,self.env 指向測試建立的暫存資料庫;connect 會啟動 Python server、完成 initialize,離開區塊時再關閉連線與子程序。這段需放在非同步測試的上下文,不是獨立執行的腳本。

async with connect(ROOT, self.env) as session:
    tools = await session.list_tools()
    names = sorted(t.name for t in tools.tools)
    public_result = await session.call_tool(
        "docs_read", {"path": "public/handbook.txt"}
    )
    private_result = await session.call_tool(
        "docs_read", {"path": "private/customer-secrets.txt"}
    )
    public_body = json.loads(public_result.content[0].text)
    private_body = json.loads(private_result.content[0].text)

工具回傳不是直接把 Python 字典交給另一個程序。MCP 先把結果放進 content 區塊,本例再從第一個文字區塊解析 JSON,才看到閘道的 okerror 或文件內容。因此有兩層結果要讀:協定往返是否完成,以及回傳內文說業務動作是否允許。

公開文件那一筆應有 public_body["ok"] == True;私人文件那一筆則是 private_body["ok"] == False,錯誤碼為 denied。後者仍然是一筆成功傳回的 MCP 工具結果:server 沒有斷線,JSON 可以解析,政策只是沒有同意這次讀取。如果把「沒有丟出 SDK 例外」當成授權成功,就會在這裡讀錯。

再看參數不合法的情況。對 docs_read 多傳 approved=True,嚴格中介層會在 SDK 的業務 handler 前結束請求,客戶端收到 MCPError,code 為 INVALID_PARAMS。此時沒有正常的文件結果可讀。第 7 天才拆開中介層如何驗證與記錄;今天先把呼叫端應辨識的差別固定下來。

關閉子程序後,還能不能看到拒絕

只在記憶體裡看到一段錯誤訊息,不足以驗證稽核。另一個整合測試在同一連線中依序送出缺必要欄位、型別錯誤、多餘欄位與未知工具四個請求。離開 connect 區塊、確定 stdio server 已退出後,測試用 Store 重開剛才的資料庫。

應留下四筆 deny,原因分別是 missing_argumentsinvalid_typeextra_argumentsunknown_tool。這個順序把「收到錯誤」與「錯誤已保存」分成兩次觀察;如果只是 server 把訊息印到 stderr、沒有寫進資料庫,後半段便會失敗。

測試還會確認超長修改只留下單筆拒絕,而沒有建立文件。這項檢查曾揭露同一請求在邊界與 gateway 各記一次的問題。現在的中介層拒絕後就結束,不再把請求往下傳,避免同一次失敗被計成兩次。

SDK 行為可對照固定版本的 參數模型與驗證實作MCPServer 工具處理。本機中介層在它們之前驗證原始參數;本篇測試確認的是這個程式順序與回應。

正常與失敗對照

同一條 docs_read 路徑上,幾種結果可以並排看(test_mcp_stdio.py,環境變數指向暫存 DB):

呼叫 回傳 在哪一層結束
docs_read public/handbook.txt ok: true 閘道 allow
docs_read private/customer-secrets.txt ok: falseerror: denied 閘道 deny
docs_read public/../private/customer-secrets.txt error: invalid_path 閘道 deny
docs_readapproved: true MCPError(INVALID_PARAMS) 邊界 deny
does_not_exist MCPError(INVALID_PARAMS) 邊界 deny

第一列是正常路徑:形狀正確、路徑合法,閘道放行並留下理由。第二、三列形狀正確但無權限或路徑混淆——這一層的參數驗證完全不會知道 scope 的事,它只確定形狀沒問題,其餘交給 policy。第四、五列連閘道都沒到,直接以協定錯誤結束,並且在稽核表各留下一筆。

最後一項測試是超長內容:docs_updatedrafts/big.txt 送 20000 字元,得到 INVALID_PARAMS,邊界稽核恰好一筆 invalid_arguments,而且 drafts/big.txt 沒有進到業務儲存。這一條同時驗證拒絕只記一次,以及被拒絕的內容沒有落地。

重跑這一組:

python -m unittest tests.test_mcp_stdio -v

請使用第 3 天建立的 Python 虛擬環境,在專案根目錄執行。這組測試直接啟動 stdio 子程序,不需要 Pi、Node 橋接或模型金鑰;每案使用自己的暫存資料庫。這裡的案例數是程式測試數,不是後面模型比較的樣本數。

這一層不能證明什麼

參數驗證是形狀檢查,不是授權。形狀正確的越權呼叫——例如參數形狀合法的 tickets_get 去讀 T-2001——會一路走到 policy 才被拒,這一層不會提前發現,也不該假裝它發現了。工單越權拒絕依靠後續 scope 判斷;路徑拒絕與多餘參數拒絕則各有自己的檢查,不能統稱 scope 拒絕。

本篇的路徑拒絕發生在 SQLite 的虛擬鍵上,不是對主機資料夾做隔離。合法的文件讀取仍可能回到模型上下文;參數形狀正確,不等於內容可信。這裡只證明指定 SDK 與中介層的入口檢查,不推論其他部署或遠端協定的身分防護。

今天區分了連線可用、參數合法與操作獲准,也確認不合法欄位會留下單筆邊界拒絕紀錄,不進入業務操作。下一篇先建立共同的合成文件與工單資料,之後每次允許、拒絕和等待核准,才有明確的對象可以比較。


上一篇
Day 03|拆開 Pi:工具呼叫到底從哪裡出發
下一篇
Day 05|用一個小資料庫,固定文件與工單的世界
系列文
AI Agent 不該有萬能鑰匙:打造可稽核的 MCP 工具權限閘道5
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言