生活分享
一個 MCP 伺服器,同時接上 Claude Code、Codex、Gemini CLI 三個客戶端
同一支用官方 Python SDK 寫成的 MCP 伺服器,不必改一行程式碼就能同時登記進 Claude Code、Codex 與 Gemini CLI:三邊的設定檔位置與加入指令都不一樣,Codex 寫成 TOML、另外兩邊是 JSON,但讀到的是同一份工具名稱、輸入 Schema 與回傳格式。這篇列出三邊的設定檔與加入指令,並說明 Codex 的 enabled_tools、Gemini CLI 的 --include-tools 與 Claude Code 的權限規則,要怎麼分別把工具收窄到剛好夠用。
閱讀時間約 9 分鐘

同一個 MCP模型上下文協定(MCP)是什麼:連接工具與資料的共同介面MCP 是讓 AI 應用程式與外部工具、資料和提示範本交換資訊的開放協定,不是模型本身,也不保證接上就能完成任務。本文用查詢社區圖書室資料的例子,說明主機、用戶端、伺服器及工具、資源、提示的分工,並比較 MCP、A2A 與 Agent Skills。附連線驗證與權限檢查方法,幫你區分已設定、已連接、可呼叫與真正取得結果。閱讀全文 伺服器要給 Claude Code、Codex、Gemini CLI 三個客戶端共用,不必為每個客戶端另外寫一份工具,只要在三邊各自的設定裡登記同一支啟動指令,並且各自把工具的權限收窄到剛好夠用就好。
讀完這篇,你會用官方的 MCP Python SDK 寫一支只公開一個工具的伺服器,再把它原封不動分別登記進 Claude Code、Codex 與 Gemini CLI,最後在三邊各自做一次工具的權限最小化,而不是只在其中一邊設定。跟著做需要 Python 3.10 以上(SDK 的官方說明寫的就是這個版本下限)、照官方安裝指令裝好 mcp 這個套件,以及三個客戶端裡你至少用得到一個能在本機啟動 stdio 伺服器的版本;範例工具只讀寫在程式裡的假資料,不需要任何 API 金鑰或帳號登入。
一支伺服器,三個客戶端各自去接
MCP 官方網站的說法是,它是一個用來把 AI 應用連到外部系統的開放原始碼標準;伺服器負責公開工具、資料或提示範本,客戶端負責決定要不要用、怎麼呈現。這裡的客戶端不是一個抽象角色,而是三支各自獨立的軟體:Claude Code、Codex、Gemini CLI 各是一個行程,互相不知道彼此存在,各自去連你寫的同一支伺服器。伺服器程式完全不需要知道現在是誰連上來,它只要照協定把工具講清楚,連線的細節、要不要顯示確認畫面、要不要記住上次的授權,全部是客戶端自己的事。
MCP 完整的角色分工,包括主機、客戶端、伺服器怎麼分工,站上「模型上下文協定(MCP)是什麼:連接工具與資料的共同介面」已經整理過,這裡不重講;在 Claude 這個一般對話應用裡加連接器,是另一篇「在 Claude 裡怎麼加 MCP 連接器:讀資料夾與查行事曆的設定示範」的主題,跟本篇要講的 Claude Code 指令列工具是兩回事。一支伺服器裡多個工具怎麼命名、輸入 Schema 與分頁怎麼設計,是「Claude Code|設計 MCP 工具名稱、輸入 Schema 與分頁」的範圍,本篇的範例刻意只留一個工具。手上還沒有想接的伺服器,也可以先看「MCP 伺服器實用清單:檔案、Google、Notion、瀏覽器」找現成的;這篇假設你已經決定要接哪一支。
用官方 Python SDK 寫一個工具的伺服器
MCP 的 Python SDK 由 modelcontextprotocol 這個官方組織維護,安裝時裝的是 mcp 這個套件,要用命令列工具就裝 mcp[cli] 這個附加元件。目前是 SDK 的第二版,伺服器物件從 mcp.server 這個模組匯入,類別叫 MCPServer;幫函式加上 @mcp.tool() 這個裝飾器,函式就變成一個工具,函式名稱變成工具名稱,docstring 變成模型看到的描述,型別提示則變成輸入 Schema,伺服器在客戶端呼叫 tools/list 的時候把 Schema 送過去,不用自己寫 JSON Schema、也不用自己處理協定。下面這支伺服器只有一個工具,查一個寫死在程式裡的假航班狀態表,不接任何外部 API。
"""One read-only MCP tool for flight status. Install: pip install "mcp[cli]"."""
from typing import Annotated
from pydantic import Field
from mcp.server import MCPServer
from mcp.types import ToolAnnotations
mcp = MCPServer("Mokaair Flight Desk")
# Demo data only: a fixed table so the example has no external dependency.
_STATUS = {
"CI100": "on_time",
"CI102": "delayed_30m",
"BR225": "boarding",
}
@mcp.tool(
title="Look up one flight's status",
annotations=ToolAnnotations(read_only_hint=True, open_world_hint=False),
)
def flight_status(
flight_no: Annotated[str, Field(description="Flight number, such as CI100.")],
) -> str:
"""Return one flight's status from the demo schedule."""
return _STATUS.get(flight_no.upper(), "unknown")
if __name__ == "__main__":
mcp.run()
flight_no 這種有型別但沒有預設值的參數,SDK 會直接標成輸入 Schema 裡的必填欄位,官方文件的說法是兩個參數都在 required 裡,因為兩個都沒有預設值。annotations 裡的 read_only_hint、open_world_hint 是說給客戶端聽的行為提示,官方 SDK 文件講得很白:它們是提示、不是安全機制,還特別要你不要指望客戶端會照做,真正擋不擋還是要看客戶端自己的權限設定。工具執行完會回傳兩種東西:content 是模型會讀到的文字,structured_content 則是給客戶端程式用的結構化輸出,由函式的回傳型別決定要不要有、長什麼樣子。這支範例只示範 stdio 這個傳輸方式;要換成常駐的 Streamable HTTP,官方文件寫只要在 mcp.run() 裡指定 transport 與它的選項,工具本身不用改,同一頁三個檔案公開的是一模一樣的工具。
在三個客戶端各自登記同一支伺服器
三個客戶端都能用 stdio 這個傳輸方式接同一支伺服器:照 SDK 文件的說法,主機會把你的檔案當成子行程啟動,透過它的標準輸入與標準輸出交談,你從頭到尾沒有給過任何埠號,也沒有埠號可給。伺服器端完全不用改,要改的是各客戶端自己的設定;下面這張表整理三邊的設定檔、格式與加入指令。
| 客戶端 | 設定檔 | 格式 | 登記同一支 stdio 伺服器的指令 | 只留必要工具的做法 |
|---|---|---|---|---|
| Claude Code | 團隊共用:專案根目錄的 .mcp.json;只給自己看:~/.claude.json | JSON,鍵是 mcpServers | claude mcp add --transport stdio flight-desk -- python flight_server.py | 權限規則裡指定 mcp__flight-desk__flight_status |
| Codex | 預設 ~/.codex/config.toml;也能用專案內的 .codex/config.toml,文件註明僅限受信任的專案 | TOML,區段是 [mcp_servers.flight-desk] | codex mcp add flight-desk -- python flight_server.py | config.toml 裡設 enabled_tools 只列 flight_status |
| Gemini CLI | 預設寫進專案內的 .gemini/settings.json;也能指定使用者層級 | JSON,鍵是 mcpServers | gemini mcp add flight-desk python flight_server.py | 加入時帶 --include-tools flight_status |
三邊的加入指令長得不一樣,語法也有差。Claude Code 的文件把規則寫出來了:對 stdio 伺服器來說,兩個連字號(--)把 Claude 自己的選項(文件點名 --transport、--env、--scope)跟啟動伺服器的指令與參數分開,-- 後面的內容原封不動交給伺服器;文件也說,沒有這個分隔號的話,Claude Code 會把伺服器的旗標當成自己的選項來解析。Codex 那一頁沒有這段文字說明,但 codex mcp add 的語法與範例同樣把 stdio 伺服器指令放在 -- 後面。Gemini CLI 的文件寫的基本語法,是 gemini mcp add 後面依序接選項、伺服器名稱、指令或網址,再接指令自己的參數,中間不帶這個分隔號;那一頁唯一帶 -- 的示範,是把它放在伺服器自己的旗標前面,頁面沒有說明理由,所以本文也不替它補一個。
# Claude Code: local scope by default, so this lands in your home-directory
# config (add --scope project to write .mcp.json at the project root instead)
claude mcp add --transport stdio flight-desk -- python flight_server.py
# Codex: writes into ~/.codex/config.toml under [mcp_servers.flight-desk]
codex mcp add flight-desk -- python flight_server.py
# Gemini CLI: --scope defaults to project, so this lands in .gemini/settings.json
gemini mcp add flight-desk python flight_server.py
預設值也不一樣,兩邊的文件都寫明了。Claude Code 的文件說 local scope 是預設值,每一道加入指令都寫進 local scope,除非你自己加上 --scope project 或 --scope user;這種伺服器只在你加它的那個專案載入,只有你看得到,設定存在家目錄的設定檔,不在專案裡。Gemini CLI 的 gemini mcp add 則把 -s, --scope 的預設值標成 project,直接寫進目前專案的 .gemini/settings.json,要放到使用者層級才另外指定。Claude Code 要讓團隊共用,得指定 --scope project 寫進專案根目錄的 .mcp.json,文件要你把這個檔案簽進版控,團隊每個人才會拿到同一份設定;Gemini CLI 的這一頁沒有提版控或團隊共用,只說 scope 決定寫進使用者設定還是專案設定。
把工具收窄到剛好夠用
同一支伺服器如果本來就只公開一個工具,三邊天生就是最小權限;但伺服器通常不會只有一個工具,這時候要收窄的是每個客戶端各自的白名單,而且三邊要分開做,做了其中一邊不會連帶影響另外兩邊。Codex 的文件把 enabled_tools 寫成工具的允許清單、disabled_tools 寫成拒絕清單,而且說拒絕清單在允許清單之後才套用;兩個欄位都放在 [mcp_servers.<伺服器名稱>] 這個區段裡,而文件說設定檔裡一支伺服器一張表,所以已經用 codex mcp add 登記過的話,是把 enabled_tools 加進那張表,不是另外再寫一張同名的;下面這段先把整張表寫到另一個檔案,你再把 enabled_tools 那一行加進去。
# Codex: the finished [mcp_servers.flight-desk] table, with the allow list.
# Written to a scratch file so nothing here touches your own config: copy
# enabled_tools into the table `codex mcp add` already wrote in
# ~/.codex/config.toml, which holds one table per server.
cat > flight-desk.codex.toml <<'EOF'
[mcp_servers.flight-desk]
command = "python"
args = ["flight_server.py"]
enabled_tools = ["flight_status"]
EOF
# Gemini CLI: pass the same allow list when you register the server
gemini mcp add --include-tools flight_status flight-desk python flight_server.py
Claude Code 走的是另一條路。本文查證當天,它的 MCP 文件裡沒有列出像 enabled_tools、--include-tools 這種在加入伺服器時只留某幾個工具的設定;文件教的是用工具的可呼叫名稱去限制。那個名稱的形狀是 mcp__伺服器名稱__工具名稱,以這篇的例子來說就是 mcp__flight-desk__flight_status;文件講外掛伺服器的工具名稱時寫著,要用完整名稱去指定權限規則、Skill 的 allowed-tools 清單、子代理子代理(Subagent)是什麼:把有邊界的工作交出去子代理是由主代理委派特定工作的代理,通常有自己的任務上下文,再把結果交回主代理整合;它不一定使用不同模型,也不必永久保存記憶。本文以社區刊物的資料查核為例,說明交辦範圍、證據格式、同步與背景執行的差別,以及為什麼子代理說完成仍需要驗收。附交接圖與實用檢查表,幫你判斷何時分派能減少負擔。閱讀全文的 tools 欄位或 hook 的比對條件,同一段也拿只寫伺服器名稱的形式當 hook 比對的例子。Claude Code 官方文件也提醒,連接伺服器前要先確認信任它,因為會抓外部內容的伺服器可能帶來提示詞注入提示詞注入(Prompt Injection)是什麼提示詞注入是讓模型把不可信內容中的文字當成應遵循的指令,進而偏離原本任務。本文用閱讀活動報名郵件的原創案例,說明直接與間接注入、資料和權限的界線,以及為何檢索、引用格式或一句「忽略惡意指令」不能包辦防護,並整理外部內容分隔、工具授權與人工確認各自能阻止的失敗。閱讀全文風險,這也是三邊都值得把工具收到剛好夠用的原因。
怎麼確認三邊真的看到同一支伺服器
改完設定,值得花一分鐘用每個客戶端自己的指令確認一次,而不是直接請模型呼叫工具試手氣。
- Claude Code:文件說要確認它連上了就跑 claude mcp get flight-desk;claude mcp list 會在每一台旁邊顯示健康狀態,例如 Connected。在對話裡輸入 /mcp 也可以看伺服器狀態。
- Codex:執行 codex mcp list 看已設定的伺服器,確認 flight-desk 在裡面;在 codex 的 TUI 裡用 /mcp 看目前作用中的 MCP 伺服器。
- Gemini CLI:執行 gemini mcp list 看每一台的連線狀態。文件提醒,stdio 伺服器只有在目前資料夾被信任時才會被測試並顯示為 Connected,資料夾沒被信任就會顯示 Disconnected,這時用 gemini trust 信任目前資料夾。
三邊都自己確認過一次,才代表真的讀到同一份工具契約。只看到伺服器被寫進設定檔,不等於連線成功,更不等於工具已經可以被呼叫,這三件事是分開的檢查點——Claude Code 的文件就分得很清楚:claude mcp add 印出的那行 Added,意思只是設定被寫進去了,連不連得上要看 claude mcp list 旁邊的健康狀態。
常見問題
三個客戶端可以直接共用同一份設定檔嗎?
不行,三邊讀的是三個不同的檔案。Claude Code 用 .mcp.json 或 ~/.claude.json,Gemini CLI 用 settings.json,這兩邊都是 JSON 的 mcpServers 物件,但檔案位置與其他設定不同;Codex 用的是 ~/.codex/config.toml,寫成 TOML 的 [mcp_servers.<伺服器名稱>] 區段。能共用的是同一支伺服器程式跟同一個啟動指令,三邊各自登記一次就好。
一定要用 stdio 嗎?
不一定。這篇的例子用 stdio,是因為三個客戶端都支援、設定也最簡單:客戶端自己啟動伺服器程式,不用另外管網址或連線狀態。伺服器也能改成用 Streamable HTTP 常駐一份,三個客戶端改成指向同一個網址,但那樣就要多處理連線位址這些事,不是本篇要講的重點。
伺服器的工具改了,三邊要各自更新嗎?
伺服器程式本身只要改一次,三個客戶端下次重新連上它、呼叫 tools/list 的時候都會拿到新的輸入 Schema,不用分別修改。但如果連工具名稱都改了,而這個名稱已經寫進某個客戶端的白名單,像 Codex 的 enabled_tools 或 Gemini CLI 的 --include-tools,那個白名單也要跟著改,不然新名字會直接被擋下來。
加入指令裡,為什麼有的地方要加 --,有的不用?
Claude Code 的文件寫,-- 把 Claude 自己的選項(文件點名 --transport、--env、--scope)跟啟動伺服器的指令分開,-- 後面的內容原封不動交給伺服器,沒有它 Claude Code 會把伺服器的旗標當成自己的選項解析。Codex 那一頁沒有這段說明,但它的 codex mcp add 語法同樣把伺服器指令放在 -- 後面。Gemini CLI 文件寫的基本語法則是伺服器名稱後面直接接指令與參數,不帶這個分隔號;那一頁唯一帶 -- 的示範,是把它放在伺服器自己的旗標前面,頁面沒有解釋原因。
把 read_only_hint 設成 true,客戶端就不會再跳確認了嗎?
不一定。官方 Python SDK 的文件把 read_only_hint 這類標註寫成給客戶端參考的提示,不是強制的安全機制,還特別要你不要指望客戶端會照做。真正決定要不要跳確認、或乾脆不讓模型看到某個工具的,是客戶端自己的權限設定,像 Codex 的 enabled_tools 或 Gemini CLI 的 --include-tools,這些才是三邊各自要分開做的事。
多模型 AI 工作流教學:從拆任務到串接不同模型多模型 AI 工作流教學:從拆任務到串接不同模型這個系列教的是怎麼把一件工作拆開、交給合適的模型,再把結果接回同一條流程:判斷該不該拆、拆給誰,換供應商不改程式,設計便宜先試的路由與級聯,讓模型之間用結構化輸出交接資料,再到代理式工具怎麼分工、同一套工具怎麼給多個客戶端共用、Claude Code 與 Codex 怎麼搭本機模型,以及上線後怎麼追蹤與防護。一般使用者可以從判斷該不該拆的觀念讀起,已經會寫 Python 的人能直接進到換供應商、寫路由與交接資料的幾篇。閱讀全文
MCP 伺服器實用清單:檔案、Google、Notion、瀏覽器MCP 伺服器實用清單:檔案、Google、Notion、瀏覽器MCP 伺服器就是讓 AI 助理碰得到某樣東西的插頭。這篇整理四類日常用得到的伺服器:本機檔案的 filesystem、Google 的雲端硬碟與行事曆與 Gmail、Notion 代管的遠端伺服器,以及 Playwright 與 Chrome DevTools。每一個都寫清楚誰維護、授權、需要哪個方案、官方設定片段怎麼填,並附上只接信任來源、唯讀優先、提示詞注入與金鑰保管的安全檢查,內容以 2026 年 9 月 15 日官方文件為準。閱讀全文
同主題延伸閱讀
生活分享
Claude Code、Codex 搭本機模型:兩種接法怎麼選
Claude Code 與 Codex 搭配本機模型有兩種接法:代理照常連雲端、把大量雜務交給腳本或 MCP 工具去問本機模型,或是把代理的模型整個換成本機模型。這篇用資料能不能出門、上下文開得夠不夠長、工作的類型三個問題幫你選,並對照 Ollama、LM Studio、Anthropic 與 OpenAI 的官方文件,分清楚本機權重、Ollama 的 cloud 標籤與供應商端點是三種不同的東西。
引用本文的文章
最新旅遊情報攻略

情報
2026 韓國楓葉預測:雪嶽山 10 月 20 日、首爾近郊 10 月底、內藏山與漢拏山 11 月上旬
韓國山林廳 2026 年 9 月 22 日公布的楓紅高峰預測:雪嶽山 10 月 20 日,春川、國立樹木園到首爾植物園落在 10 月 28 日到 11 月 2 日,內藏山 11 月 4 日、漢拏山 11 月 6 日,整體比最近 5 年晚約 0.8 天。整理各地楓樹與銀杏的預測日、首爾出發怎麼排,以及出發前去哪裡看即時楓況。2026 年 10 月查證。
- 季節活動
- 自然
- 觀景

攻略胡志明市
胡志明市到頭頓一日遊:白藤碼頭搭高速船、船票與班次,下船就是胡梅纜車與耶穌基督像
人在胡志明市挪一天去頭頓看海:市中心的白藤高速船碼頭搭船,航程 120 分鐘到頭頓的胡梅碼頭,平日成人 320,000 越南盾、週末 350,000,回程末班平日 15:00。下船就是胡梅纜車站,同一條路上有白宮,小山頂上是耶穌基督像。平日一天只有兩班船,整天要從末班船倒推著排。
- 交通
- 行程範例
- 海灘

攻略沖繩
沖繩不開車攻略:單軌只到浦添,美麗海水族館要坐兩個多小時的巴士,回那霸的最後一班直達車 17:22 就開走
不租車的沖繩怎麼移動:那霸市區靠沖繩都市單軌電車(ゆいレール),那霸機場站到終點てだこ浦西 19 站、17 公里、37 分鐘,一日券 1,000 日圓;美麗海水族館有那霸機場直達的高速巴士,單程 2,000 日圓起、官方時刻表上 2 小時上下,下車後還要走 10 分鐘;古宇利島要在今帰仁村役場轉車,當天來回光坐車就六個半小時;回程的最後一班直達車 17:22 就從記念公園前開走(2026 年 9 月查證)。
- 交通
- 行程範例
- 預算
資料來源
- What is the Model Context Protocol (MCP)? · 查證日期:
- MCP Python SDK (README) · 查證日期:
- Tools - MCP Python SDK · 查證日期:
- Running your server - MCP Python SDK · 查證日期:
- Connect Claude Code to tools via MCP · 查證日期:
- Model Context Protocol (Codex) · 查證日期:
- MCP servers with Gemini CLI · 查證日期: