Claude Platform Docs
MessagesTunnels MCP

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.
  • 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")
EOF

Les é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.

  1. Préparer le répertoire de déploiement

    mkdir -p mcp-tunnel/{config,data}
    cd mcp-tunnel
    sudo chown 65532:65532 data

    Les conteneurs s'exécutent avec l'UID non root 65532 et ont besoin d'un accès en écriture à data/.

  2. É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"
    EOF

    Si 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
    EOF
  3. Provisionner le tunnel

    Définissez les identifiants. Laissez TUNNEL_ID non 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-000000000000

    Si 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_TOKEN sur 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 setup

    setup init est idempotent vis-à-vis de data/ : 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 lorsque data/ est vide ou que TUNNEL_ID a 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"
  4. Écrire la configuration du proxy

    tunnel_domain est obligatoire : le proxy l'utilise pour retirer le suffixe de domaine des noms d'hôte entrants avant de rechercher le sous-domaine dans routes. routes est 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
    EOF

    La 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.

  5. Démarrer le déploiement

    export TUNNEL_TOKEN=$(sudo cat data/tunnel-token)
    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 cloudflared

L'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:/data

Les 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.ext

Dans 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

Was this page helpful?