Distribuire i tunnel MCP con Docker Compose
Installa lo stack dei tunnel MCP su una VM utilizzando Docker Compose.
Questa guida distribuisce lo stack del tunnel come container rafforzati su un singolo host. La stessa configurazione può essere replicata su più host per garantire la disponibilità.
Prima di iniziare
Ti servono:
- Un tunnel. Con l'accesso programmatico, il componente di setup ne crea uno per te quando non fornisci un ID tunnel; per collegarti invece a un tunnel esistente, crealo nella Claude Console e annota l'ID del tunnel (
tnl_...). Il provisioning manuale parte sempre da un tunnel creato nella Console. - Un modo per consentire all'host di autenticarsi alla Tunnels API.
- Accesso programmatico (consigliato). Attiva Set up programmatic access durante la creazione del tunnel, in modo che il componente di setup possa autenticarsi tramite Workload Identity Federation. Se lasci che sia il componente di setup a creare il tunnel, crea invece la regola di federazione direttamente nella pagina Workload identity (
platform.claude.com/settings/workload-identity-federation) nella Claude Console. Annota l'ID della regola di federazione (fdrl_...) e l'ID della tua organizzazione. - Manuale. Salta l'accesso programmatico. Dovrai ottenere il token del tunnel dalla Console, generare autonomamente una CA e un certificato del server e registrare la CA nella Console.
- Accesso programmatico (consigliato). Attiva Set up programmatic access durante la creazione del tunnel, in modo che il componente di setup possa autenticarsi tramite Workload Identity Federation. Se lasci che sia il componente di setup a creare il tunnel, crea invece la regola di federazione direttamente nella pagina Workload identity (
- Un host con Docker e Docker Compose installati. Il flusso manuale richiede anche
openssl(1.1.1 o successivo). - Connettività di rete in uscita dall'host verso
api.anthropic.com(443 TCP) e verso il tunnel edge (7844 TCP e UDP). Consulta i requisiti di rete completi. - Uno o più server MCP in esecuzione e raggiungibili dall'host agli indirizzi che configurerai in
routes. Se non ne hai ancora uno, usa il server di esempio.
Facoltativo: usa un server MCP di esempio
Se non hai un server MCP disponibile per i test, usa questo server minimale:
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")
EOFI passaggi di installazione seguenti eseguono cd in mcp-tunnel/ e indicano dove aggiungere il servizio e la route corrispondenti.
Installazione
Questa guida fornisce un approccio di riferimento basato su Docker Compose. Sei responsabile di adattarlo ai requisiti di sicurezza della tua organizzazione.
Questo percorso richiede che l'host disponga di un provider di identità OIDC (come un server di metadati di una VM cloud o SPIFFE). In caso contrario, usa invece la scheda Senza accesso programmatico.
Il componente di setup usa Workload Identity Federation per recuperare il token del tunnel, generare una CA e un certificato del server e registrare la CA presso Anthropic.
Prepara la directory di distribuzione
mkdir -p mcp-tunnel/{config,data} cd mcp-tunnel sudo chown 65532:65532 dataI container vengono eseguiti con l'UID non root
65532e necessitano dell'accesso in scrittura adata/.Scrivi docker-compose.yaml
Il file compose fissa le immagini tramite digest SHA-256, esegue ogni container come non root con un filesystem di sola lettura, rimuove tutte le capability Linux e disabilita l'escalation dei privilegi.
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 # Condividi il netns del proxy così che localhost:8080 lo raggiunga. 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" EOFSe stai usando il server MCP di esempio, aggiungilo come servizio:
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 EOFEsegui il provisioning del tunnel
Imposta gli identificatori. Lascia
TUNNEL_IDnon impostato affinché il componente di setup crei un tunnel; impostalo per collegarti a un tunnel esistente dalla Console:# export TUNNEL_ID=tnl_... # imposta per collegarti a un tunnel esistente export ANTHROPIC_FEDERATION_RULE_ID=fdrl_... export ANTHROPIC_ORGANIZATION_ID=00000000-0000-0000-0000-000000000000Se la tua regola di federazione è limitata a un workspace diverso da quello predefinito della tua organizzazione, imposta anche
ANTHROPIC_WORKSPACE_ID=wrkspc_...; in caso contrario, il componente di setup usa il workspace predefinito. Un tunnel creato automaticamente viene creato in quel workspace.Imposta
ANTHROPIC_IDENTITY_TOKENsu un JWT OIDC emesso dal provider di identità di questo host. Segui la guida WIF per il tuo provider per registrare l'issuer, impostare il subject della regola e generare il token; l'audience della regola deve corrispondere all'audience che richiedi durante la generazione.Esegui il componente di setup:
docker compose run --rm setupsetup initè idempotente rispetto adata/: eseguirlo di nuovo riutilizza l'ID del tunnel e la CA già memorizzati lì e non crea mai un secondo tunnel. Una nuova CA viene generata e registrata solo quandodata/è vuota oTUNNEL_IDè cambiato; in tal caso si applica il limite di due certificati attivi, quindi revocane prima uno nella Console se entrambi gli slot sono occupati.Consulta Errori di autenticazione del componente di setup se si verificano errori.
Recupera il dominio del tunnel ed esportalo per i passaggi successivi:
export TUNNEL_DOMAIN=$(sudo cat data/tunnel-domain) echo "$TUNNEL_DOMAIN"Scrivi la configurazione del proxy
tunnel_domainè obbligatorio: il proxy lo usa per rimuovere il suffisso del dominio dagli hostname in ingresso prima di cercare il sottodominio inroutes.routesè una mappa piatta da sottodominio a URL upstream, non una lista.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 EOFLa route
echo:punta al server MCP di esempio; sostituiscila con le tue route (o aggiungile). Consulta il riferimento sulla configurazione del proxy per tutti i campi disponibili.Avvia la distribuzione
export TUNNEL_TOKEN=$(sudo cat data/tunnel-token) docker compose up -d
Usa questo flusso se non hai attivato Set up programmatic access, oppure per lo sviluppo e i test in locale. Non è presente alcun servizio setup.
Ottieni il token e il dominio del tunnel dalla Console
Nella pagina dei dettagli del tunnel, copia il Domain (ha la forma
abcd1234.tunnel.anthropic.com), quindi fai clic sull'icona a forma di occhio accanto a Token per recuperare il token del tunnel e usa l'icona di copia per copiarlo.Imposta entrambi come variabili di shell per il resto della guida:
export TUNNEL_DOMAIN=YOUR_TUNNEL_DOMAIN_HERE export TUNNEL_TOKEN='eyJ...'Crea la struttura e genera i certificati
mkdir -p mcp-tunnel/{data,config} cd mcp-tunnelIl proxy è in ascolto su
:8080tramite WebSocket semplice; l'handshake del TLS interno avviene all'interno di quel flusso WebSocket utilizzando questi certificati. Anthropic verifica l'handshake interno rispetto alla CA che registri nella Console. Il Subject Alternative Name (SAN) del certificato server deve includere*.<tunnel-domain>come descritto nei requisiti dei certificati.# CA autofirmata. Estensioni esplicite affinché soddisfi i requisiti del certificato # indipendentemente dai valori predefiniti di openssl.cnf della distro. 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" # File di estensioni per il certificato del server. Usare -extfile (invece di # -copy_extensions, disponibile solo da OpenSSL 3.0+) lo mantiene funzionante su # OpenSSL 1.1.x. cat > data/tls.ext <<EOF subjectAltName = DNS:${TUNNEL_DOMAIN},DNS:*.${TUNNEL_DOMAIN} authorityKeyIdentifier = keyid,issuer extendedKeyUsage = serverAuth EOF # Certificato del server firmato dalla 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 # Consenti al container proxy non root (UID 65532) di leggere la chiave dal # bind mount. Senza il bit di lettura per tutti, il container non può aprire # un file di proprietà dell'host. chmod 644 data/tls.keyRegistra il certificato CA nella Console
Nella pagina di dettaglio del tunnel, scorri fino alla sezione Certificates e fai clic su Add certificate. Carica
data/ca.crtdirettamente con Choose file (la finestra modale accetta.pem,.crte.cer), oppure incollane il contenuto:cat data/ca.crtIl badge Needs certificate accanto al nome del tunnel scompare una volta registrato un certificato. Consulta Aggiungere un certificato CA.
Scrivi la configurazione del proxy
tunnel_domainè obbligatorio: il proxy lo usa per rimuovere il suffisso del dominio dagli hostname in ingresso prima di cercare il sottodominio inroutes.routesè una mappa piatta da sottodominio a URL upstream, non una lista.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 EOFLa route
echo:punta al server MCP di esempio; sostituiscila con le tue route (o aggiungile). Consulta il riferimento sulla configurazione del proxy per tutti i campi disponibili.Scrivi docker-compose.yaml
L'impostazione
network_mode: "service:mcp-proxy"colloca cloudflared nel namespace di rete del proxy, in modo chelocalhost:8080all'interno del container cloudflared raggiunga il proxy. Il flag--url http://localhost:8080fornisce a cloudflared la destinazione di inoltro; senza quel flag, cloudflared non ha alcuna route per le richieste in ingresso e restituisce un 503.cat > docker-compose.yaml <<'EOF' services: cloudflared: image: cloudflare/cloudflared@sha256:0e56c13c7058fcef9c4f0e7d83372f82330548dbf8f8b366f3d8b506526d93b2 # --url è obbligatorio: nel flusso manuale non vengono inviate regole di ingress, # quindi senza di esso cloudflared risponde 503 a ogni richiesta. command: tunnel --no-autoupdate run --url http://localhost:8080 environment: - TUNNEL_TOKEN # Condividi il netns del proxy così che localhost:8080 lo raggiunga. 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" EOFSe stai usando il server MCP di esempio, aggiungilo come servizio:
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 EOFAvvia la distribuzione
docker compose up -d
Il file compose legge TUNNEL_TOKEN dall'ambiente dell'host senza alcun valore predefinito, quindi devi ripetere l'export in ogni nuova shell e dopo un riavvio.
Per una distribuzione su più VM, copia la directory mcp-tunnel/ su ciascun host, imposta TUNNEL_TOKEN ed esegui docker compose up -d. Nel flusso programmatico TUNNEL_TOKEN è $(sudo cat data/tunnel-token); nel flusso manuale è il valore che hai copiato dalla Console. Lo stesso token del tunnel e gli stessi certificati funzionano su tutte le repliche.
Verifica la distribuzione
Verifica end-to-end chiamando un server MCP upstream dal lato di Anthropic: consulta Usare i server MCP tramite tunnel. Con il server MCP di esempio, l'URL instradato è https://echo.<your-tunnel-domain>/mcp. Se la verifica non riesce, consulta Risoluzione dei problemi.
Aggiornamenti
Esegui i comandi di questa sezione dall'interno della directory di distribuzione mcp-tunnel/.
Ruota il token del tunnel
Con l'accesso programmatico, incrementa --token-version nel comando del servizio setup, imposta gli identificatori di Workload Identity Federation, genera un nuovo JWT OIDC ed esegui di nuovo il componente di setup:
# Modifica docker-compose.yaml: incrementa l'intero nell'argomento del servizio setup
# --token-version (ad esempio, da --token-version=1 a
# --token-version=2). Il binario di setup rifiuta la rotazione se il valore
# non è cambiato.
# export TUNNEL_ID=tnl_... # impostalo solo se l'hai impostato durante l'installazione
export ANTHROPIC_FEDERATION_RULE_ID=fdrl_...
export ANTHROPIC_ORGANIZATION_ID=00000000-0000-0000-0000-000000000000
# export ANTHROPIC_WORKSPACE_ID=wrkspc_... # se la tua regola è limitata al workspace
# Genera di nuovo ANTHROPIC_IDENTITY_TOKEN secondo la guida del provider WIF per il tuo
# ambiente (sarà scaduto dopo l'installazione).
export ANTHROPIC_IDENTITY_TOKEN=...
docker compose run --rm setup
export TUNNEL_TOKEN=$(sudo cat data/tunnel-token)
docker compose up -d cloudflaredL'argomento --token-version viene modificato in docker-compose.yaml anziché passato dalla riga di comando, in modo che il nuovo valore persista per le esecuzioni future del componente di setup. Il componente di setup si autentica con Workload Identity Federation; non c'è alcun token API da revocare.
Senza accesso programmatico, fai clic su Rotate token nella pagina dei dettagli del tunnel nella Console, quindi aggiorna la variabile d'ambiente TUNNEL_TOKEN su ciascun host e riavvia cloudflared (docker compose up -d cloudflared).
Rinnovo dei certificati
Sei responsabile del monitoraggio della scadenza e del rinnovo del certificato del server prima che scada.
Con l'accesso programmatico:
docker compose run --rm setup renew-cert --output=dir:/dataGli argomenti della CLI sostituiscono il command del servizio setup (gli argomenti di init) ma ne mantengono l'entrypoint, quindi viene eseguito /setup renew-cert --output=dir:/data.
Senza accesso programmatico, firma un nuovo certificato del server con la tua CA esistente (la CA registrata nella Console non cambia) e sostituisci data/tls.crt. Imposta prima TUNNEL_DOMAIN se lo esegui da una nuova shell.
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.extIn entrambi i flussi il proxy controlla periodicamente tls.cert_file e lo ricarica automaticamente, quindi non è necessario alcun riavvio.
Passaggi successivi
Collega un server MCP upstream a un Managed Agent o alla Messages API.
Indicazioni per il rafforzamento, rotazione delle credenziali e risposta alle violazioni.
Diagnostica problemi di connettività, TLS e instradamento.
Was this page helpful?