Déployer des tunnels MCP avec Docker Compose
Installez la pile de tunnels MCP sur une VM à l'aide de Docker Compose.
Ce guide déploie la pile de tunnels sous forme de conteneurs renforcés sur un seul hôte. La même configuration peut être répliquée sur plusieurs hôtes pour assurer la disponibilité.
Avant de commencer
Vous avez besoin de :
- Un tunnel. Avec l'accès programmatique, le composant de configuration en crée un pour vous lorsque vous ne fournissez pas d'ID de tunnel ; pour vous rattacher plutôt à un tunnel existant, créez-le dans la Claude Console et notez l'ID du tunnel (
tnl_...). Le provisionnement manuel part toujours d'un tunnel créé dans la Console. - Un moyen pour l'hôte de s'authentifier auprès de l'API Tunnels.
- Accès programmatique (recommandé). Activez Set up programmatic access lors de la création du tunnel afin que le composant de configuration puisse s'authentifier via la « Workload Identity Federation » (fédération d'identité de charge de travail). Si vous laissez le composant de configuration créer le tunnel, créez plutôt la règle de fédération directement sur la page Workload identity (
platform.claude.com/settings/workload-identity-federation) dans la Claude Console. Notez l'ID de la règle de fédération (fdrl_...) et l'ID de votre organisation. - Manuel. Ignorez l'accès programmatique. Vous obtiendrez le jeton du tunnel depuis la Console, générerez vous-même une CA et un certificat serveur, et enregistrerez la CA dans la Console.
- Accès programmatique (recommandé). Activez Set up programmatic access lors de la création du tunnel afin que le composant de configuration puisse s'authentifier via la « Workload Identity Federation » (fédération d'identité de charge de travail). Si vous laissez le composant de configuration créer le tunnel, créez plutôt la règle de fédération directement sur la page Workload identity (
- Un hôte sur lequel Docker et Docker Compose sont installés. Le flux manuel nécessite également
openssl(1.1.1 ou ultérieur). - Une connectivité réseau sortante de l'hôte vers
api.anthropic.com(443 TCP) et la périphérie du tunnel (7844 TCP et UDP). Consultez l'ensemble des exigences réseau. - Un ou plusieurs serveurs MCP en cours d'exécution et accessibles depuis l'hôte aux adresses que vous configurerez sous
routes. Si vous n'en avez pas encore, utilisez le serveur d'exemple.
Facultatif : utiliser un serveur MCP d'exemple
Si vous ne disposez d'aucun serveur MCP pour les tests, utilisez ce serveur minimal :
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")
EOFLes étapes d'installation suivantes effectuent un cd dans mcp-tunnel/ et indiquent où ajouter le service et la route correspondants.
Installation
Ce guide propose une approche de référence utilisant Docker Compose. Il vous incombe de l'adapter pour répondre aux exigences de sécurité de votre organisation.
Ce parcours nécessite que l'hôte dispose d'un fournisseur d'identité OIDC (tel qu'un serveur de métadonnées de VM cloud ou SPIFFE). Si ce n'est pas le cas, utilisez plutôt l'onglet Sans accès programmatique.
Le composant de configuration utilise la Workload Identity Federation pour récupérer le jeton du tunnel, générer une CA et un certificat serveur, et enregistrer la CA auprès d'Anthropic.
Préparer le répertoire de déploiement
mkdir -p mcp-tunnel/{config,data} cd mcp-tunnel sudo chown 65532:65532 dataLes conteneurs s'exécutent avec l'UID non root
65532et ont besoin d'un accès en écriture àdata/.Écrire docker-compose.yaml
Le fichier compose épingle les images par empreinte SHA-256, exécute chaque conteneur en tant que non root avec un système de fichiers en lecture seule, supprime toutes les capacités Linux et désactive l'élévation de privilèges.
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 # Partager le netns du proxy pour que localhost:8080 l'atteigne. 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" EOFSi vous utilisez le serveur MCP d'exemple, ajoutez-le en tant que service :
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 EOFProvisionner le tunnel
Définissez les identifiants. Laissez
TUNNEL_IDnon défini pour que le composant de configuration crée un tunnel ; définissez-le pour vous rattacher à un tunnel existant depuis la Console :# export TUNNEL_ID=tnl_... # à définir pour se rattacher à un tunnel existant export ANTHROPIC_FEDERATION_RULE_ID=fdrl_... export ANTHROPIC_ORGANIZATION_ID=00000000-0000-0000-0000-000000000000Si votre règle de fédération est limitée à un espace de travail autre que l'espace de travail par défaut de votre organisation, définissez également
ANTHROPIC_WORKSPACE_ID=wrkspc_...; sinon, le composant de configuration utilise l'espace de travail par défaut. Un tunnel créé automatiquement est créé dans cet espace de travail.Définissez
ANTHROPIC_IDENTITY_TOKENsur un JWT OIDC provenant du fournisseur d'identité de cet hôte. Suivez le guide WIF pour votre fournisseur pour enregistrer l'émetteur, définir le sujet de la règle et émettre le jeton ; l'audience de la règle doit correspondre à l'audience que vous demandez lors de l'émission.Exécutez le composant de configuration :
docker compose run --rm setupsetup initest idempotent vis-à-vis dedata/: le réexécuter réutilise l'ID du tunnel et la CA déjà stockés à cet endroit et ne crée jamais de second tunnel. Une nouvelle CA n'est générée et enregistrée que lorsquedata/est vide ou queTUNNEL_IDa changé ; dans ce cas, la limite de deux certificats actifs s'applique, donc révoquez-en un dans la Console au préalable si les deux emplacements sont occupés.Consultez Échecs d'authentification du composant de configuration en cas d'erreur.
Récupérez le domaine de votre tunnel et exportez-le pour les étapes suivantes :
export TUNNEL_DOMAIN=$(sudo cat data/tunnel-domain) echo "$TUNNEL_DOMAIN"Écrire la configuration du proxy
tunnel_domainest obligatoire : le proxy l'utilise pour retirer le suffixe de domaine des noms d'hôte entrants avant de rechercher le sous-domaine dansroutes.routesest une table de correspondance plate entre sous-domaine et URL en amont, et non une liste.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:cible le serveur MCP d'exemple ; remplacez-la par vos propres routes (ou ajoutez-les). Consultez la référence de configuration du proxy pour tous les champs disponibles.Démarrer le déploiement
export TUNNEL_TOKEN=$(sudo cat data/tunnel-token) docker compose up -d
Utilisez ce flux si vous n'avez pas activé Set up programmatic access, ou pour le développement local et les tests. Il n'y a pas de service setup.
Obtenir le jeton et le domaine du tunnel depuis la Console
Sur la page de détail du tunnel, copiez le Domain (il a la forme
abcd1234.tunnel.anthropic.com), puis cliquez sur l'icône en forme d'œil à côté de Token pour récupérer le jeton du tunnel et utilisez l'icône de copie pour le copier.Définissez les deux comme variables shell pour le reste du guide :
export TUNNEL_DOMAIN=YOUR_TUNNEL_DOMAIN_HERE export TUNNEL_TOKEN='eyJ...'Créer la structure et générer les certificats
mkdir -p mcp-tunnel/{data,config} cd mcp-tunnelLe proxy écoute sur
:8080en WebSocket simple ; la poignée de main TLS interne a lieu à l'intérieur de ce flux WebSocket à l'aide de ces certificats. Anthropic vérifie la poignée de main interne par rapport à la CA que vous enregistrez dans la Console. Le Subject Alternative Name (SAN) du certificat serveur doit inclure*.<tunnel-domain>comme décrit dans les exigences relatives aux certificats.# CA auto-signée. Extensions explicites pour satisfaire les exigences # du certificat quels que soient les paramètres par défaut de openssl.cnf de la distribution. 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" # Fichier d'extensions pour le certificat serveur. Utiliser -extfile (au lieu de # -copy_extensions, disponible uniquement à partir d'OpenSSL 3.0) garantit le fonctionnement sur # OpenSSL 1.1.x. cat > data/tls.ext <<EOF subjectAltName = DNS:${TUNNEL_DOMAIN},DNS:*.${TUNNEL_DOMAIN} authorityKeyIdentifier = keyid,issuer extendedKeyUsage = serverAuth EOF # Certificat serveur signé par la 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 # Permet au conteneur proxy non root (UID 65532) de lire la clé depuis # le montage bind. Sans le bit de lecture pour tous, le conteneur ne peut pas ouvrir # un fichier appartenant à l'hôte. chmod 644 data/tls.keyEnregistrer le certificat CA dans la Console
Sur la page de détails du tunnel, faites défiler jusqu'à la section Certificates et cliquez sur Add certificate. Téléversez
data/ca.crtdirectement avec Choose file (la fenêtre modale accepte.pem,.crtet.cer), ou collez son contenu :cat data/ca.crtLe badge Needs certificate à côté du nom du tunnel disparaît une fois qu'un certificat est enregistré. Consultez Ajouter un certificat CA.
Écrire la configuration du proxy
tunnel_domainest obligatoire : le proxy l'utilise pour retirer le suffixe de domaine des noms d'hôte entrants avant de rechercher le sous-domaine dansroutes.routesest une table de correspondance plate entre sous-domaine et URL en amont, et non une liste.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:cible le serveur MCP d'exemple ; remplacez-la par vos propres routes (ou ajoutez-les). Consultez la référence de configuration du proxy pour tous les champs disponibles.Écrire docker-compose.yaml
Le paramètre
network_mode: "service:mcp-proxy"place cloudflared dans l'espace de noms réseau du proxy afin quelocalhost:8080à l'intérieur du conteneur cloudflared atteigne le proxy. L'option--url http://localhost:8080fournit à cloudflared sa cible de transfert ; sans cette option, cloudflared n'a aucune route pour les requêtes entrantes et renvoie une erreur 503.cat > docker-compose.yaml <<'EOF' services: cloudflared: image: cloudflare/cloudflared@sha256:0e56c13c7058fcef9c4f0e7d83372f82330548dbf8f8b366f3d8b506526d93b2 # --url est requis : aucune règle d'ingress n'est poussée dans le flux manuel, # donc sans lui, cloudflared renvoie une 503 à chaque requête. command: tunnel --no-autoupdate run --url http://localhost:8080 environment: - TUNNEL_TOKEN # Partagez le netns du proxy pour que localhost:8080 l'atteigne. 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" EOFSi vous utilisez le serveur MCP d'exemple, ajoutez-le en tant que service :
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 EOFDémarrer le déploiement
docker compose up -d
Le fichier compose lit TUNNEL_TOKEN depuis l'environnement de l'hôte sans valeur par défaut ; vous devez donc répéter l'export dans chaque nouveau shell et après un redémarrage.
Pour un déploiement sur plusieurs VM, copiez le répertoire mcp-tunnel/ sur chaque hôte, définissez TUNNEL_TOKEN et exécutez docker compose up -d. Dans le flux programmatique, TUNNEL_TOKEN vaut $(sudo cat data/tunnel-token) ; dans le flux manuel, il s'agit de la valeur que vous avez copiée depuis la Console. Le même jeton de tunnel et les mêmes certificats fonctionnent sur toutes les répliques.
Vérifier le déploiement
Vérifiez de bout en bout en appelant un serveur MCP en amont depuis le côté d'Anthropic : consultez Utiliser les serveurs MCP tunnelisés. Avec le serveur MCP d'exemple, l'URL routée est https://echo.<your-tunnel-domain>/mcp. Si la vérification échoue, consultez Dépannage.
Mises à niveau
Exécutez les commandes de cette section depuis le répertoire de déploiement mcp-tunnel/.
Renouveler le jeton du tunnel
Avec l'accès programmatique, incrémentez --token-version dans la commande du service setup, définissez les identifiants de Workload Identity Federation, émettez un nouveau JWT OIDC et réexécutez le composant de configuration :
# Modifiez docker-compose.yaml : incrémentez l'entier dans l'argument du service setup
# --token-version (par exemple, de --token-version=1 à
# --token-version=2). Le binaire setup refuse la rotation si la valeur
# n'a pas changé.
# export TUNNEL_ID=tnl_... # à définir uniquement si vous l'avez défini lors de l'installation
export ANTHROPIC_FEDERATION_RULE_ID=fdrl_...
export ANTHROPIC_ORGANIZATION_ID=00000000-0000-0000-0000-000000000000
# export ANTHROPIC_WORKSPACE_ID=wrkspc_... # si votre règle est limitée à un espace de travail
# Générez à nouveau ANTHROPIC_IDENTITY_TOKEN selon le guide du fournisseur WIF pour votre
# environnement (il aura expiré depuis l'installation).
export ANTHROPIC_IDENTITY_TOKEN=...
docker compose run --rm setup
export TUNNEL_TOKEN=$(sudo cat data/tunnel-token)
docker compose up -d cloudflaredL'argument --token-version est modifié dans docker-compose.yaml plutôt que passé en ligne de commande afin que la nouvelle valeur persiste pour les exécutions futures du composant de configuration. Le composant de configuration s'authentifie avec la Workload Identity Federation ; il n'y a aucun jeton d'API à révoquer.
Sans accès programmatique, cliquez sur Rotate token sur la page de détail du tunnel dans la Console, puis mettez à jour la variable d'environnement TUNNEL_TOKEN sur chaque hôte et redémarrez cloudflared (docker compose up -d cloudflared).
Renouvellement des certificats
Il vous incombe de surveiller l'expiration et de renouveler le certificat serveur avant qu'il n'expire.
Avec l'accès programmatique :
docker compose run --rm setup renew-cert --output=dir:/dataLes arguments CLI remplacent la command du service setup (les arguments init) mais conservent son entrypoint, de sorte que cela exécute /setup renew-cert --output=dir:/data.
Sans accès programmatique, signez un nouveau certificat serveur avec votre CA existante (la CA enregistrée dans la Console ne change pas) et remplacez data/tls.crt. Définissez d'abord TUNNEL_DOMAIN si vous exécutez ceci depuis un nouveau 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.extDans les deux flux, le proxy interroge régulièrement tls.cert_file et le recharge automatiquement ; aucun redémarrage n'est donc nécessaire.
Étapes suivantes
Rattachez un serveur MCP en amont à un Managed Agent ou à l'API Messages.
Conseils de renforcement, renouvellement des identifiants et réponse aux violations.
Diagnostiquez les problèmes de connectivité, de TLS et de routage.
Was this page helpful?