MCP(Model Context Protocol)讓 Claude Code、Claude Desktop 這類 agent 用戶端,透過標準化的方式呼叫外部的工具與資料來源。最常見的用法是 stdio:用戶端把 MCP server 當子行程啟動,透過標準輸入輸出溝通。但如果你想把 MCP server 放在另一台機器上(例如家裡那台常開的 agent 主機),讓多台裝置共用,就要改用 streamable HTTP 傳輸——MCP server 跑成一個獨立的 HTTP 服務,用戶端用網址連過去。
問題是,一個能讀檔案、查資料庫、執行工具呼叫的 MCP server,絕對不能直接開在公網。這篇講怎麼把它放在 tailnet 裡:用 Tailscale Serve 只對 tailnet 開放,再依規模選 bearer token(簡單的靜態憑證)或 tsidp(OAuth authorization server,支援 DCR)。身分標頭與「為什麼後端要只聽 loopback」的基礎,先看 Serve 的身分標頭。
本文依 2026 年 10 月的 Tailscale、Anthropic、MCP 規格與 tsidp 官方文件整理,以 Tailscale v1.104 為準。MCP 的 streamable HTTP 傳輸以 2025-11-25 版規格、Authorization 以 draft 版規格為準;Claude Code 以核對時點的 v2.1.x 為準(官方 changelog 於 2026 年 9 月底已到 v2.1.283,更新頻繁);Claude Desktop 沒有單一的公開版本號(各平台 build 編號不一,例如 MacUpdate 列的 2.9939.2 與 Debian 套件的 1.52386.6),其設定以 2026 年 10 月核對的文件為準;tsidp 為實驗性專案(版本尚未滿 1.0.0)。本文未實機跑過整套設定,屬文件整理。
MCP 的 streamable HTTP 傳輸
MCP 規格目前定義了兩種標準傳輸:stdio(子行程、標準輸入輸出)與 streamable HTTP(獨立 HTTP 服務)。streamable HTTP 取代了舊的 HTTP+SSE 傳輸,特點是:
- MCP server 提供單一一個 HTTP 端點(例如
https://example.com/mcp),同時接受POST與GET。 - 用戶端用
POST送出 JSON-RPC 訊息,server 可以回傳單一 JSON,或開一條 SSE(Server-Sent Events)串流回傳多則訊息。 - 規格給實作者三條安全要求:驗證
Origin標頭(防 DNS rebinding)、本機執行時只綁127.0.0.1、以及對所有連線做驗證。
這三條正是「放在 tailnet」的切入點:把 server 綁在 loopback,再由 Serve 轉發出去,tailnet 上的其他人只能走 Serve 進來,不能直連後端;驗證則交給下一節的 bearer token 或 tsidp。
第一步:MCP server 只聽 loopback
和 Serve 的身分標頭 裡講的原則一樣,MCP server 要綁 127.0.0.1,不要綁 0.0.0.0。綁 loopback 之後,能直接碰到它的只剩同一台主機上的程式,tailnet 上的人都得經 Serve 轉發。
另外注意 Serve 的反向代理永遠轉給 http://127.0.0.1,所以 server 必須聽在 IPv4 的迴路位址,只綁 IPv6 的 [::1] 會被拒絕(細節見 Tailscale Serve)。你的 MCP server 若以 localhost 起,記得確認它實際綁的是 127.0.0.1。
第二步:用 Serve 發布
假設 MCP server 跑在本機 9933 連接埠(tsidp 官方範例也是用這個埠):
tailscale serve --bg http://127.0.0.1:9933之後 tailnet 內用 https://<你的主機>.tailxxxxx.ts.net 就能連到它。streamable HTTP 的 POST/GET 與 SSE 串流都是標準 HTTP,Serve 的 HTTP 代理可以正常轉發,不需要特別的旗標。想讓多個服務共用同一個主機名稱時,可以用 --set-path 把 MCP 端點掛在子路徑(例如 --set-path=/mcp),此時用戶端的網址就是 https://<你的主機>.tailxxxxx.ts.net/mcp。
第三步:設定 Claude Code
Claude Code 對遠端 MCP server 的官方做法是 HTTP 傳輸:
claude mcp add --transport http <名稱> <網址>以「有 bearer token」的 server 為例:
claude mcp add --transport http my-mcp https://mcp.tailxxxxx.ts.net \
--header "Authorization: Bearer <你的-token>"--transport http:這個 server 是放在網址上的 HTTP 服務,不是本機子行程。--header:把靜態憑證(bearer token)帶上,每次請求都會送出。- 不加
--scope時寫進本機專案;--scope user對所有專案生效,--scope project寫進可提交進版本控制的.mcp.json。
用 JSON 設定檔(.mcp.json、~/.claude.json,或 claude mcp add-json)時的寫法:
{
"mcpServers": {
"my-mcp": {
"type": "http",
"url": "https://mcp.tailxxxxx.ts.net",
"headers": {
"Authorization": "Bearer <你的-token>"
}
}
}
}兩個要點:type 必填,可以寫 http 或規格裡的 streamable-http(兩者等價);只有 url 沒有 type 會被當成 stdio server 而失敗。要確認連線狀態,用 claude mcp list,或在 Claude Code 裡開 /mcp 面板看。
如果你的 server 走 OAuth(下一節的 tsidp 就是),指令一樣,但不用帶 --header:
claude mcp add --transport http my-mcp https://mcp.tailxxxxx.ts.net加了之後 claude mcp list 會顯示 ! Needs authentication,接著在 Claude Code 裡開 /mcp 完成瀏覽器登入即可。
Claude Desktop 的遠端 MCP
Claude Desktop 的設定檔 claude_desktop_config.json(macOS 在 ~/Library/Application Support/Claude/)是給本機 stdio server 用的(command/args)。遠端 MCP server 走的是另一條路:Connectors UI——Settings → Connectors → Add → Add custom connector → 貼上網址,接著照提示完成驗證(通常是 OAuth)。
所以對 Claude Desktop 來說,放在 tailnet 的 MCP server 就貼上它的 Serve 網址,讓它對 tsidp 跑 OAuth 登入。要注意的是,Claude Desktop 的原生 connector 主要走 OAuth;如果你用的是純 bearer token(靜態憑證)驗證,Anthropic 官方把這種 static_headers 型標為 beta、只開放給部分組織。要用最直接、跨平台穩定的靜態憑證做法,Claude Code 的 --header 是更簡單的路線。
bearer token 與「為什麼不要 Funnel」
把上面的設定攤開看,其實是兩層防護疊在一起:
- 網路層:Serve 只對 tailnet 開放,公網連不到。這層由 Tailscale 把關。
- 應用層:bearer token,用戶端每次請求都帶
Authorization: Bearer <token>,server 驗證後才放行。token 怎麼設是每個 MCP server 各自定義的(可能是啟動旗標、環境變數或反代設定),要查該 server 自己的文件。
bearer token 適合小規模、只有你自己和少數信任裝置的情境。它把「誰能連」簡化成「誰有這把鑰匙」——鑰匙洩漏就要換,且沒有使用者身分、沒有權限細分。
為什麼不用 Funnel?Funnel 是把服務公開到整個網際網路,對一個能讀你檔案、執行工具呼叫的 MCP server 來說,這是完全錯誤的暴露等級。Funnel 不帶 tailnet 身分標頭,公開後你就得獨自承擔 MCP 規格裡警告的那些風險(DNS rebinding、惡意 Origin、暴力破解 token)。MCP server 除非是給外部 SaaS 呼叫,否則一律走 Serve。tsidp 自己也有 --funnel 旗標,但那只是為了讓跑在 tailnet 外的 SaaS 應用能用它登入,跟「把 MCP server 開出去」是兩回事。
更進一步:tsidp 當 OAuth authorization server(DCR)
bearer token 的「一把鑰匙」模型,在多人、多 agent、或需要轉發的中間架構下會不夠用。MCP 的 Authorization 規格(選用,HTTP 傳輸建議遵循)定義了一套 OAuth 2.1 流程:MCP server 當 resource server,用戶端當 OAuth client,再交給一個 authorization server 發 access token,之後每次請求帶 Authorization: Bearer <access-token>。
tsidp 就是 Tailscale 開源、跑在 tailnet 裡的 OIDC/OAuth 身分提供者。它用 tsnet 與 LocalAPI 的 whois 直接拿 tailnet 身分,宣稱支援 MCP Authorization 規格要求的端點,包括 DCR(Dynamic Client Registration,RFC 7591)。
DCR 解決的是「手動建立 OAuth client」的麻煩:用戶端第一次連線時,自動向 authorization server 的 /register 端點註冊自己、拿回 client ID。多數大型 IdP(Google、GitHub、Microsoft Entra ID)至今不支援 DCR,所以 MCP 這種大量動態註冊的情境,是 DCR 少見的實際應用場域。Tailscale 在這篇部落格說明瞭這個設計。
有一點要留意:MCP Authorization 的 draft 版規格已把 DCR(RFC 7591)標為 deprecated——保留給不支援 Client ID Metadata Documents 的 authorization server 做向後相容,長期方向是以 Client ID Metadata Documents 作為建議做法。所以 DCR 是「現階段 tsidp 已支援、能動」的機制,但不是規格長期首選,寫進產品規劃時要一併考慮。
tsidp 是實驗性專案,這是本文最重要的提醒:
- 官方文件與 README 都標示「experimental、actively developed、可能隨時有 breaking changes」。
- 跑起來需要設
TAILSCALE_USE_WIP_CODE=1,這個環境變數就是為了「版本還沒到 1.0.0」而設的門檻。 - 它屬於社群專案,官方不提供支援,有問題到 GitHub repo 發 issue。
- app capability 的 schema 仍在開發中,可能隨時改。
部署 tsidp 需要:MagicDNS、HTTPS 憑證、一把 auth key(或 OAuth client)讓節點註冊,以及一個 tailscale.com/cap/tsidp 的 app capability grant。預設 admin UI 與 DCR 端點都是拒絕存取的,要分別用 allow_admin_ui 與 allow_dcr 兩個欄位打開(見下一節的 grants 範例)。tsidp 需要持久化狀態(-dir/TS_STATE_DIR),無狀態部署每次重啟都會遺失 DCR 註冊的 client。
DCR 的運作方式,官方有 MCP client/server 與 MCP gateway 兩個範例可照著做。
最小權限的 grants 範例
MCP 的 OAuth 流程要求用戶端帶 resource indicator(RFC 8707),指定要存取哪個 MCP server 的 canonical URI;tsidp 會拿 grant 的 resources 欄位比對這個值,決定要不要發 token。而 DCR 端點由 allow_dcr 控制,兩者各自獨立——users/resources 只管「誰能為哪些資源拿到 token」,allow_dcr/allow_admin_ui 才是「誰能註冊 client/用後台」。
所以範例拆成三條 grant:成員能連 MCP server、成員能拿 token、只有管理員能 DCR 註冊:
"tagOwners": {
"tag:mcp-server": ["autogroup:admin"],
"tag:tsidp": ["autogroup:admin"],
},
"grants": [
// 成員可以連到 MCP server 的 Serve 443(後端原始埠不開放)
{
"src": ["autogroup:member"],
"dst": ["tag:mcp-server"],
"ip": ["tcp:443"],
},
// 成員可以為這個 MCP server 拿 access token(users 用 "*" 或實際 login 名稱)
{
"src": ["autogroup:member"],
"dst": ["tag:tsidp"],
"ip": ["tcp:443"],
"app": {
"tailscale.com/cap/tsidp": [
{
"users": ["*"],
"resources": ["https://mcp.tailxxxxx.ts.net"],
},
],
},
},
// 只有管理員能透過 DCR 註冊 client(要後台管理就一併開 allow_admin_ui)
{
"src": ["autogroup:admin"],
"dst": ["tag:tsidp"],
"ip": ["tcp:443"],
"app": {
"tailscale.com/cap/tsidp": [
{
"allow_dcr": true,
"allow_admin_ui": true,
},
],
},
},
],欄位語意(對照 tsidp 原始碼 server/clients.go、server/token.go、server/appcap.go):
-
allow_dcr:DCR 端點(RFC 7591 的/register)只看這個布林值。clients.go的serveDynamicClientRegistration只檢查access.allowDCR,與users/resources無關——所以它要獨立放進「管理員」那條,放進「成員」那條等於每個成員都能註冊 client。 -
users/resources:只在 token 發放時用。token.go的validateResourcesForUser比對users是否含"*"或使用者的 login 名稱、resources是否含請求的 resource。users不支援 autogroup——寫autogroup:admin永遠不會 match,要嘛用"*",要嘛用實際登入名稱(email)。 -
多條 grant 的 app capability 會累積合併:
appcap.go的addGrantAccessContext把每條 rule 的allowAdminUI/allowDCROR 起來。所以管理員同時吃得到「成員」那條的users/resources與自己那條的allow_dcr。 -
MCP server 與 tsidp 各自打上 tag,身分不再綁在個人帳號上(見 Tags 與 auth key)。
-
上面
users用"*"表示「tailnet 內任何成員都能為這個 MCP server 拿 token」;要更收緊就換成具體的 login 名稱(例如alice@example.com)。 -
如果 tsidp 要當「gateway 中間層」做 token exchange(RFC 8693),才需要額外開
TSIDP_ENABLE_STS=1;單純的 client ↔ server 流程用不到。 -
tsidp 的 app capability schema 仍在開發中,欄位可能變動,部署前以當時的 README 為準。規則寫法見 ACL / Grants 入門。
小結
| 驗證方式 | 適用情境 | 把關層 |
|---|---|---|
| Serve(網路層) | 所有情境,一律要做 | 只有 tailnet 內 |
| bearer token | 小規模、自己用 | 應用層靜態憑證 |
| tsidp(OAuth) | 多人、多 agent、需要身分與轉發 | OAuth 2.1 + DCR |
無論選哪一種,骨架都一樣:MCP server 綁 loopback → Serve 只開 tailnet → 應用層再補驗證。bearer token 是把事情做小的選擇,tsidp 是往標準 OAuth 走的選擇,但後者還掛著「實驗性」的牌子,要放進正式環境前先評估那份 breaking changes 的風險。
參考資料
- MCP 規格:Transports(streamable HTTP,2025-11-25)
- MCP 規格:Authorization(draft)
- Connect Claude Code to tools via MCP(Anthropic 官方文件)
- Connect to remote MCP Servers(modelcontextprotocol.io,Claude Desktop 的 Connectors)
- Authentication for connectors(Anthropic,OAuth 與 static_headers)
- tsidp(GitHub,實驗性、DCR 與範例)
- tsidp(Tailscale 官方文件)
- tsidp 原始碼:
server/appcap.go(app capability 欄位與累積合併) - tsidp 原始碼:
server/clients.go(DCR 只看allow_dcr) - tsidp 原始碼:
server/token.go(users/resources的 token 驗證) - Easy, Secure Dynamic Client Registration for MCP & AI Agents(Tailscale 部落格)
- Dynamic Client Registration:RFC 7591
- Tailscale Serve:在 tailnet 內分享本機服務
- Serve 的身分標頭:讓自架服務直接認得 tailnet 使用者
- OpenClaw/Hermes Agent 個人助理只開給自己:Serve 模式與身分驗證
- ACL / Grants 入門