MCP tunnels are in research preview. Request access to try them.
This guide deploys the tunnel stack as hardened containers on a single host. The same configuration can be replicated across multiple hosts for availability.
You need:
tnl_...).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.If you don't have an MCP server available for testing, use this minimal one:
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")
EOFThe following Install steps 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.
The compose file reads TUNNEL_TOKEN from the host environment with no default, so the export must be repeated in every fresh shell and after a reboot.
For a multi-VM deployment, copy the mcp-tunnel/ directory to each host, set TUNNEL_TOKEN, and run docker compose up -d. In the programmatic flow TUNNEL_TOKEN is $(sudo cat data/tunnel-token); in the manual flow it's the value you copied from the Console. The same tunnel token and certificates work across all replicas.
Verify end to end by calling an upstream MCP 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, and re-run the setup component:
# 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 --token-version argument is edited in docker-compose.yaml rather than passed on the command line so the new value persists for future runs of the setup component. The setup component 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:/dataThe CLI arguments replace the setup service's command (the init arguments) but keep its entrypoint, so this runs /setup renew-cert --output=dir:/data.
Pass --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.
Attach an upstream MCP server to a Managed Agent or the Messages API.
Hardening guidance, credential rotation, and breach response.
Diagnose connectivity, TLS, and routing issues.
Was this page helpful?