使用 Docker Compose 部署 MCP 通道
使用 Docker Compose 在 VM 上安裝 MCP 通道堆疊。
本指南將通道堆疊以強化容器的形式部署在單一主機上。相同的設定可以複製到多台主機上以提高可用性。
開始之前
您需要:
- 一個 tunnel。 使用程式化存取時,若您未提供 tunnel ID,setup 元件會為您建立一個;若要改為連接到現有的 tunnel,請在 Claude Console 中建立並記下 tunnel ID(
tnl_...)。手動佈建一律從在 Console 中建立的 tunnel 開始。 - 讓主機向 Tunnels API 進行驗證的方式。
- 程式化存取(建議)。 建立通道時開啟 Set up programmatic access,讓設定元件可以透過 Workload Identity Federation 進行驗證。如果您讓設定元件建立通道,請改為直接在 Claude Console 的 Workload identity 頁面(
platform.claude.com/settings/workload-identity-federation)上建立聯合規則。記下聯合規則 ID(fdrl_...)和您的組織 ID。 - 手動。 略過程式化存取。您將從 Console 取得通道權杖、自行產生 CA 和伺服器憑證,並在 Console 中註冊 CA。
- 程式化存取(建議)。 建立通道時開啟 Set up programmatic access,讓設定元件可以透過 Workload Identity Federation 進行驗證。如果您讓設定元件建立通道,請改為直接在 Claude Console 的 Workload identity 頁面(
- 已安裝 Docker 和 Docker Compose 的主機。手動流程還需要
openssl(1.1.1 或更新版本)。 - 從主機到
api.anthropic.com(443 TCP)和通道邊緣(7844 TCP 和 UDP)的對外網路連線。請參閱完整的網路需求。 - 一個或多個 MCP 伺服器正在執行,且主機可透過您將在
routes下設定的位址連線到它們。如果您還沒有,請使用範例伺服器。
選用:使用範例 MCP 伺服器
如果您沒有可用於測試的 MCP 伺服器,請使用這個最精簡的伺服器:
mkdir -p mcp-tunnel
cat > mcp-tunnel/hello_server.py <<'EOF'
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("hello-server", host="0.0.0.0", port=9000)
@mcp.tool()
def hello(name: str = "world") -> str:
"""Say hello to someone."""
return f"Hello, {name}!"
if __name__ == "__main__":
mcp.run(transport="streamable-http")
EOF以下的安裝步驟會 cd 進入 mcp-tunnel/,並註明在何處新增對應的服務和路由。
安裝
本指南提供一種使用 Docker Compose 的參考做法。您有責任調整它以符合您組織的安全需求。
此路徑要求主機具有 OIDC 身分提供者(例如雲端 VM 中繼資料伺服器或 SPIFFE)。如果沒有,請改用 不使用程式化存取 分頁。
設定元件使用 Workload Identity Federation 來擷取通道權杖、產生 CA 和伺服器憑證,並向 Anthropic 註冊 CA。
準備部署目錄
mkdir -p mcp-tunnel/{config,data} cd mcp-tunnel sudo chown 65532:65532 data容器以非 root 的 UID
65532執行,並需要data/的寫入權限。撰寫 docker-compose.yaml
此 compose 檔案以 SHA-256 摘要固定映像檔,以非 root 身分和唯讀檔案系統執行每個容器,移除所有 Linux capabilities,並停用權限提升。
cat > docker-compose.yaml <<'EOF' services: setup: image: us-docker.pkg.dev/anthropic-public-registry/images/mcp-proxy@sha256:d243d30642d39857a0579e465cb3b9bf3ae7c1141e83143bb6856777a31467a5 entrypoint: ["/setup"] command: - init - --api-url=https://api.anthropic.com - --output=dir:/data - --token-version=1 environment: - TUNNEL_ID - ANTHROPIC_FEDERATION_RULE_ID - ANTHROPIC_ORGANIZATION_ID - ANTHROPIC_WORKSPACE_ID - ANTHROPIC_IDENTITY_TOKEN volumes: - ./data:/data user: "65532:65532" read_only: true security_opt: - no-new-privileges:true cap_drop: - ALL profiles: ["setup"] cloudflared: image: cloudflare/cloudflared@sha256:0e56c13c7058fcef9c4f0e7d83372f82330548dbf8f8b366f3d8b506526d93b2 command: tunnel --no-autoupdate run --url http://localhost:8080 environment: - TUNNEL_TOKEN # 共用 proxy 的 netns,讓 localhost:8080 能連到它。 network_mode: "service:mcp-proxy" restart: unless-stopped user: "65532:65532" read_only: true security_opt: - no-new-privileges:true cap_drop: - ALL stop_grace_period: 30s logging: options: max-size: "10m" max-file: "3" mcp-proxy: image: us-docker.pkg.dev/anthropic-public-registry/images/mcp-proxy@sha256:d243d30642d39857a0579e465cb3b9bf3ae7c1141e83143bb6856777a31467a5 volumes: - ./config/mcp-proxy.yaml:/etc/mcp-gateway/config.yaml:ro - ./data:/data:ro restart: unless-stopped user: "65532:65532" read_only: true security_opt: - no-new-privileges:true cap_drop: - ALL stop_grace_period: 30s logging: options: max-size: "10m" max-file: "3" EOF如果您使用的是範例 MCP 伺服器,請將其附加為一個服務:
cat >> docker-compose.yaml <<'EOF' hello-mcp: image: python:3.13-slim working_dir: /app volumes: - ./hello_server.py:/app/hello_server.py:ro command: sh -c "pip install --quiet mcp && python hello_server.py" restart: unless-stopped EOF佈建通道
設定識別碼。不設定
TUNNEL_ID可讓設定元件建立通道;設定它則可連接到來自 Console 的現有通道:# export TUNNEL_ID=tnl_... # 設定此值以連接至現有的 tunnel export ANTHROPIC_FEDERATION_RULE_ID=fdrl_... export ANTHROPIC_ORGANIZATION_ID=00000000-0000-0000-0000-000000000000如果您的聯合規則範圍限定在組織預設工作區以外的工作區,請同時設定
ANTHROPIC_WORKSPACE_ID=wrkspc_...;否則設定元件會使用預設工作區。自動建立的通道會建立在該工作區中。將
ANTHROPIC_IDENTITY_TOKEN設定為來自此主機身分提供者的 OIDC JWT。請依照適用於您提供者的 WIF 指南註冊簽發者、設定規則的主體並產生權杖;規則的受眾必須與您產生權杖時請求的受眾相符。執行設定元件:
docker compose run --rm setupsetup init對data/具有冪等性:重新執行時會重複使用已儲存在其中的通道 ID 和 CA,且絕不會建立第二個通道。只有在data/為空或TUNNEL_ID已變更時,才會產生並註冊新的 CA;在這種情況下,最多兩個有效憑證的上限會適用,因此如果兩個位置都已佔用,請先在 Console 中撤銷其中一個。如果發生錯誤,請參閱設定元件驗證失敗。
擷取您的通道網域並將其匯出以供後續步驟使用:
export TUNNEL_DOMAIN=$(sudo cat data/tunnel-domain) echo "$TUNNEL_DOMAIN"撰寫代理設定
tunnel_domain為必填:代理會使用它從傳入的主機名稱中移除網域後綴,然後在routes中查詢子網域。routes是從子網域到上游 URL 的扁平對應,而不是清單。cat > config/mcp-proxy.yaml <<EOF listen_addr: ":8080" log_level: info shutdown_timeout: 30s tunnel_domain: ${TUNNEL_DOMAIN} tls: cert_file: /data/tls.crt key_file: /data/tls.key routes: echo: http://hello-mcp:9000 EOFecho:路由指向範例 MCP 伺服器;請將其替換為(或新增)您自己的路由。如需所有可用欄位,請參閱代理設定參考資料。啟動部署
export TUNNEL_TOKEN=$(sudo cat data/tunnel-token) docker compose up -d
如果您沒有開啟 Set up programmatic access,或用於本機開發和測試,請使用此流程。此流程沒有 setup 服務。
從 Console 取得通道權杖和網域
在通道詳細資料頁面上,複製 Domain(格式為
abcd1234.tunnel.anthropic.com),然後點擊 Token 旁的眼睛圖示以擷取通道權杖,並使用複製圖示複製它。將兩者設定為 shell 變數,以供本指南其餘部分使用:
export TUNNEL_DOMAIN=YOUR_TUNNEL_DOMAIN_HERE export TUNNEL_TOKEN='eyJ...'建立架構並產生憑證
mkdir -p mcp-tunnel/{data,config} cd mcp-tunnelproxy 透過純 WebSocket 在
:8080上監聽;內層 TLS 交握會使用這些憑證在該 WebSocket 串流內部進行。Anthropic 會根據您在 Console 中註冊的 CA 驗證內層交握。伺服器憑證的 Subject Alternative Name(SAN)必須包含*.<tunnel-domain>,如憑證需求中所述。# 自簽 CA。明確指定擴充欄位,使其符合憑證 # 要求,不受各發行版 openssl.cnf 預設值影響。 openssl req -x509 -newkey rsa:2048 -nodes \ -keyout data/ca.key -out data/ca.crt \ -days 3650 -subj "/CN=mcp-tunnel-ca" \ -addext "basicConstraints=critical,CA:TRUE" \ -addext "keyUsage=critical,keyCertSign,cRLSign" \ -addext "subjectKeyIdentifier=hash" # 伺服器憑證的擴充欄位檔案。使用 -extfile(而非 # 僅 OpenSSL 3.0+ 支援的 -copy_extensions)可確保在 # OpenSSL 1.1.x 上也能運作。 cat > data/tls.ext <<EOF subjectAltName = DNS:${TUNNEL_DOMAIN},DNS:*.${TUNNEL_DOMAIN} authorityKeyIdentifier = keyid,issuer extendedKeyUsage = serverAuth EOF # 由 CA 簽署的伺服器憑證 openssl req -newkey rsa:2048 -nodes \ -keyout data/tls.key -out /tmp/server.csr \ -subj "/CN=${TUNNEL_DOMAIN}" openssl x509 -req -in /tmp/server.csr \ -CA data/ca.crt -CAkey data/ca.key -CAcreateserial \ -out data/tls.crt -days 90 \ -extfile data/tls.ext # 允許非 root 的 proxy 容器(UID 65532)從 bind mount # 讀取金鑰。若無 world-read 權限位元,容器將無法開啟 # 主機擁有的檔案。 chmod 644 data/tls.key在 Console 中註冊 CA 憑證
在 tunnel 詳細資料頁面上,捲動到 Certificates 區段並點擊 Add certificate。使用 Choose file 直接上傳
data/ca.crt(此對話視窗接受.pem、.crt和.cer),或貼上其內容:cat data/ca.crt註冊憑證後,通道名稱旁的 Needs certificate 標記就會消失。請參閱新增 CA 憑證。
撰寫代理設定
tunnel_domain為必填:代理會使用它從傳入的主機名稱中移除網域後綴,然後在routes中查詢子網域。routes是從子網域到上游 URL 的扁平對應,而不是清單。cat > config/mcp-proxy.yaml <<EOF listen_addr: ":8080" log_level: info tunnel_domain: ${TUNNEL_DOMAIN} tls: cert_file: /data/tls.crt key_file: /data/tls.key routes: echo: http://hello-mcp:9000 EOFecho:路由指向範例 MCP 伺服器;請將其替換為(或新增)您自己的路由。如需所有可用欄位,請參閱代理設定參考資料。撰寫 docker-compose.yaml
network_mode: "service:mcp-proxy"設定會將 cloudflared 放在代理的網路命名空間中,使 cloudflared 容器內的localhost:8080能連線到代理。--url http://localhost:8080旗標為 cloudflared 提供轉送目標;若沒有此旗標,cloudflared 將沒有傳入請求的路由,並會傳回 503。cat > docker-compose.yaml <<'EOF' services: cloudflared: image: cloudflare/cloudflared@sha256:0e56c13c7058fcef9c4f0e7d83372f82330548dbf8f8b366f3d8b506526d93b2 # --url 為必要參數:手動流程不會推送任何 ingress 規則, # 因此若未指定,cloudflared 會對每個請求回傳 503。 command: tunnel --no-autoupdate run --url http://localhost:8080 environment: - TUNNEL_TOKEN # 共用 proxy 的 netns,讓 localhost:8080 能連到它。 network_mode: "service:mcp-proxy" restart: unless-stopped user: "65532:65532" read_only: true security_opt: - no-new-privileges:true cap_drop: - ALL stop_grace_period: 30s logging: options: max-size: "10m" max-file: "3" mcp-proxy: image: us-docker.pkg.dev/anthropic-public-registry/images/mcp-proxy@sha256:d243d30642d39857a0579e465cb3b9bf3ae7c1141e83143bb6856777a31467a5 volumes: - ./config/mcp-proxy.yaml:/etc/mcp-gateway/config.yaml:ro - ./data:/data:ro restart: unless-stopped user: "65532:65532" read_only: true security_opt: - no-new-privileges:true cap_drop: - ALL stop_grace_period: 30s logging: options: max-size: "10m" max-file: "3" EOF如果您使用的是範例 MCP 伺服器,請將其附加為一個服務:
cat >> docker-compose.yaml <<'EOF' hello-mcp: image: python:3.13-slim working_dir: /app volumes: - ./hello_server.py:/app/hello_server.py:ro command: sh -c "pip install --quiet mcp && python hello_server.py" restart: unless-stopped EOF啟動部署
docker compose up -d
compose 檔案會從主機環境讀取 TUNNEL_TOKEN 且沒有預設值,因此您必須在每個新的 shell 中以及重新開機後重複執行匯出。
若要進行多 VM 部署,請將 mcp-tunnel/ 目錄複製到每台主機,設定 TUNNEL_TOKEN,然後執行 docker compose up -d。在程式化流程中,TUNNEL_TOKEN 為 $(sudo cat data/tunnel-token);在手動流程中,則是您從 Console 複製的值。相同的通道權杖和憑證可在所有副本上使用。
驗證部署
從 Anthropic 端呼叫上游 MCP 伺服器以進行端對端驗證:請參閱使用透過通道連線的 MCP 伺服器。使用範例 MCP 伺服器時,路由後的 URL 為 https://echo.<your-tunnel-domain>/mcp。如果驗證失敗,請參閱疑難排解。
升級
請在 mcp-tunnel/ 部署目錄內執行本節中的命令。
輪替通道權杖
使用程式化存取時,請遞增 setup 服務命令中的 --token-version,設定 Workload Identity Federation 識別碼,產生新的 OIDC JWT,然後重新執行設定元件:
# 編輯 docker-compose.yaml:將 setup 服務的
# --token-version 引數中的整數遞增(例如從 --token-version=1 改為
# --token-version=2)。若該值未變更,setup 二進位檔將拒絕輪替
# 。
# export TUNNEL_ID=tnl_... # 僅在您安裝時有設定才需設定
export ANTHROPIC_FEDERATION_RULE_ID=fdrl_...
export ANTHROPIC_ORGANIZATION_ID=00000000-0000-0000-0000-000000000000
# export ANTHROPIC_WORKSPACE_ID=wrkspc_... # 若您的規則限定於工作區範圍
# 請依照您環境適用的 WIF 提供者指南重新產生 ANTHROPIC_IDENTITY_TOKEN
# (自安裝以來該權杖應已過期)。
export ANTHROPIC_IDENTITY_TOKEN=...
docker compose run --rm setup
export TUNNEL_TOKEN=$(sudo cat data/tunnel-token)
docker compose up -d cloudflared--token-version 引數是在 docker-compose.yaml 中編輯,而不是在命令列上傳遞,這樣新值才會在設定元件日後執行時保留。設定元件使用 Workload Identity Federation 進行驗證;沒有需要撤銷的 API 權杖。
不使用程式化存取時,請在 Console 的通道詳細資料頁面上點擊 Rotate token,然後更新每台主機上的 TUNNEL_TOKEN 環境變數並重新啟動 cloudflared(docker compose up -d cloudflared)。
憑證更新
您有責任監控到期時間,並在伺服器憑證到期前更新它。
使用程式化存取時:
docker compose run --rm setup renew-cert --output=dir:/dataCLI 引數會取代 setup 服務的 command(即 init 引數),但保留其 entrypoint,因此這會執行 /setup renew-cert --output=dir:/data。
不使用程式化存取時,請使用您現有的 CA(在 Console 中註冊的 CA 不會變更)簽署新的伺服器憑證,並替換 data/tls.crt。如果您是從新的 shell 執行此操作,請先設定 TUNNEL_DOMAIN。
export TUNNEL_DOMAIN=YOUR_TUNNEL_DOMAIN_HERE
openssl req -new -key data/tls.key -out /tmp/server.csr \
-subj "/CN=${TUNNEL_DOMAIN}"
openssl x509 -req -in /tmp/server.csr \
-CA data/ca.crt -CAkey data/ca.key -CAcreateserial \
-out data/tls.crt -days 90 \
-extfile data/tls.ext在任一流程中,代理都會輪詢 tls.cert_file 並自動重新載入,因此無需重新啟動。
後續步驟
Was this page helpful?