MCP 剛出來的時候,這個問題根本不存在。

那時候一個 server 就是你本機起的一支 process。你在設定檔裡寫 npx some-mcp-server,Claude Code 把它 spawn 起來,兩邊用 stdin 跟 stdout 講話。認證?不需要。那支 process 跑在你的帳號底下,讀你的環境變數,能碰到的東西跟你自己在終端機裡能碰到的完全一樣。

問題是從「有些 server 不可能跑在你電腦上」開始的。

第一階段:遠端來了,認證用貼的

Sentry 不會讓你在本機起一份他們的 MCP server,Notion 也不會。這些服務的資料在他們的雲上,server 必須跑在他們那邊,你的 Claude Code 只能透過網路連過去。

於是 transport 多了遠端的選項,最早進來的是 SSE(Server-Sent Events)。連得上之後,下一個問題馬上出現:對方怎麼知道你是誰。

那個階段的標準做法是自己去服務商後台產一組 API token,然後貼進設定:

1
2
claude mcp add --transport http corridor https://app.corridor.dev/api/mcp \
--header "Authorization: Bearer ..."

這招現在還能用,官方 claude mcp add --help 的範例裡就有這一行。但它有個沒被講明的代價。

想像你要請人幫你收包裹,於是把家裡大門鑰匙打了一把給他。他確實能收包裹了,但他也能進你臥室。你想收回這個權限,唯一的方法是換門鎖,而換鎖會影響所有人。

手貼的 Bearer token 就是那把複製鑰匙。它通常沒有有效期限、範圍是整個帳號、要撤銷得回服務商後台把整組金鑰廢掉。更麻煩的是它會躺在你的設定檔裡,而設定檔常常被不小心 commit 進 repo。

第二階段:換成房卡

OAuth 解決的就是這件事,而它的心智模型不是鑰匙,是飯店櫃檯給你的房卡。

你在櫃檯出示證件,櫃檯發一張卡給你。這張卡只開你那間房、只在退房前有效、掉了打電話掛失就好,飯店不用換門鎖。發卡的是飯店,不是你,所以飯店隨時知道有幾張卡在外面、分別開哪扇門。

Claude Code 支援 OAuth 2.0,觸發點很機械:當一個遠端 server 回 401 Unauthorized403 Forbidden,Claude Code 就把它標記成需要認證,在 /mcp 面板裡列出來讓你去登入。

官方文件的標準流程是兩步。先加 server:

1
claude mcp add --transport http sentry https://mcp.sentry.dev/mcp

然後在 session 裡打 /mcp,跟著瀏覽器把登入走完。token 存起來之後會自動續期,要撤銷就在 /mcp 選單裡選 Clear authentication。

這裡有個容易踩的互斥關係值得記住:如果你已經自己設了 headers.Authorization,而 server 拒絕那個 header,Claude Code 會直接報連線失敗,不會退回去走 OAuth。它不會替你猜你其實想換一條路。要用 OAuth 就要把那個 header 拿掉。

第三階段:把登入搬出 session

第二階段有個彆扭的地方:登入這件事只能在 Claude Code 的互動 session 裡做。你得先開一個 session、打 /mcp、在面板裡操作。這對「我只是想把環境設好」的情境來說是繞路。

從 v2.1.186 開始,claude mcp login <name> 可以直接在 shell 裡跑完 OAuth:

1
claude mcp login sentry

清掉憑證則是 claude mcp logout sentry

我在自己這台(Claude Code 2.1.220)跑 claude mcp login --help,輸出是這樣:

1
2
3
4
5
6
7
8
Usage: claude mcp login [options] <name>

Authenticate with an MCP server (HTTP, SSE, or claude.ai connector)

Options:
-h, --help Display help for command
--no-browser Print the authorization URL instead of opening a browser (for
SSH/headless sessions — paste the redirect URL back when prompted)

--no-browser 這個旗標是給第四階段用的。

第四階段:SSH 進去的那台機器沒有瀏覽器

OAuth 的流程天生假設你面前有一個瀏覽器。你 SSH 進一台 Linux server,那上面沒有 display server,開不了瀏覽器,整個流程就卡在第一步。

v2.1.191 補了這個洞。這個版本開始,指令會自己偵測本機有沒有可用的瀏覽器,沒有的話就改成印出授權 URL。你把那串 URL 複製到自己筆電的瀏覽器打開,登入完之後,從網址列把完整的 redirect URL 整段複製回來,貼進提示框。

官方文件在這裡有一句很實際的提醒:貼上這個動作需要互動式終端機,所以連線要用 ssh -t。少了那個 -t,你會走到最後一步才發現沒地方貼。

如果本機明明有瀏覽器但你就是不想開,加 --no-browser 強制走 URL 那條路。

第五階段:一連串補洞

從這裡開始的更新沒有一個算新功能,全都在修「OAuth 用久了會遇到的那些煩人狀況」。照時序排出來,可以看出他們在追什麼問題。

v2.1.193:啟動時會直接通知你有幾個 server 需要登入,不用自己開 /mcp 才發現。到了 v2.1.218,這個通知只計算「你真的能從 Claude Code 登入」的 server,先前它會把那些只能在 claude.ai 網站上連的 connector 也算進來,讓人白跑一趟。

v2.1.195:refresh token 被 server 拒絕的時候,會立刻跳提示指向 /mcp,選單裡多了 Re-authenticate。在這之前你只會在下一次呼叫工具失敗時才知道憑證過期了。

v2.1.196:非互動模式(claude -p 或 Agent SDK)沒有 /mcp 面板,跑不了 OAuth。這個版本開始,Claude 會被告知「這個 server 的工具目前不可用,要先授權」,於是它能明確講出是哪個 server 需要登入,而不是表現得像那個 server 根本沒設定過。

v2.1.206:修掉一個很惡劣的行為。在這之前,只要 token refresh 因為暫時性原因失敗(例如網路斷一下),那個 server 就會被標記成「需要認證」直到 session 結束,即使它的 refresh token 其實還有效。現在只有在 refresh、重連、重試一次都失敗之後才會標記。

這五條放在一起看,講的是同一件事:自動續期這個機制本身很好,但它失敗的時候要讓人知道發生什麼、並且要能分辨「憑證真的死了」跟「網路抖了一下」。

兩個進階旗標,什麼時候會用到

大部分 server 支援 Dynamic Client Registration,也就是 Claude Code 自己去跟對方註冊一個 OAuth client,你什麼都不用準備。但有些企業內部的 server 不吃這套。

第一種狀況是對方要求固定的 redirect URI。Claude Code 預設會隨機挑一個可用的 port 當 OAuth callback,而對方後台登記的是寫死的那一個,對不上就失敗。解法是 --callback-port

1
2
3
claude mcp add --transport http \
--callback-port 8080 \
my-server https://mcp.example.com/mcp

登記的 redirect URI 格式是 http://localhost:PORT/callback,port 要跟這裡填的一致。

第二種狀況是對方根本不支援 Dynamic Client Registration,你會看到這個錯誤:Incompatible auth server: does not support dynamic client registration

這裡先別急著自己開 app。有些 server 走的是 Client ID Metadata Document(CIMD)而不是 DCR,這種 Claude Code 會自動探索,你什麼都不用做。只有連 CIMD 都探索失敗,才需要自己去服務商的開發者後台開一個 OAuth app,拿到 client ID 跟 secret 帶進來:

1
2
3
4
claude mcp add --transport http \
--client-id YOUR_CLIENT_ID --client-secret \
--callback-port 8080 \
my-server https://mcp.example.com/mcp

--client-secret 不吃參數值,它會跳出遮蔽輸入的提示讓你打,所以 secret 不會留在指令列上。很多工具在這裡就直接讓你把它寫進參數了。

CI 環境沒辦法互動輸入,官方給的路是走環境變數:

1
2
3
MCP_CLIENT_SECRET=your-secret claude mcp add --transport http \
--client-id your-client-id --client-secret --callback-port 8080 \
my-server https://mcp.example.com/mcp

這裡要分清楚一件事:這個環境變數只是幫你跳過「輸入 client secret」那個提示,不等於 CI 能自己跑完 OAuth。使用者授權那一步還是要有人在瀏覽器上點同意。這個區別在最後一段還會再出現。

順帶一提,Claude Code 找 OAuth 授權伺服器的順序是先看 RFC 9728 的 /.well-known/oauth-protected-resource,找不到才 fallback 到 RFC 8414 的 /.well-known/oauth-authorization-server。內部 server 走 proxy 導致這兩個都對不上的話,可以用 authServerMetadataUrl 直接指定。

順便講清楚 transport 到底有幾種

這件事我自己寫錯過,所以講明白一點。

claude mcp add --transport 這個旗標接受的值只有三個。本機 --help 原文:

1
2
-t, --transport <transport>  Transport type (stdio, sse, http). Defaults to
stdio if not specified.

stdio 是本機起 process,http 是現在推薦的遠端做法,sse 已經被標記為 deprecated,官方原話是 “The SSE (Server-Sent Events) transport is deprecated. Use HTTP servers instead, where available.”

但設定檔那一層還多一種。.mcp.jsonclaude mcp add-jsontype 欄位可以寫 ws

1
2
claude mcp add-json events-server \
'{"type":"ws","url":"wss://mcp.example.com/socket","headers":{"Authorization":"Bearer YOUR_TOKEN"}}'

官方對它的定位寫得很直白:WebSocket 保持雙向長連線,適合那種會主動推事件給你的 server;但如果對方只是被動回應請求,用 HTTP 就好,因為「HTTP 支援 OAuth 跟 claude mcp add --transport 旗標,WebSocket 兩個都不支援」。

所以 ws 的認證只能走 header,回到第一階段那把複製鑰匙。要嘛在 headers 裡塞一個靜態 token,要嘛用 headersHelper 在連線當下現產一個。另外 WebSocket server 不會出現在 claude mcp list 的輸出裡,要用 claude mcp get <name>/mcp 才看得到。

要記的話就記這句:旗標三種,設定檔四種,能走 OAuth 的只有 HTTP。

最後決定你要不要共用

加 server 的時候有個 -s 旗標決定這份設定放哪裡,三個值:

local 是預設,只有你自己、只在這個專案有效,寫進 ~/.claude.json 裡對應這個專案的區塊。project 會寫進 repo 裡的 .mcp.json,團隊每個人都拿得到。user 是跨專案,你自己所有專案都能用。

注意 OAuth 憑證跟 server 設定是分開存的。.mcp.json 分享出去的是「這裡有一台 server」,不是你的登入狀態,隊友 clone 下來還是要自己跑一次 claude mcp login。這個切分是對的,但第一次遇到會有人以為設定好了卻連不上。

還沒補上的那個洞

寫到這裡,這條線看起來挺完整了:登入可以在 shell 跑、SSH 有解、憑證會自動續期、失敗會提示、企業 server 有旗標可以喬。

但非互動模式那個洞還在。claude -p 跑不了 OAuth,v2.1.196 做的是「讓 Claude 知道並講出來」,不是「讓它能自己登入」。也就是說,任何排程、CI、無人值守的自動化,只要用到需要 OAuth 的遠端 server,都必須有一次人類回到互動 session 完成登入。

這在單機上不是問題,你設定一次就好。但如果你的 agent 跑在會被重建的容器裡,或者 refresh token 有效期比你的部署週期短,這件事就會週期性地咬你。目前官方給的路只有一條:回互動 session 跑 /mcpclaude mcp login <name>

至於那個要怎麼在完全無人的環境裡繞過去,我還沒找到讓自己滿意的答案。想到了再寫。


驗證說明:文中的指令、旗標與 --help 輸出,為本機 Claude Code 2.1.220 實跑 claude mcp add --help / claude mcp login --help / claude mcp --help 的結果;版本註記(v2.1.186、v2.1.191、v2.1.193、v2.1.195、v2.1.196、v2.1.206、v2.1.218)、OAuth discovery 順序、錯誤訊息原文與 WebSocket 段落,出自官方文件 Connect Claude Code to tools via MCP。我沒有實際跑完一次完整的 Sentry OAuth 授權流程,那部分的步驟描述以官方文件為準。另外官方文件網址已從 docs.claude.com/en/docs/claude-code/* 搬到 code.claude.com/docs/en/*,舊連結會 301 轉址。