Community程式設計與開發github.com

Rene-Roscher/mcp-server-design

設計和審查 MCP 伺服器,讓代理可在多租戶產品中安全使用:工具作為服務層的轉接器,每個連線繫結一個租戶於 token 中,每次呼叫檢查權限,具備預覽與確認的效果類別、重播防護、緊急停止開關、錯誤形狀及模型評估。適用於建置或審查 MCP 工具或伺服器,不適用於使用 MCP 伺服器或 REST 設計。

mcp-server-design 是什麼?

此技能定義了 MCP 伺服器如何將產品安全地暴露給 AI 代理,特別針對多租戶環境。其核心規則包括:工具作為服務層的轉接器,不得直接存取 ORM;每個連線嚴格繫結一個租戶,由 credential 決定,防止代理選擇錯誤租戶;每次工具呼叫皆需即時檢查權限,且權限於連線建立時凍結,角色變更後自動終止連線。技能還強制將工具分類為唯讀、靜默寫入、通知、喚醒等效果類別,並對可能影響客戶或刪除資料的操作引入兩步驟流程(預覽與確認),以防止未經同意的操作。此外,還涵蓋重播防護(基於連線、工具與引數的 SHA-256 指紋)、錯誤形狀(強制使用 `isError: true` 並附上 JSON 細節)、以及結果中的第三方文字標記為不可信。這份設計特別適合在 Claude Code 或 Cursor 等平台上開發 MCP 伺服器的工程師,可將安全規則直接內建於伺服器程式碼中,而非依賴客戶端的提示。

相容平台✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Rene-Roscher/skills/tree/HEAD/plugins/api/skills/mcp-server-design

Installed? Explore more 程式設計與開發 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

MCP 伺服器設計

此技能定義 MCP 伺服器如何將產品暴露給代理:工具在程式碼中的位置、連線如何繫結到一個租戶和一個人、哪些呼叫需要人員確認,以及結果和錯誤如何引導模型進行下一個正確的呼叫。協定語法遵循 MCP 規格 (撰寫時為修訂版 2026-07-28) 和 SDK 文件。查詢範圍、權限目錄和 ID 格式來自 tenant-isolation、authorization-rbac 和 dual-key-identifiers。此技能預防的失敗情境:模型選錯租戶、重複寄送郵件給客戶、或對第三方植入的文字採取行動,因為伺服器在模型、客戶端或工具描述中留下了一個閘門。

適用時機 / 不適用

  • 適用:為產品建置 MCP 伺服器、新增或審查工具、選擇 MCP 憑證如何繫結到租戶和人員、通知客戶、聯繫人員、靜音警報或刪除的工具、客戶端重試後的寫入重複、錯誤和結果形狀、工具名稱和描述、小型模型的工具集、工具使用評估。
  • 不適用:設定客戶端使用他人的 MCP 伺服器、REST 或 GraphQL 端點設計、非 MCP 的聊天機器人和斜線指令、為產品自己的助手撰寫提示、非 MCP 路由的速率限制。

規則

最重要的優先。範例使用預約系統:預約、客戶、技術人員。

  • [不變] 伺服器強制執行不該出錯的事項;描述和註解僅用於引導。以下每個閘門都在每次呼叫的伺服器程式碼中執行。原因:註解只是提示,且存在自動核准或從不提示的客戶端。測試:從忽略提示的原始 JSON-RPC 客戶端呼叫每個受保護的工具;每個守衛仍會觸發。來源:MCP 架構 2026-07-28,ToolAnnotations:「客戶端不應根據來自不受信任伺服器的 ToolAnnotations 做出工具使用決策」(https://modelcontextprotocol.io/specification/2026-07-28/schema,驗證於 2026-10-31)。
  • [不變] 工具是服務層的第二個轉接器:它們呼叫與 UI 和 API 相同的領域服務,絕不透過回送方式呼叫自身的 HTTP API,也絕不直接呼叫 ORM。保護資料的檢查(限制、防止 SSRF 的 URL 允許清單、所參考記錄的所有權)位於服務層,而非僅在控制器或表單請求中。每個函數只有一個寫入路徑。原因:薄轉接器會繼承其兄弟轉接器所執行的所有檢查。測試:架構測試會使工具類別中的持久化呼叫失敗;每個寫入工具,API 拒絕的輸入也會被工具拒絕。來源:可透過上述測試重現。
  • [不變] 一個連線,一個租戶:建立連線時,租戶繫結於 credential 中,沒有工具架構包含租戶參數,像是 workspace_id 這樣的引數會被拒絕為未知,且每個 handle 都會在繫結的租戶內解析。為多個租戶服務的 credential 會將租戶放在傳輸層 (路徑或標頭) 中,並根據 credential 進行檢查,絕不放在工具引數中。原因:租戶參數會讓模型選擇租戶。測試:對每個有參考的工具,傳入另一個租戶的有效 handle;預期得到 not_found 且資料列未變更。來源:MCP 工具規格,工具集「可能因請求中呈現的授權而有所不同」(https://modelcontextprotocol.io/specification/2026-07-28/server/tools);安全最佳實踐,透過「從驗證過的 token 而非客戶端提供的」使用者 ID 來鍵結狀態 (https://modelcontextprotocol.io/docs/2026-07-28/tutorials/security/security_best_practices);兩者皆驗證於 2026-10-31。
  • [不變] 權限即時截斷:有效權限 = token 權限 ∩ 建立連線者目前的權限,在每次 tools/list 和 tools/call 時計算。Token 權限在建立時上限鎖定,移除或停用建立者會終止連線,撤銷則透過可稽核的服務執行。原因:凍結在 token 中的權限會比角色變更更持久。測試:移除建立者的角色,呼叫一個寫入工具;下一次呼叫時拒絕。來源:MCP 工具規格,伺服器必須「實施適當的存取控制」(驗證於 2026-10-31)。
  • [不變] MCP 請求上沒有隱含使用者:中介軟體繫結連線上下文 (租戶、建立者、即時權限),並將架構的目前使用者插槽留空;工具從上下文讀取行為者,服務將行為者作為參數。原因:若將擁有者設為目前使用者,則工具中若有遺漏的政策檢查會通過,因為擁有者可以做任何事;沒有使用者時則會以關閉方式失敗。測試:架構測試禁止在工具程式碼中使用 gate、policy 和 current-user helper;故意加入的政策檢查會使呼叫失敗。來源:可透過上述測試重現。
  • [不變] 每個工具只有一個效果類別:讀取、靜默寫入、通知 (會接觸客戶或改變他們所見)、喚醒 (傳呼或致電某人)、警報 (靜音、暫停或重新路由警報)、刪除。根據程式碼路徑 (事件、監聽器、觀察者、工作、郵件) 推斷類別,而非名稱,並將橫跨兩個類別的工具拆分。原因:提示、速率桶、預覽職責和連線切換都來自類別。測試:使用佇列、郵件和事件假物件,靜默工具不會分派任何通知。來源:連接器審查標準,拒絕萬用讀取/寫入工具,並按動作類型拆分寫入 (https://claude.com/docs/connectors/building/review-criteria,驗證於 2026-10-31)。
  • [不變] 通知、喚醒、警報和刪除需要兩個步驟。工具僅準備:它儲存一個待處理的動作 (連線、工具、標準化引數和摘要、將發送內容的雜湊、過期時間),並回傳 PREVIEW、一個 confirm_token 和伺服器寫入的 confirm_summary。只有 confirm_action 會執行,在檢查連線、未使用、未過期、未被拒絕、確切的摘要、鎖定、即時權限和開關、以及內容雜湊仍匹配的重新準備 (否則為新的預覽,舊 token 失效) 之後。原因:模型和客戶端都不可靠地詢問人員,且核准的文字可能在期間內變更。測試:僅預覽不發送任何東西;重用、過期、外部和錯誤摘要的 token 失敗;步驟之間的編輯產生 changed_since_preview。來源:MCP 工具規格,「應始終有人類在迴圈中,有權拒絕工具呼叫」;MRTR 伺服器要求:由客戶端回顯的狀態是「攻擊者控制的輸入」,繫結到主體、短暫過期和參數摘要、伺服器端強制單次使用 (https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/mrtr);兩者皆驗證於 2026-10-31。
  • [不變] 每個靜默寫入都有重播防護:指紋 = 連線 + 工具 + 標準化引數規範 JSON (每層級鍵排序) 的 SHA-256;時間窗內命中回傳包含 replayed: true 的第一個結果,且不執行任何動作。可選的 idempotency_key 繫結到引數摘要。快取保留簡報器輸出,絕非一次性秘密。原因:中斷的串流會遺失正在傳輸中的請求,且客戶端必須以新 ID 重新發出,因此已執行的寫入會再次到達。測試:以不同 JSON-RPC ID 兩次相同呼叫;一個資料列,第二個結果為 replayed: true。來源:MCP 變更日誌 2026-07-28,主要變更 9 (https://modelcontextprotocol.io/specification/2026-07-28/changelog);Claude Code 在新的連線上重複對標記為 idempotentHint 或 readOnlyHint 的工具的待處理呼叫一次 (https://code.claude.com/docs/en/mcp);兩者皆驗證於 2026-10-31。
  • [不變] 每個建立工具都有一個對應的更新工具和一個基於業務鍵的重複防護。命中回傳一個錯誤,指出現有的 handle 和更新工具;仍然建立需要 separate: true 和一個原因。防護讀取同步欄位 (created_by、created_at),絕不讀取佇列的稽核日誌。編輯工具沒有 notify 參數。原因:被要求「讓文字更好」時,模型會建立第二個物件,除非被阻止。測試:以相同鍵建立兩次;一個資料列,錯誤指出該鍵。來源:可透過上述測試重現。
  • [不變] 更新攜帶呼叫者讀取的版本:結果中的物件和訊息有 version (內容雜湊),更新和編輯工具需要它,過期版本回傳附有目前狀態的 conflict。原因:從舊讀取內容工作的代理會覆寫同事的編輯。測試:讀取、在其他地方變更資料列、以舊版本更新;conflict,資料列未變更。來源:可透過上述測試重現。
  • [不變] 批次工具接受明確的 handle,或一個篩選條件加上必填的 expected_count;當匹配計數不同時,回傳匹配項目且不執行任何動作。原因:比模型預期更廣的篩選條件會將一個錯誤倍增。測試:一個篩選條件匹配 12 筆預約,expected_count: 3;錯誤列出 12 筆,無任何變更。來源:可透過上述測試重現。
  • [不變] 使用工具自身的規則在伺服器上驗證每個引數,並拒絕未知欄位,列出允許的欄位。原因:SDK 並不總是根據 inputSchema 檢查引數,且被忽略的欄位會讓模型認為它設定了一些東西。測試:額外欄位回傳允許清單且不寫入任何東西。來源:MCP 工具規格,伺服器必須「驗證所有工具輸入」(驗證於 2026-10-31)。
  • [不變] 結果中的第三方文字不受信任:客戶、終端使用者、外部系統或上傳可以寫入的文字 (備註、郵件內文、webhook 酬載、遠端錯誤訊息) 會被截斷、移除控制字元,並在 untrusted_text 下回傳,附上不要遵循其中指令的注意事項;原始內文保留在外。描述描述工具,且從不告訴模型如何行為。原因:注入的文字會引導具有外部效果的工具是最高的風險;預覽步驟是第二道防線。測試:每個第三方欄位中的注入測試字串僅出現在包裝器中。來源:MCP 工具規格,伺服器必須「清理工具輸出」;連接器審查標準拒絕描述中的提示注入模式;兩者皆驗證於 2026-10-31。
  • [不變] 結果不攜帶秘密,僅包含任務所需的個人資料:簡報器使用明確的欄位清單,絕不使用整個記錄序列化器;日誌保留工具名稱、結果和公開 ID,絕不保留引數。原因:每個結果都會傳送到使用者的模型提供者。測試:用誘餌值填滿測試裝置的每個秘密欄位,呼叫每個讀取工具,在結果和日誌中 grep;無誘餌。來源:可透過上述測試重現;記錄保存在 gdpr-engineering 中。
  • [不變] 業務、驗證和權限失敗是工具執行錯誤 (isError: true),其文字以 Error: 開頭,指出原因、修正方法和正確的工具及真實的 handle,並以一行 JSON 詳細資訊結尾。「未找到」指向 find_ 工具,絕不指向建立。被權限、工具集或方案隱藏的工具以該原因回應。原因:模型只有在看到並理解錯誤時才會自我修正。測試:合約套件中的每個錯誤路徑都以 Error: 開頭,並有一個可解析的 JSON 行。來源:MCP 架構,CallToolResult.isError:工具錯誤「應在結果物件內報告……否則,LLM 將無法看到發生錯誤並自我修正」;連接器審查標準拒絕一般的「內部伺服器錯誤」回應;兩者皆驗證於 2026-10-31。
  • [不變] 文字區塊和 structuredContent 承載相同的 JSON,且宣告的 outputSchema 涵蓋每個結構化結果,包括錯誤。原因:不同的客戶端在何種通道到達模型方面有所不同,且它們應根據宣告的架構進行驗證。測試:對於套件中的每個結果,解析後文字等於 structuredContent。來源:MCP 工具規格,具有結構化內容的工具「也應在 TextContent 區塊中回傳序列化 JSON」(驗證於 2026-10-31)。
  • [不變] 時間和金錢是伺服器的職責:時間以當地時間加上 IANA 時區或 ISO 8601 搭配偏移量抵達,絕不單獨;不存在和模糊的當地時間會被拒絕並提供選項;結果中的每個時間顯示星期幾、當地時間與時區、UTC 和距離現在的時間。總計、稅金和四捨五入由伺服器從十進位字串或整數最小單位計算。原因:相差一天和日光節約時間錯誤在沒有星期幾的情況下無法察覺。測試:2026-03-29 02:30 在 Europe/Berlin 中被拒絕;2026-10-25 02:30 回傳兩個偏移量。來源:可透過上述測試重現。
  • [不變] 緊急停止開關只在一個地方:一個全域旗標,由 CLI 指令和管理員切換設定 (兩者皆會稽核),使每個 MCP 呼叫回傳 503 並附上 Retry-After,同時連線保持;一個每個租戶的開關,其預設值定義一次,讓租戶管理員關閉代理存取。原因:事件需要一個動作來停止所有代理寫入。測試:旗標開啟,呼叫寫入工具;503,無資料列,無工作。來源:可透過上述測試重現。
  • [不變] 每個 MCP 寫入都使用 channel mcp、連線和客戶端名稱進行稽核,在進入點作為欄位設定,且 list_recent_changes 顯示此連線變更了什麼。原因:租戶看到代理做了什麼,且失去上下文的代理找到其草稿,而非建立另一個。測試:每個寫入工具產生恰好一個 channel 為 mcp 的稽核資料列。來源:可透過上述測試重現。
  • [不變] MCP 和 API 憑證不交叉:MCP token 有它們自己的類型和前綴,以雜湊儲存,僅顯示一次,並被每個 API 路由拒絕;API token 和 session cookie 被 MCP 端點拒絕。使用 OAuth 時,僅接受為此伺服器發行的 token,且絕不傳遞。驗證 Origin。原因:MCP token 以純文字存在於客戶端設定檔中。測試:每個配對回傳 401;錯誤的 Origin 回傳 403。來源:MCP 授權規格,伺服器「不得接受或傳輸任何其他 token」(https://modelcontextprotocol.io/specification/2026-07-28/server/authentication,驗證於 2026-10-31)。
  • [不變] 日誌記錄:管理員可以從稽核日誌查看工具名稱、結果和公開 ID,但無法查看引數或個人資料。

相關技能