---
title: Implementa túneles MCP con Docker Compose
url: https://platform.claude.com/docs/es/agents-and-tools/mcp-tunnels/deploy-compose
description: Instala la pila de túneles MCP en una VM usando Docker Compose.
---

<Note>
  Los "MCP tunnels" (túneles de MCP) están en "research preview" (vista previa de investigación). [Solicita acceso](https://claude.com/form/mcp-tunnels) para probarlos.
</Note>

Esta guía implementa la [pila de túneles](https://platform.claude.com/docs/es/agents-and-tools/mcp-tunnels/concepts#components) como contenedores reforzados en un solo host. La misma configuración se puede replicar en varios hosts para lograr disponibilidad.

## Antes de empezar

Necesitas:

* **Un túnel.** Con acceso programático, el [componente de configuración](https://platform.claude.com/docs/es/agents-and-tools/mcp-tunnels/concepts#components) crea uno por ti cuando no proporcionas un ID de túnel; para conectarte a un túnel existente en su lugar, [créalo en la Claude Console](https://platform.claude.com/docs/es/agents-and-tools/mcp-tunnels/console#create-a-tunnel) y anota el ID del túnel (`tnl_...`). El aprovisionamiento manual siempre parte de un túnel creado en la Console.

* **Una forma de que el host se autentique ante la Tunnels API.**

  * **Acceso programático (recomendado).** Activa **Set up programmatic access** al crear el túnel para que el componente de configuración pueda autenticarse mediante Workload Identity Federation. Si dejas que el componente de configuración cree el túnel, crea en su lugar la regla de federación directamente en la página [Workload identity](https://platform.claude.com/settings/workload-identity-federation) (`platform.claude.com/settings/workload-identity-federation`) de la Claude Console. Anota el ID de la regla de federación (`fdrl_...`) y el ID de tu organización.
  * **Manual.** Omite el acceso programático. [Obtendrás el token del túnel desde la Console](https://platform.claude.com/docs/es/agents-and-tools/mcp-tunnels/console#get-the-connection-details), generarás tú mismo una CA y un certificado de servidor, y [registrarás la CA en la Console](https://platform.claude.com/docs/es/agents-and-tools/mcp-tunnels/console#add-a-ca-certificate).

* **Un host con Docker y Docker Compose** instalados. El flujo manual también requiere `openssl` (1.1.1 o posterior).

* **Conectividad de red saliente** desde el host hacia `api.anthropic.com` (443 TCP) y el [borde del túnel](https://platform.claude.com/docs/es/agents-and-tools/mcp-tunnels/concepts#components) (7844 TCP y UDP). Consulta los [requisitos de red](https://platform.claude.com/docs/es/agents-and-tools/mcp-tunnels/overview#network-requirements) completos.

* **Uno o más servidores MCP** en ejecución y accesibles desde el host en las direcciones que configurarás en `routes`. Si aún no tienes uno, [usa el servidor de ejemplo](https://platform.claude.com/docs/es/agents-and-tools/mcp-tunnels/deploy-compose#optional-use-a-sample-mcp-server).

## Opcional: Usa un servidor MCP de ejemplo

Si no tienes un servidor MCP disponible para pruebas, usa este servidor mínimo:

```bash
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
```

Los siguientes pasos de instalación hacen `cd` a `mcp-tunnel/` e indican dónde agregar el servicio y la ruta correspondientes.

## Instalación

Esta guía ofrece un enfoque de referencia usando Docker Compose. Eres responsable de adaptarlo para cumplir con los requisitos de seguridad de tu organización.

<Tabs>
  <Tab title="Con acceso programático">
    Esta ruta requiere que el host tenga un proveedor de identidad OIDC (como un servidor de metadatos de VM en la nube o SPIFFE). Si no lo tiene, usa en su lugar la pestaña **Sin acceso programático**.

    El componente de configuración usa Workload Identity Federation para obtener el token del túnel, generar una CA y un certificado de servidor, y registrar la CA con Anthropic.

    <Steps>
      <Step title="Prepara el directorio de implementación">
        ```bash
        mkdir -p mcp-tunnel/{config,data}
        cd mcp-tunnel
        sudo chown 65532:65532 data
        ```

        Los contenedores se ejecutan con el UID no root `65532` y necesitan acceso de escritura a `data/`.
      </Step>

      <Step title="Escribe docker-compose.yaml">
        El archivo compose fija las imágenes por digest SHA-256, ejecuta cada contenedor como no root con un sistema de archivos de solo lectura, elimina todas las capacidades de Linux y deshabilita la escalada de privilegios.

        ```bash
        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
            # Comparte el netns del proxy para que localhost:8080 llegue a él.
            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 usas el [servidor MCP de ejemplo](https://platform.claude.com/docs/es/agents-and-tools/mcp-tunnels/deploy-compose#optional-use-a-sample-mcp-server), agrégalo como servicio:

        ```bash
        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
        ```
      </Step>

      <Step title="Aprovisiona el túnel">
        Establece los identificadores. Deja `TUNNEL_ID` sin definir para que el componente de configuración cree un túnel; defínelo para conectarte a un túnel existente de la [Console](https://platform.claude.com/docs/es/agents-and-tools/mcp-tunnels/console#create-a-tunnel):

        ```bash
        # export TUNNEL_ID=tnl_...   # configúralo para conectarte a un túnel existente
        export ANTHROPIC_FEDERATION_RULE_ID=fdrl_...
        export ANTHROPIC_ORGANIZATION_ID=00000000-0000-0000-0000-000000000000
        ```

        Si tu regla de federación está limitada a un espacio de trabajo distinto del predeterminado de tu organización, establece también `ANTHROPIC_WORKSPACE_ID=wrkspc_...`; de lo contrario, el componente de configuración usa el espacio de trabajo predeterminado. Un túnel creado automáticamente se crea en ese espacio de trabajo.

        Establece `ANTHROPIC_IDENTITY_TOKEN` con un JWT OIDC del proveedor de identidad de este host. Sigue la [guía de WIF para tu proveedor](https://platform.claude.com/docs/es/manage-claude/workload-identity-federation#identity-providers) para registrar el emisor, establecer el sujeto de la regla y emitir el token; la audiencia de la regla debe coincidir con la audiencia que solicitas al emitirlo.

        Ejecuta el componente de configuración:

        ```bash
        docker compose run --rm setup
        ```

        `setup init` es idempotente sobre `data/`: volver a ejecutarlo reutiliza el ID del túnel y la CA ya almacenados allí y nunca crea un segundo túnel. Solo se genera y registra una nueva CA cuando `data/` está vacío o `TUNNEL_ID` ha cambiado; en ese caso se aplica el límite de dos certificados activos, así que revoca uno en la Console primero si ambos espacios están ocupados.

        Consulta [Fallos de autenticación del componente de configuración](https://platform.claude.com/docs/es/agents-and-tools/mcp-tunnels/troubleshooting#setup-component-authentication-failures) si se produce un error.

        Obtén el dominio de tu túnel y expórtalo para los pasos posteriores:

        ```bash
        export TUNNEL_DOMAIN=$(sudo cat data/tunnel-domain)
        echo "$TUNNEL_DOMAIN"
        ```

        <Note>
          Los tokens de Workload Identity Federation son de corta duración (1 hora de forma predeterminada) y caducan automáticamente; no hay nada que revocar una vez completada la configuración.
        </Note>
      </Step>

      <Step title="Escribe la configuración del proxy">
        `tunnel_domain` es **obligatorio**: el [proxy](https://platform.claude.com/docs/es/agents-and-tools/mcp-tunnels/concepts#components) lo usa para quitar el sufijo de dominio de los nombres de host entrantes antes de buscar el subdominio en `routes`. `routes` es un mapa plano de subdominio a URL upstream, no una lista.

        ```bash
        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 ruta `echo:` apunta al [servidor MCP de ejemplo](https://platform.claude.com/docs/es/agents-and-tools/mcp-tunnels/deploy-compose#optional-use-a-sample-mcp-server); reemplázala por tus propias rutas (o agrégalas). Consulta la referencia de [configuración del proxy](https://platform.claude.com/docs/es/agents-and-tools/mcp-tunnels/reference#proxy-configuration) para ver todos los campos disponibles.
      </Step>

      <Step title="Inicia la implementación">
        ```bash
        export TUNNEL_TOKEN=$(sudo cat data/tunnel-token)
        docker compose up -d
        ```
      </Step>
    </Steps>
  </Tab>

  <Tab title="Sin acceso programático">
    Usa este flujo si no activaste **Set up programmatic access**, o para desarrollo y pruebas locales. No hay servicio `setup`.

    <Steps>
      <Step title="Obtén el token y el dominio del túnel desde la Console">
        En la página de detalles del túnel, copia el **Domain** (tiene la forma `abcd1234.tunnel.anthropic.com`), luego haz clic en el ícono del ojo junto a **Token** para obtener el token del túnel y usa el ícono de copiar para copiarlo.

        Establece ambos como variables de shell para el resto de la guía:

        ```bash
        export TUNNEL_DOMAIN=YOUR_TUNNEL_DOMAIN_HERE
        export TUNNEL_TOKEN='eyJ...'
        ```
      </Step>

      <Step title="Crea la estructura y genera los certificados">
        ```bash
        mkdir -p mcp-tunnel/{data,config}
        cd mcp-tunnel
        ```

        El proxy escucha en `:8080` mediante WebSocket sin cifrar; el handshake de [TLS interno](https://platform.claude.com/docs/es/agents-and-tools/mcp-tunnels/concepts#components) ocurre **dentro** de ese flujo WebSocket usando estos certificados. Anthropic verifica el handshake interno contra la CA que registras en la Console. El "Subject Alternative Name" (nombre alternativo del sujeto), o SAN, del certificado de servidor debe incluir `*.<tunnel-domain>` según se describe en los [requisitos de certificados](https://platform.claude.com/docs/es/agents-and-tools/mcp-tunnels/reference#certificate-requirements).

        ```bash
        # CA autofirmada. Extensiones explícitas para que cumpla los requisitos
        # del certificado sin importar los valores predeterminados de openssl.cnf de la 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"

        # Archivo de extensiones para el certificado del servidor. Usar -extfile (en lugar de
        # -copy_extensions, que solo existe en OpenSSL 3.0+) mantiene esto funcionando en
        # OpenSSL 1.1.x.
        cat > data/tls.ext <<EOF
        subjectAltName = DNS:${TUNNEL_DOMAIN},DNS:*.${TUNNEL_DOMAIN}
        authorityKeyIdentifier = keyid,issuer
        extendedKeyUsage = serverAuth
        EOF

        # Certificado del servidor firmado por 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

        # Permite que el contenedor proxy sin root (UID 65532) lea la clave desde
        # el bind mount. Sin el bit de lectura para todos, el contenedor no puede abrir
        # un archivo propiedad del host.
        chmod 644 data/tls.key
        ```
      </Step>

      <Step title="Registra el certificado de la CA en la Console">
        En la página de detalles del túnel, desplázate hasta la sección **Certificates** y haz clic en **Add certificate**. Sube `data/ca.crt` directamente con **Choose file** (la ventana modal acepta `.pem`, `.crt` y `.cer`), o pega su contenido:

        ```bash
        cat data/ca.crt
        ```

        La insignia **Needs certificate** junto al nombre del túnel desaparece una vez que se registra un certificado. Consulta [Agregar un certificado de CA](https://platform.claude.com/docs/es/agents-and-tools/mcp-tunnels/console#add-a-ca-certificate).
      </Step>

      <Step title="Escribe la configuración del proxy">
        `tunnel_domain` es **obligatorio**: el proxy lo usa para quitar el sufijo de dominio de los nombres de host entrantes antes de buscar el subdominio en `routes`. `routes` es un mapa plano de subdominio a URL upstream, no una lista.

        ```bash
        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
        EOF
        ```

        La ruta `echo:` apunta al [servidor MCP de ejemplo](https://platform.claude.com/docs/es/agents-and-tools/mcp-tunnels/deploy-compose#optional-use-a-sample-mcp-server); reemplázala por tus propias rutas (o agrégalas). Consulta la referencia de [configuración del proxy](https://platform.claude.com/docs/es/agents-and-tools/mcp-tunnels/reference#proxy-configuration) para ver todos los campos disponibles.
      </Step>

      <Step title="Escribe docker-compose.yaml">
        La opción `network_mode: "service:mcp-proxy"` coloca a [cloudflared](https://platform.claude.com/docs/es/agents-and-tools/mcp-tunnels/concepts#components) en el espacio de nombres de red del proxy para que `localhost:8080` dentro del contenedor de cloudflared llegue al proxy. La opción `--url http://localhost:8080` le da a cloudflared su destino de reenvío; sin esa opción, cloudflared no tiene ruta para las solicitudes entrantes y devuelve un 503.

        ```bash
        cat > docker-compose.yaml <<'EOF'
        services:
          cloudflared:
            image: cloudflare/cloudflared@sha256:0e56c13c7058fcef9c4f0e7d83372f82330548dbf8f8b366f3d8b506526d93b2
            # --url es obligatorio: en el flujo manual no se envían reglas de ingress,
            # así que sin él cloudflared responde 503 a cada solicitud.
            command: tunnel --no-autoupdate run --url http://localhost:8080
            environment:
              - TUNNEL_TOKEN
            # Comparte el netns del proxy para que localhost:8080 lo alcance.
            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 usas el [servidor MCP de ejemplo](https://platform.claude.com/docs/es/agents-and-tools/mcp-tunnels/deploy-compose#optional-use-a-sample-mcp-server), agrégalo como servicio:

        ```bash
        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
        ```
      </Step>

      <Step title="Inicia la implementación">
        ```bash
        docker compose up -d
        ```
      </Step>
    </Steps>
  </Tab>
</Tabs>

El archivo compose lee `TUNNEL_TOKEN` del entorno del host sin valor predeterminado, por lo que debes repetir la exportación en cada shell nueva y después de un reinicio.

Para una implementación en varias VM, copia el directorio `mcp-tunnel/` a cada host, establece `TUNNEL_TOKEN` y ejecuta `docker compose up -d`. En el flujo programático, `TUNNEL_TOKEN` es `$(sudo cat data/tunnel-token)`; en el flujo manual, es el valor que copiaste de la Console. El mismo token de túnel y los mismos certificados funcionan en todas las réplicas.

## Verifica la implementación

Verifica de extremo a extremo llamando a un [servidor MCP upstream](https://platform.claude.com/docs/es/agents-and-tools/mcp-tunnels/concepts#components) desde el lado de Anthropic: consulta [Usar los servidores MCP tunelizados](https://platform.claude.com/docs/es/agents-and-tools/mcp-tunnels/overview#use-the-tunneled-mcp-servers). Con el [servidor MCP de ejemplo](https://platform.claude.com/docs/es/agents-and-tools/mcp-tunnels/deploy-compose#optional-use-a-sample-mcp-server), la URL enrutada es `https://echo.<your-tunnel-domain>/mcp`. Si la verificación falla, consulta [Solución de problemas](https://platform.claude.com/docs/es/agents-and-tools/mcp-tunnels/troubleshooting).

## Actualizaciones

Ejecuta los comandos de esta sección desde dentro del directorio de implementación `mcp-tunnel/`.

### Rota el token del túnel

Con acceso programático, incrementa `--token-version` en el comando del servicio `setup`, establece los identificadores de Workload Identity Federation, emite un JWT OIDC nuevo y vuelve a ejecutar el componente de configuración:

```bash
# Edita docker-compose.yaml: incrementa el entero en el argumento
# --token-version del servicio setup (por ejemplo, de --token-version=1 a
# --token-version=2). El binario setup se niega a rotar si el valor
# no ha cambiado.

# export TUNNEL_ID=tnl_...   # configúralo solo si lo configuraste durante la instalación
export ANTHROPIC_FEDERATION_RULE_ID=fdrl_...
export ANTHROPIC_ORGANIZATION_ID=00000000-0000-0000-0000-000000000000
# export ANTHROPIC_WORKSPACE_ID=wrkspc_...   # si tu regla está limitada al espacio de trabajo
# Vuelve a generar ANTHROPIC_IDENTITY_TOKEN según la guía del proveedor WIF para tu
# entorno (habrá expirado desde la instalación).
export ANTHROPIC_IDENTITY_TOKEN=...

docker compose run --rm setup

export TUNNEL_TOKEN=$(sudo cat data/tunnel-token)
docker compose up -d cloudflared
```

El argumento `--token-version` se edita en `docker-compose.yaml` en lugar de pasarse en la línea de comandos para que el nuevo valor persista en futuras ejecuciones del componente de configuración. El componente de configuración se autentica con Workload Identity Federation; no hay ningún token de API que revocar.

Sin acceso programático, haz clic en **Rotate token** en la página de detalles del túnel en la Console, luego actualiza la variable de entorno `TUNNEL_TOKEN` en cada host y reinicia cloudflared (`docker compose up -d cloudflared`).

<Warning>
  Hacer clic en **Rotate token** invalida el token actual de inmediato. Entre ese momento y la actualización de `TUNNEL_TOKEN` en cada host y el reinicio de cloudflared, cualquier host cuyo cloudflared se reinicie (fallo, reinicio del host) no podrá volver a conectarse. Actualiza cada host rápidamente después de rotar.
</Warning>

### Renovación de certificados

Eres responsable de supervisar la caducidad y renovar el certificado de servidor antes de que caduque.

Con acceso programático:

```bash
docker compose run --rm setup renew-cert --output=dir:/data
```

Los argumentos de la CLI reemplazan el `command` del servicio `setup` (los argumentos de `init`) pero conservan su `entrypoint`, por lo que esto ejecuta `/setup renew-cert --output=dir:/data`.

<Tip>
  Pasa `--renew-before=720h` para que el comando no haga nada cuando queden más de 30 días de validez. Esto hace que sea seguro ejecutarlo con una programación fija.
</Tip>

Sin acceso programático, firma un nuevo certificado de servidor con tu CA existente (la CA registrada en la Console no cambia) y reemplaza `data/tls.crt`. Establece primero `TUNNEL_DOMAIN` si ejecutas esto desde una shell nueva.

```bash
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
```

En cualquiera de los dos flujos, el proxy consulta periódicamente `tls.cert_file` y lo recarga automáticamente, por lo que no se requiere reiniciar.

## Próximos pasos

<CardGroup cols={2}>
  <Card title="Usa los servidores MCP tunelizados" icon="link" href="https://platform.claude.com/docs/es/agents-and-tools/mcp-tunnels/overview#use-the-tunneled-mcp-servers">
    Conecta un servidor MCP upstream a un Managed Agent o a la Messages API.
  </Card>

  <Card title="Seguridad" icon="lock" href="https://platform.claude.com/docs/es/agents-and-tools/mcp-tunnels/security">
    Guía de refuerzo, rotación de credenciales y respuesta ante brechas.
  </Card>

  <Card title="Solución de problemas" icon="wrench" href="https://platform.claude.com/docs/es/agents-and-tools/mcp-tunnels/troubleshooting">
    Diagnostica problemas de conectividad, TLS y enrutamiento.
  </Card>
</CardGroup>
