Was this page helpful?
MCP tunnels is a Research Preview feature. Request access to try it.
This guide deploys the MCP tunnel stack as hardened containers on a single host. The same configuration can be replicated across multiple hosts for availability.
You need:
tnl_...).setup service can authenticate through Workload Identity Federation. Record the federation rule ID (fdrl_...) and your organization ID.openssl (1.1.1 or newer).api.anthropic.com (443 TCP) and the tunnel edge (7844 TCP and UDP). See the full network requirements.routes. If you don't have one yet, use the sample server below.If you don't have an MCP server available for testing, use this minimal one:
mkdir -p mcp-tunnel/{config,data}
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")
EOFThe Install steps below cd into mcp-tunnel/ and note where to add the corresponding service and route.
This guide provides one reference approach using Docker Compose. You are responsible for adapting it to meet your organization's security requirements.
For a multi-VM deployment, copy your deployment directory to each host, set TUNNEL_TOKEN ($(sudo cat data/tunnel-token) in the programmatic flow, or the revealed value in the manual flow), and run docker compose up -d. The compose file reads TUNNEL_TOKEN from the environment with no default, so the export must run in every fresh shell, including after a reboot. The same tunnel token and certificates work across all replicas.
Verify end to end by calling a routed server from Anthropic's side: see Use the tunneled MCP servers. With the sample MCP server, the routed URL is https://echo.<your-tunnel-domain>/mcp. If verification fails, see Troubleshooting.
Run the commands in this section from inside the mcp-tunnel/ deployment directory.
With programmatic access, increment --token-version in the setup service command, set the Workload Identity Federation identifiers, mint a fresh OIDC JWT (it will have expired since install), and re-run setup:
# Edit docker-compose.yaml: increment the integer in the setup service's
# --token-version argument (for example, --token-version=1 to
# --token-version=2). The setup binary refuses to rotate when the value
# hasn't changed.
export TUNNEL_ID=tnl_...
export ANTHROPIC_FEDERATION_RULE_ID=fdrl_...
export ANTHROPIC_ORGANIZATION_ID=00000000-0000-0000-0000-000000000000
# export ANTHROPIC_WORKSPACE_ID=wrkspc_... # if your rule is workspace-scoped
# Re-mint ANTHROPIC_IDENTITY_TOKEN per the WIF provider guide for your
# environment (it will have expired since install).
export ANTHROPIC_IDENTITY_TOKEN=...
docker compose run --rm setup
export TUNNEL_TOKEN=$(sudo cat data/tunnel-token)
docker compose up -d cloudflaredThe setup binary authenticates with Workload Identity Federation; there is no API token to revoke.
Without programmatic access, click Rotate token on the tunnel detail page in the Console, then update the TUNNEL_TOKEN environment variable on each host and restart cloudflared (docker compose up -d cloudflared).
Clicking Rotate token invalidates the current token immediately. Between that moment and updating TUNNEL_TOKEN on every host and restarting cloudflared, any host whose cloudflared restarts (crash, host reboot) cannot reconnect. Update each host promptly after rotating.
You're responsible for monitoring expiry and renewing the server certificate before it expires.
With programmatic access:
docker compose run --rm setup renew-cert --output=dir:/dataPass --renew-before=720h to make the command a no-op when more than 30 days of validity remain. This makes it safe to run on a fixed schedule.
Without programmatic access, sign a new server certificate with your existing CA (the CA registered in the Console doesn't change) and replace data/tls.crt. Set TUNNEL_DOMAIN first if you're running this from a fresh 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 either flow the proxy polls tls.cert_file and reloads it automatically, so no restart is required.
Diagnose connectivity, TLS, and routing issues.