What this read moved
1–4 of 4connectors/mcp-tunnels/oauth New page · 77 lines, new page
# Authenticate to MCP servers behind a tunnel ## How OAuth works through a tunnel ## Route the authorization server through the tunnel ## Set the Tunnel OAuth configuration ## Publish split metadata instead
A whole new page. There's nothing to diff it against, so here is what it says.
# Authenticate to MCP servers behind a tunnel
> Make OAuth sign-in work for MCP servers reached through an MCP tunnel when the authorization server or identity provider is inside your network. Covers the Tunnel OAuth configuration fields (issuer, authorization endpoint, token endpoint, registration endpoint, scopes) and the split-metadata alternative.
<Note>
MCP tunnels are in research preview and are available to organizations on the Claude Enterprise plan by request. To request access, contact your Anthropic account team.
</Note>
An MCP tunnel carries Claude's requests to an MCP server inside your network, but it does not authenticate to that server. Each tunneled server should still require OAuth, as the [MCP authorization specification](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization) describes, so that a member signs in with their own account before Claude can call the server's tools. This page is for the administrator adding a tunneled server as a custom connector, and explains what to configure when the OAuth authorization server is itself only reachable inside your network.
If your authorization server is reachable from the public internet and its metadata advertises public URLs, you don't need anything on this page. Add the connector as described in [Set up an MCP tunnel](/docs/connectors/mcp-tunnels/setup#add-tunneled-servers-as-connectors) and members sign in as they would for any other connector.
## How OAuth works through a tunnel
Two different parties make requests during an OAuth sign-in, and they reach your authorization server by different paths.
* **The member's browser** is redirected to the authorization endpoint to sign in and approve access. This request comes from the member's device, so the authorization endpoint must be a URL their browser can load, either on the public internet or on your corporate network. It can't be a `tunnel.anthropic.com` hostname, because tunnel hostnames accept connections only from Claude.
* **Claude's servers** fetch the authorization server's metadata, register an OAuth client if the server supports dynamic registration, and exchange the authorization code for tokens at the token endpoint. These requests come from Anthropic's network, so the endpoints must be reachable from there, either publicly or through the tunnel.
By default Claude discovers all of these URLs from the metadata your MCP server and authorization server publish. When the authorization server sits inside your network, that metadata usually advertises internal hostnames. Claude then can't reach the token endpoint, or the member's browser is sent to an address it can't load, and sign-in fails.
You fix this by routing Claude's server-to-server calls through the tunnel and telling Claude explicitly which URL to use for each endpoint.
## Route the authorization server through the tunnel
Add a route for the authorization server to the proxy configuration, next to the routes for your MCP servers, and apply it as described in [Add more servers later](/docs/connectors/mcp-tunnels/setup#add-more-servers-later).
```yaml theme={null}
routes:
docs: http://docs-mcp.example.corp:8080
auth: https://sso.example.corp:8443
```
With a tunnel domain of `abc123.tunnel.anthropic.com`, Claude can now reach the authorization server at `https://auth.abc123.tunnel.anthropic.com`. For an `https://` upstream like this one, also set `upstream.tls.ca_file` or `upstream.tls.include_system_cas` in the proxy configuration so the proxy can verify the server's certificate. See the [proxy configuration reference](https://platform.claude.com/docs/en/agents-and-tools/mcp-tunnels/reference#proxy-configuration).
## Set the Tunnel OAuth configuration
When you add the tunneled MCP server as a custom connector in **Organization settings > Connectors**, turn on **Tunnel OAuth configuration** in the connector dialog. The values you enter replace the ones Claude would otherwise read from the authorization server's metadata. Anthropic enables this option for each organization in the research preview on request, so if the toggle does not appear in the dialog, contact your Anthropic account team.
| Field | What to enter | Example |
| ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- |
| **Issuer** | The issuer identifier your authorization server puts in its metadata and tokens. This can be an internal URL, because Claude uses it for validation rather than as an address to connect to. | `https://sso.example.corp:8443` |
| **Authorization endpoint** | The sign-in URL that members' browsers are redirected to. It must be loadable from their devices, on the public internet or your corporate network. | `https://sso.example.corp/authorize` |
| **Token endpoint** | The token endpoint Claude exchanges the authorization code at. Either the endpoint as reached through the tunnel (an `https://` URL under your tunnel domain), or a public `https://` URL on the same origin (scheme, host, and port) as the **Authorization endpoint**. | `https://auth.abc123.tunnel.anthropic.com/oauth/token` |
| **Registration endpoint (optional)** | The dynamic client registration endpoint, under the same rule as **Token endpoint**: a URL under your tunnel domain or one on the **Authorization endpoint**'s origin. Leave it blank if you select **Use your own OAuth client** and enter a client ID you registered with the authorization server yourself. | `https://auth.abc123.tunnel.anthropic.com/oauth/register` |
| **Requested scopes** | The scopes Claude requests at sign-in, separated by spaces. | `openid wiki:read wiki:write` |
The paths after the hostname (`/authorize`, `/oauth/token`, and so on) are whatever your authorization server uses. Copy them from its metadata document, usually served at `/.well-known/oauth-authorization-server` or `/.well-known/openid-configuration`, and change only the scheme and host.
After you save the connector, connect it yourself from your own connector settings. Your browser should land on your sign-in page, and after you approve access the connector should show as connected. If either step fails, see [Troubleshooting](/docs/connectors/mcp-tunnels/troubleshooting#sign-in-redirects-to-a-tunnel-address-that-does-not-load).
## Publish split metadata instead
If you operate the authorization server and can change the metadata it publishes, you can get the same result without the connector settings by advertising the split yourself. Point `authorization_endpoint` at the browser-reachable hostname and every other endpoint at the tunnel hostname in the authorization server's `/.well-known/oauth-authorization-server` document:
```json theme={null}
{
"issuer": "https://auth.abc123.tunnel.anthropic.com",
"authorization_endpoint": "https://sso.example.corp/authorize",
"token_endpoint": "https://auth.abc123.tunnel.anthropic.com/oauth/token",
"registration_endpoint": "https://auth.abc123.tunnel.anthropic.com/oauth/register",
"code_challenge_methods_supported": ["S256"]
}
```
Then have the MCP server's `/.well-known/oauth-protected-resource` document name the tunnel hostname as its authorization server:
```json theme={null}
{
"resource": "https://docs.abc123.tunnel.anthropic.com/mcp",
"authorization_servers": ["https://auth.abc123.tunnel.anthropic.com"]
}
```
This approach also suits an authorization server that is publicly reachable but sits behind a source-IP allowlist that you don't want to open to Anthropic's egress ranges. The [platform troubleshooting guide](https://platform.claude.com/docs/en/agents-and-tools/mcp-tunnels/troubleshooting#oauth-fails-behind-a-source-ip-allowlist) walks through the same configuration.
Use **Tunnel OAuth configuration** when the authorization server is a product whose metadata you can't edit, or when you prefer to keep tunnel-specific addresses out of the server's configuration. Use split metadata when you control the authorization server and want the configuration to apply to every client that discovers it through the tunnel.
connectors/mcp-tunnels/overview New page · 108 lines, new page
# MCP tunnels ## When to use an MCP tunnel ## How traffic flows ## What you need ### Network requirements ## Security model ### Shared responsibility ## Limits ## Next steps
A whole new page. There's nothing to diff it against, so here is what it says.
# MCP tunnels
> Connect Claude to MCP servers inside your private network without opening inbound firewall ports or exposing the servers to the internet. How MCP tunnels work, what you deploy, network and plan requirements, and the security model.
<Note>
MCP tunnels are in research preview and are available to organizations on the Claude Enterprise plan by request. To request access, contact your Anthropic account team. The preview is provided as-is, without uptime, support, or continuity commitments, and it depends on a third-party network provider (Cloudflare) that makes no availability commitment for the underlying transport. Anthropic may modify or discontinue MCP tunnels at any time.
</Note>
MCP tunnels connect Claude to [Model Context Protocol (MCP)](/docs/connectors/building/mcp) servers that run inside your private network. You run a small tunnel stack on a host in your network, the stack opens an outbound-only connection to Anthropic, and Claude sends MCP requests to your servers over that connection. Your firewall needs no inbound rules and your MCP servers need no public endpoint. Members of your organization use the tunneled servers as [custom connectors](/docs/connectors/custom/remote-mcp) in Claude, the same way they use any other remote MCP server.
This section is for administrators of claude.ai organizations on the Enterprise plan and the infrastructure teams they work with. To use MCP tunnels with the Claude Console, Claude Managed Agents, or the Messages API, see [MCP tunnels in the Claude Platform docs](https://platform.claude.com/docs/en/agents-and-tools/mcp-tunnels/overview). A tunnel belongs to the organization that created it, so a tunnel created from a Console organization can't serve connectors in claude.ai, and a tunnel created from claude.ai can't serve the API.
## When to use an MCP tunnel
Use a tunnel when the MCP server your organization wants to reach from Claude is only reachable inside your network, and your security policy rules out giving it a public endpoint or allowlisting Anthropic's IP ranges at your edge. Internal knowledge bases, ticketing systems, and data services wrapped in an MCP server are typical candidates.
If the MCP server is already reachable from the internet, you don't need a tunnel. Add it as a [custom connector](/docs/connectors/custom/remote-mcp) directly.
## How traffic flows
The tunnel stack is two containers that you run inside your network, from images that Anthropic and Cloudflare publish:
* **cloudflared** is Cloudflare's open-source tunnel connector. It dials out from your network to the tunnel edge and keeps that connection open. It never listens on an inbound port.
* **The proxy** (`mcp-proxy`) is Anthropic's routing component. It terminates an inner layer of TLS, checks that each destination address falls inside an allowed private range, and forwards each request to the right MCP server based on the hostname it was sent to.
When you create a tunnel, Anthropic assigns it a domain such as `abc123.tunnel.anthropic.com`. Each MCP server you expose gets a subdomain of that domain, chosen by you in the proxy's route configuration. A route named `docs` that points at `http://docs-mcp.example.corp:8080` makes that server reachable from Claude at `https://docs.abc123.tunnel.anthropic.com`.
A request then travels like this:
1. cloudflared opens an outbound connection from your network to the tunnel edge on port 7844 and holds it open.
2. A member uses the connector in Claude. Claude sends the MCP request to `docs.abc123.tunnel.anthropic.com`, and the request travels over the already-open connection to cloudflared and then to the proxy.
3. The proxy decrypts the request, looks up the `docs` route, and forwards the request to `docs-mcp.example.corp:8080`. The response returns along the same path.
Hostnames under `tunnel.anthropic.com` accept connections only from Claude. You can't open them in a browser or test them with `curl` from your own network, so you verify a tunnel by using it from Claude.
## What you need
* A claude.ai organization on the Enterprise plan with MCP tunnels enabled. Contact your Anthropic account team to request access.
* The Owner or Primary Owner role in that organization, to create the API key the tunnel setup uses and to add the tunneled servers as connectors.
* A place to run the tunnel stack inside your network: a Kubernetes cluster (deployed with Helm) or a Linux host with Docker and Docker Compose. One stack serves one tunnel, and you can run replicas of it on several hosts for availability.
* One or more MCP servers that speak the Streamable HTTP transport and are reachable from that cluster or host.
* Outbound network access from the stack as listed under [Network requirements](#network-requirements).
### Network requirements
| Component | Destination | Port and protocol | Used during |
| --------------- | ---------------------------------------------------- | ---------------------------- | ---------------------------- |
| Setup component | `api.anthropic.com` | 443 TCP | Provisioning, token rotation |
| cloudflared | Tunnel edge (`198.41.192.0/19`, `2606:4700:a0::/44`) | 7844 TCP and UDP | Runtime |
| Proxy | Your MCP servers | As configured in your routes | Runtime |
No inbound rules are required. See [Cloudflare's tunnel firewall documentation](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/configure-tunnels/tunnel-with-firewall/) for the authoritative edge IP list.
## Security model
Three independent layers protect every request through a tunnel.
| Layer | Protects against |
| --------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| Outer mutual TLS between Anthropic and the transport provider, with IP validation | Unauthorized clients reaching the tunnel |
| Inner TLS from Anthropic's backend to your proxy | Payload inspection by the transport provider or any network intermediary |
| OAuth on each MCP server | Unauthorized use of MCP tools by traffic that has reached the server |
The proxy terminates inner TLS with a certificate signed by a certificate authority (CA) that the setup component generates inside your environment and registers with Anthropic. Only your deployment holds the private keys, so Cloudflare carries ciphertext and cannot read MCP requests or responses. Anthropic does not connect to a tunnel until a CA certificate is registered for it. Cloudflare does receive connection metadata: the egress IP address and a host fingerprint of the machine running cloudflared, connection timing and byte volume, and the `tunnel.anthropic.com` subdomain assigned to your tunnel. Cloudflare acts as a subprocessor for this research preview.
The tunnel carries traffic to your MCP servers but does not authenticate to them. Configure each MCP server to require OAuth as described in the [MCP authorization specification](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization), and see [Authenticate to MCP servers behind a tunnel](/docs/connectors/mcp-tunnels/oauth) for how sign-in works when the authorization server is also inside your network.
### Shared responsibility
| Anthropic handles | Your organization handles |
| --------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| Restricting tunnel access so that only Anthropic can connect | All content and traffic that transits your tunnel, and compliance with applicable third-party acceptable-use policies, including Cloudflare's |
| Validating your CA certificate before connecting to your proxy | Securing the tunnel token, the Tunnels API key, and the TLS private keys |
| Sending Claude's requests only to tunnels that your organization owns | Renewing the server certificate before it expires |
| | Requiring OAuth on each MCP server and limiting each server to the tools it needs |
| | Restricting network access for the proxy hosts and MCP servers |
| | Notifying Anthropic if you suspect a compromise |
<Warning>
An attacker who obtains your tunnel token and one of your TLS private keys could impersonate your proxy and read MCP request payloads, including OAuth tokens. Store both with your organization's secrets-management controls, restrict file permissions, and rotate them on a schedule and immediately after any suspected exposure. See [Rotate credentials](/docs/connectors/mcp-tunnels/setup#rotate-credentials).
</Warning>
## Limits
* An organization can have up to 10 active tunnels.
* A tunnel holds up to two active CA certificates at a time, so you can rotate without downtime.
* The server certificate that the setup component generates is valid for 90 days.
* The proxy connects to upstream MCP servers over IPv4 only, and by default only to addresses in the RFC 1918 private ranges (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`).
## Next steps
<Columns cols={2}>
<Card title="Set up an MCP tunnel" icon="rocket" href="/docs/connectors/mcp-tunnels/setup">
Create the API key, deploy the tunnel stack with Helm or Docker Compose, and add your servers as connectors.
</Card>
<Card title="Authenticate through a tunnel" icon="lock" href="/docs/connectors/mcp-tunnels/oauth">
Make OAuth sign-in work when your authorization server is inside your network.
</Card>
<Card title="Troubleshooting" icon="wrench" href="/docs/connectors/mcp-tunnels/troubleshooting">
Diagnose connection, certificate, routing, and sign-in failures.
</Card>
<Card title="Platform reference" icon="book" href="https://platform.claude.com/docs/en/agents-and-tools/mcp-tunnels/reference">
Proxy configuration fields, certificate requirements, and the setup component.
</Card>
</Columns>
connectors/mcp-tunnels/setup New page · 425 lines, new page
# Set up an MCP tunnel ## Create a Tunnels API key ## Deploy the tunnel stack ## Verify the connection ## Add tunneled servers as connectors ### Add more servers later ## Rotate credentials ## Remove a tunnel
A whole new page. There's nothing to diff it against, so here is what it says.
# Set up an MCP tunnel
> Create a Tunnels API key in claude.ai, deploy the MCP tunnel stack with Helm or Docker Compose, verify the connection, add tunneled MCP servers as custom connectors, rotate the tunnel token and certificates, and remove a tunnel.
<Note>
MCP tunnels are in research preview and are available to organizations on the Claude Enterprise plan by request. To request access, contact your Anthropic account team.
</Note>
This page covers the full setup of an MCP tunnel for a claude.ai Enterprise organization, from creating the API key that provisioning uses to members calling a tunneled MCP server from Claude. You need the Owner or Primary Owner role in claude.ai, and someone who can deploy containers to a Kubernetes cluster or a Docker host inside your network. Read [MCP tunnels](/docs/connectors/mcp-tunnels/overview) first if the tunnel stack, the tunnel domain, and routes are unfamiliar.
The deployment steps on this page are reference deployments. You are responsible for adapting them to your organization's security requirements. For the full set of proxy options, certificate requirements, and hardening guidance, see the [MCP tunnels reference](https://platform.claude.com/docs/en/agents-and-tools/mcp-tunnels/reference) and [MCP tunnels security](https://platform.claude.com/docs/en/agents-and-tools/mcp-tunnels/security) pages in the Claude Platform docs. Those pages describe the Claude Console flow, which authenticates the setup component differently. For a claude.ai organization, follow the authentication steps on this page.
## Create a Tunnels API key
The setup component that runs alongside the tunnel stack needs a short-lived credential to create the tunnel, register its certificate authority (CA) certificate with Anthropic, and fetch the tunnel token. In claude.ai that credential is a Tunnels API key.
1. In claude.ai, go to **Organization settings > Tunnels**. This page appears once Anthropic has enabled MCP tunnels for your organization.
2. Open **Tunnels API** and create a key.
3. Copy the key somewhere safe for the next section. You pass it to the setup component once.
The tunnel stack does not use the key at runtime. Revoke the key as soon as setup completes, and create a fresh one later when you rotate the tunnel token.
## Deploy the tunnel stack
Choose Helm if you run Kubernetes. The chart provisions the tunnel, stores the credentials in a Secret, and renews the server certificate automatically. Choose Docker Compose for a single host or a VM, where you run the setup component and certificate renewal yourself.
Both paths need at least one route. A route maps a subdomain of your tunnel domain to the internal URL of an MCP server, in the form `scheme://host:port` with no path. The examples use `docs` pointing at `http://docs-mcp.example.corp:8080`. Replace them with your own servers.
<Tabs>
<Tab title="Helm">
<Steps>
<Step title="Fetch the default values">
```bash theme={null}
helm show values \
oci://us-docker.pkg.dev/anthropic-public-registry/charts/mcp-tunnel \
--version 2.0.2 > values.yaml
```
The file includes comments explaining each field.
</Step>
<Step title="Configure routes">
Edit `values.yaml` and add a `routes` entry under `gateway.config` for each MCP server. Leave `tunnel.id` empty so the setup component creates the tunnel during install.
```yaml values.yaml theme={null}
tunnel:
id: ""
# Increment to rotate the tunnel token on a later upgrade.
tokenVersion: "1"
gateway:
config:
routes:
docs: http://docs-mcp.example.corp:8080
search: http://10.0.12.7:9000
```
With these routes, Claude reaches the servers at `docs.<your-tunnel-domain>` and `search.<your-tunnel-domain>`. If a route targets an address outside the RFC 1918 private ranges (some managed Kubernetes distributions allocate Service IPs elsewhere), add the range under `gateway.config.upstream.allowed_ips` as described in [Troubleshooting](/docs/connectors/mcp-tunnels/troubleshooting#proxy-logs-ip-validation-failed).
</Step>
<Step title="Review the rendered manifests">
Render the chart with a placeholder key and review the output according to your organization's practices for third-party manifests. Rendering makes no API calls.
```bash theme={null}
helm template mcp-tunnel \
oci://us-docker.pkg.dev/anthropic-public-registry/charts/mcp-tunnel \
--version 2.0.2 \
-n mcp-tunnel \
-f values.yaml \
--set api.token=placeholder > rendered.yaml
```
</Step>
<Step title="Install">
Read the Tunnels API key into an environment variable so it stays out of your shell history and values file, then install into a dedicated namespace.
```bash theme={null}
# Paste the Tunnels API key (input is hidden)
read -rs API_TOKEN && export API_TOKEN
helm install mcp-tunnel \
oci://us-docker.pkg.dev/anthropic-public-registry/charts/mcp-tunnel \
--version 2.0.2 \
--namespace mcp-tunnel --create-namespace \
-f values.yaml \
--set api.token="$API_TOKEN"
```
The setup component runs as a pre-install hook, so `helm install` blocks until the tunnel is created, the CA is registered, and the credentials are stored in the `mcp-tunnel` Secret. If the install fails with a hook error, see [Troubleshooting](/docs/connectors/mcp-tunnels/troubleshooting#helm-install-fails-with-a-hook-error).
<Warning>
Revoke the Tunnels API key in **Organization settings > Tunnels > Tunnels API** as soon as the install completes. Helm records `--set` values in its release history Secrets, and Kubernetes Secrets are not encrypted at rest by default, so the key remains recoverable from the cluster until you revoke it.
</Warning>
</Step>
<Step title="Read the tunnel domain">
You need the tunnel domain to add connectors later.
```bash theme={null}
kubectl -n mcp-tunnel get secret mcp-tunnel \
-o jsonpath='{.data.tunnel-domain}' | base64 -d
```
The value looks like `abc123.tunnel.anthropic.com`.
</Step>
</Steps>
To restrict the pod's egress at the network level, set `networkPolicy.enabled: true` in `values.yaml` and list your MCP servers under `networkPolicy.mcpServers`. The policy already allows cloudflared to reach the tunnel edge. Your cluster's network plugin must support NetworkPolicy.
For later configuration changes such as routes or replica count, edit `values.yaml` and run `helm upgrade` with the same `--version` and `-f values.yaml`, without the API key. Keep a complete `values.yaml` rather than relying on `--reuse-values`, because Helm's deep merge can silently keep a route you deleted.
</Tab>
<Tab title="Docker Compose">
<Steps>
<Step title="Prepare the deployment directory">
```bash theme={null}
mkdir -p mcp-tunnel/{config,data}
cd mcp-tunnel
sudo chown 65532:65532 data
```
The containers run as the non-root user ID `65532` and need write access to `data/`.
</Step>
<Step title="Write docker-compose.yaml">
The compose file pins images by digest, runs every container as non-root with a read-only filesystem, drops all Linux capabilities, and disables privilege escalation.
```bash theme={null}
cat > docker-compose.yaml <<'EOF'
services:
# One-time provisioning. Run with: docker compose run --rm setup
setup:
image: us-docker.pkg.dev/anthropic-public-registry/images/mcp-proxy@sha256:efb27b299d627e4134815663cb8896641eeaee025d734c0f695582b4df38f013
entrypoint: ["/setup"]
command:
- init
- --api-url=https://api.anthropic.com
- --output=dir:/data
- --token-version=1
environment:
- API_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:6b599ca3e974349ead3286d178da61d291961182ec3fe9c505e1dd02c8ac31b0
command: tunnel --no-autoupdate run --url http://localhost:8080
environment:
- TUNNEL_TOKEN
# Share the proxy's network namespace so localhost:8080 reaches it.
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:efb27b299d627e4134815663cb8896641eeaee025d734c0f695582b4df38f013
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
# Match shutdown_timeout in the proxy config
stop_grace_period: 30s
logging:
options:
max-size: "10m"
max-file: "3"
EOF
```
</Step>
<Step title="Provision the tunnel">
Read the Tunnels API key into an environment variable, then run the setup component. It creates the tunnel, generates the CA and server certificate, registers the CA with Anthropic, fetches the tunnel token, and writes everything to `data/`.
```bash theme={null}
# Paste the Tunnels API key (input is hidden)
read -rs API_TOKEN && export API_TOKEN
docker compose run --rm setup
```
Read the tunnel domain and keep it for later steps.
```bash theme={null}
export TUNNEL_DOMAIN=$(sudo cat data/tunnel-domain)
echo "$TUNNEL_DOMAIN"
```
<Warning>
Revoke the Tunnels API key in **Organization settings > Tunnels > Tunnels API** before continuing, and run `unset API_TOKEN`. The stack does not need the key at runtime.
</Warning>
</Step>
<Step title="Write the proxy config">
`tunnel_domain` is required so the proxy can strip the domain from incoming hostnames and look up the remaining subdomain in `routes`. `routes` is a map, not a list.
```bash theme={null}
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:
docs: http://docs-mcp.example.corp:8080
search: http://10.0.12.7:9000
upstream:
allowed_ips:
- 10.0.0.0/8
EOF
```
`upstream.allowed_ips` is the proxy's protection against server-side request forgery. Use the narrowest ranges that cover your MCP servers. Setting it replaces the RFC 1918 default rather than extending it.
</Step>
<Step title="Start the stack">
```bash theme={null}
export TUNNEL_TOKEN=$(sudo cat data/tunnel-token)
docker compose up -d
```
The compose file reads `TUNNEL_TOKEN` from the host environment with no default, so repeat the export in every fresh shell and after a reboot. For a multi-host deployment, copy the `mcp-tunnel/` directory to each host and start it the same way. The same tunnel token and certificates work across all replicas.
</Step>
</Steps>
The `data/` directory now holds the tunnel ID, tunnel domain, tunnel token, CA key pair, and server key pair. Protect it with your organization's file-permission, encryption-at-rest, and secrets-management controls, and consider moving `ca.key` and `tunnel-token` to secure storage.
</Tab>
</Tabs>
## Verify the connection
Check the logs on your side first. cloudflared logs four `Registered tunnel connection` lines when it has reached the tunnel edge, and the proxy logs one `route configured` line per route.
<CodeGroup>
```bash Helm theme={null}
kubectl -n mcp-tunnel logs deploy/mcp-tunnel -c cloudflared | grep "Registered tunnel connection"
kubectl -n mcp-tunnel logs deploy/mcp-tunnel -c mcp-proxy | grep "route configured"
```
```bash Docker Compose theme={null}
docker compose logs cloudflared | grep "Registered tunnel connection"
docker compose logs mcp-proxy | grep "route configured"
```
</CodeGroup>
The containers take a few seconds to start, so rerun the commands if they come back empty. If cloudflared never registers, see [Troubleshooting](/docs/connectors/mcp-tunnels/troubleshooting#the-tunnel-stack-starts-but-cloudflared-never-connects). The end-to-end check happens from Claude, in the next section.
## Add tunneled servers as connectors
Each route becomes a custom connector for your organization. The connector URL is the route's tunnel hostname plus the path your MCP server serves. Many servers serve at `/mcp`, and the proxy forwards the path unchanged.
1. In claude.ai, go to **Organization settings > Connectors**.
2. Select **Add**, then **Custom**. If Claude asks for the connector type, choose **Web**.
3. Enter the server URL, for example `https://docs.abc123.tunnel.anthropic.com/mcp`.
4. Configure authentication for the server. If its OAuth authorization server is also inside your network, turn on **Tunnel OAuth configuration** and follow [Authenticate to MCP servers behind a tunnel](/docs/connectors/mcp-tunnels/oauth).
5. Select **Add**.
Members then find the connector in their own connector settings and select **Connect** to sign in, as described in [Third party connectors with remote MCP](/docs/connectors/custom/remote-mcp#adding-custom-connectors). To confirm the tunnel end to end, connect the server yourself and ask Claude to use one of its tools while you watch the proxy logs for the request.
### Add more servers later
Add a route for the new server, apply the change, and register the new hostname as another custom connector. No certificate or cloudflared changes are needed, because the server certificate covers every subdomain of your tunnel domain.
<CodeGroup>
```bash Helm theme={null}
# After adding the route under gateway.config.routes in values.yaml
helm upgrade mcp-tunnel \
oci://us-docker.pkg.dev/anthropic-public-registry/charts/mcp-tunnel \
--version 2.0.2 \
-n mcp-tunnel \
-f values.yaml
```
```bash Docker Compose theme={null}
# After adding the route in config/mcp-proxy.yaml
docker compose restart mcp-proxy
Cut at 300 lines. The page has the rest.
connectors/mcp-tunnels/troubleshooting New page · 131 lines, new page
# Troubleshoot MCP tunnels ## Connection ### The tunnel stack starts but cloudflared never connects ### cloudflared logs `failed to sufficiently increase receive buffer size` ### The setup component fails with an authentication or permission error ### Setup fails with `Organization already has the maximum of 10 non-archived Tunnels` ### Helm install fails with a hook error ### A tunnel hostname does not respond to curl or a browser ## Routes and certificates ### Proxy logs `no route for host` ### Proxy logs `IP validation failed` ### Proxy exits with `cannot unmarshal !!seq into map[string]string` ### Proxy exits with `invalid upstream (must be scheme://host:port)` ### Proxy logs `tls handshake failed` ## Connectors and tools ### Adding the connector fails, or it connects but no tools appear ## OAuth sign-in ### Sign-in redirects to a tunnel address that does not load ### Sign-in succeeds in the browser but the connector never connects ### The Token endpoint field rejects the URL ### The Tunnel OAuth configuration toggle is missing ## Get help
A whole new page. There's nothing to diff it against, so here is what it says.
# Troubleshoot MCP tunnels
> Fix MCP tunnel problems: cloudflared won't connect, connector added but tools don't appear, no route for host, IP validation failed, TLS handshake failed, expired certificate, OAuth sign-in redirects to a tunnel.anthropic.com URL, token exchange fails, and setup or Helm hook errors.
<Note>
MCP tunnels are in research preview and are available to organizations on the Claude Enterprise plan by request. To request access, contact your Anthropic account team.
</Note>
A request through an [MCP tunnel](/docs/connectors/mcp-tunnels/overview) can fail at three points, and it helps to check them in order. First the outbound connection from cloudflared to the tunnel edge, then the inner TLS handshake between Anthropic and your proxy, then the proxy's routing to your MCP server. The cloudflared and proxy logs on your side show which point a request reached. If the proxy logs nothing at all for a request, it never arrived in your network.
<CodeGroup>
```bash Helm theme={null}
kubectl -n mcp-tunnel logs deploy/mcp-tunnel -c cloudflared
kubectl -n mcp-tunnel logs deploy/mcp-tunnel -c mcp-proxy
```
```bash Docker Compose theme={null}
docker compose logs cloudflared
docker compose logs mcp-proxy
```
</CodeGroup>
For proxy configuration fields and certificate rules referenced below, see the [MCP tunnels reference](https://platform.claude.com/docs/en/agents-and-tools/mcp-tunnels/reference). The [platform troubleshooting guide](https://platform.claude.com/docs/en/agents-and-tools/mcp-tunnels/troubleshooting) covers the same stack and applies to claude.ai tunnels as well, apart from its Console-specific steps.
## Connection
### The tunnel stack starts but cloudflared never connects
cloudflared logs four `Registered tunnel connection` lines when it reaches the tunnel edge. If they never appear, the cause is almost always one of two things. Either `TUNNEL_TOKEN` is missing, truncated, or from a token that has since been rotated, or a firewall is blocking outbound TCP and UDP on port 7844 to the edge ranges `198.41.192.0/19` and `2606:4700:a0::/44`. On Docker Compose, confirm the variable is exported in the shell that ran `docker compose up`. After a token rotation, restart cloudflared on every host with the new value.
### cloudflared logs `failed to sufficiently increase receive buffer size`
This is a QUIC tuning hint, not an error, and the tunnel works without addressing it. To remove the warning, raise the host's UDP buffer limits as described in the [quic-go UDP buffer documentation](https://github.com/quic-go/quic-go/wiki/UDP-Buffer-Sizes).
### The setup component fails with an authentication or permission error
The setup component authenticates to the Tunnels API with the key in `API_TOKEN` (Docker Compose) or `api.token` (Helm). A `401` or `403` means the key was revoked, was copied incompletely, or was created in a different organization from the one where MCP tunnels are enabled. Create a new key under **Organization settings > Tunnels > Tunnels API** in the claude.ai organization whose members will use the connectors, and run setup again.
### Setup fails with `Organization already has the maximum of 10 non-archived Tunnels`
Each organization can have at most 10 tunnels that are not archived, and every setup run with an empty `tunnel.id` (Helm) or no `--tunnel-id` (Docker Compose) creates a new one. Archive tunnels you no longer use, as described in [Remove a tunnel](/docs/connectors/mcp-tunnels/setup#remove-a-tunnel), then run setup again. To attach a new deployment to an existing tunnel instead of creating one, set `tunnel.id` in `values.yaml` or pass `--tunnel-id` to the setup command.
### Helm install fails with a hook error
The setup component runs as a pre-install hook Job, and on failure Helm leaves the Job behind for inspection. Read its logs, then delete it before retrying, because Helm does not manage hook resources.
```bash theme={null}
kubectl -n mcp-tunnel logs job/mcp-tunnel-setup
helm uninstall mcp-tunnel -n mcp-tunnel
kubectl -n mcp-tunnel delete job mcp-tunnel-setup
```
### A tunnel hostname does not respond to curl or a browser
This is expected. Hostnames under `tunnel.anthropic.com` accept connections only from Claude, so you can't test them from your own network or the internet. Verify the tunnel by connecting the custom connector in Claude and calling one of the server's tools while you watch the proxy logs.
## Routes and certificates
### Proxy logs `no route for host`
The hostname Claude sent the request to did not match any route. Check that `tunnel_domain` in the proxy configuration exactly matches the domain the setup component reported (Helm sets this for you), that the subdomain in the connector URL matches a key under `routes`, and that you restarted the proxy or ran `helm upgrade` after editing routes.
### Proxy logs `IP validation failed`
The full message is `IP validation failed: <ip> is not a private address`, and it means the MCP server's hostname resolved to an address outside the ranges the proxy is allowed to dial. By default those are the RFC 1918 private ranges, over IPv4 only. Check what the hostname resolves to from the proxy's host:
```bash theme={null}
dig +short docs-mcp.example.corp
```
If the address is legitimate, for example a Kubernetes Service range that your distribution allocates outside RFC 1918, add the narrowest covering range to `upstream.allowed_ips`. Setting `allowed_ips` replaces the default rather than extending it, so list the private ranges your other servers use as well.
```yaml theme={null}
upstream:
allowed_ips:
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16
- 100.64.12.0/22 # example: a cluster Service range outside RFC 1918
```
<Warning>
Don't set `0.0.0.0/0` or `disable_ip_validation` outside of isolated testing. IP validation is the proxy's protection against server-side request forgery.
</Warning>
### Proxy exits with `cannot unmarshal !!seq into map[string]string`
`routes` was written as a YAML list. It must be a map from subdomain to upstream URL, for example `routes: { docs: "http://docs-mcp.example.corp:8080" }`.
### Proxy exits with `invalid upstream (must be scheme://host:port)`
A route value includes a path or omits the port. Each upstream must be exactly `scheme://host:port`. Put the path in the connector URL instead, because the proxy forwards the request path unchanged.
### Proxy logs `tls handshake failed`
Anthropic rejected the certificate the proxy presented. Check that the server certificate has not passed its 90-day validity, that its Subject Alternative Name covers `*.<your-tunnel-domain>`, and that it was signed by the CA the setup component registered for this tunnel. On Docker Compose, also confirm the files in `data/` are readable by user ID `65532`. To renew an expired certificate, see [Rotate credentials](/docs/connectors/mcp-tunnels/setup#rotate-credentials).
## Connectors and tools
### Adding the connector fails, or it connects but no tools appear
Work through these checks in order.
1. Confirm the stack is connected, using the log checks in [Verify the connection](/docs/connectors/mcp-tunnels/setup#verify-the-connection).
2. Confirm the tunnel was created with a Tunnels API key from the same claude.ai organization where you are adding the connector. A tunnel created from another organization, including a Claude Console organization, is refused before any traffic reaches your network, and your proxy logs show nothing.
3. Confirm the connector URL includes the path your MCP server serves, such as `/mcp`. A request to the bare hostname reaches the proxy but the server may answer `404`.
4. Watch the proxy logs while you retry. `no route for host` and `IP validation failed` point to the sections above. An upstream connection error means the proxy can't reach the MCP server from where it runs.
## OAuth sign-in
See [Authenticate to MCP servers behind a tunnel](/docs/connectors/mcp-tunnels/oauth) for how the sign-in flow splits between the member's browser and Claude's servers.
### Sign-in redirects to a tunnel address that does not load
The authorization server's metadata advertises an authorization endpoint on the `tunnel.anthropic.com` hostname or an internal hostname, and the member's browser can't load it. Turn on **Tunnel OAuth configuration** for the connector and set **Authorization endpoint** to the sign-in URL members' browsers can reach, or publish split metadata from the authorization server. Both are described on the [OAuth page](/docs/connectors/mcp-tunnels/oauth#set-the-tunnel-oauth-configuration).
### Sign-in succeeds in the browser but the connector never connects
Claude could not complete the token exchange, usually because the token endpoint in the metadata is an internal hostname or sits behind a source-IP allowlist. Add a proxy route for the authorization server and set **Token endpoint** (and **Registration endpoint**, if you rely on dynamic client registration) to the corresponding `https://<route>.<your-tunnel-domain>/...` URLs. Then watch the proxy logs during sign-in to confirm the token request arrives and the authorization server answers it.
### The Token endpoint field rejects the URL
**Token endpoint** and **Registration endpoint** accept an `https://` URL that is either under `tunnel.anthropic.com` or on the same origin (scheme, host, and port) as the **Authorization endpoint**. Any other URL is rejected, because Claude sends the token exchange and the client credentials to it. Add a route for your authorization server as shown in [Route the authorization server through the tunnel](/docs/connectors/mcp-tunnels/oauth#route-the-authorization-server-through-the-tunnel) and enter the resulting tunnel URL. The **Authorization endpoint** field has no such restriction, because browsers load it directly.
### The Tunnel OAuth configuration toggle is missing
Anthropic enables the option for each organization in the research preview on request. Contact your Anthropic account team.
## Get help
If these steps don't resolve the problem, contact your Anthropic account team with the tunnel domain, the time of a failed request, and the relevant cloudflared and proxy log lines.