Skip to content
Status

MCP Gateway / Tunneled MCP servers

Tunneled MCP servers

Give an MCP server running inside a private network a hosted URL, without opening any inbound connectivity.

A tunneled MCP server is an MCP server that runs inside a private network, on a laptop, in a VPC, or behind a corporate firewall, and is reached through the platform without any inbound connectivity. Tunneled servers are created and managed from MCP Gateway > MCP in the project sidebar. For a step-by-step walkthrough that deploys a server, verifies signed caller identity, grants team access, and sets up high availability, follow Add an internal MCP.

Tunneling is enabled per organization. Until it is, the Reachable through a tunnel option is absent from the Add new page, and the plan limits below still apply once it is on.

Viewing a tunneled server requires the mcp:read scope. Creating one, rotating its tunnel key, enabling public access, and editing its settings require mcp:write, which the default Admin role holds and the Member role does not.

A lightweight tunnel agent runs next to the private server and opens a single outbound WebSocket connection to the platform’s tunnel gateway. The gateway multiplexes per-request streams back down that connection. Nothing in the private network is exposed directly, and no inbound firewall rule is needed.

Once connected, a tunneled server behaves like any other MCP server. It gets a hosted URL, goes through the same proxy stack as remote servers, and picks up authentication, usage metering, team access, and tool logs, where calls appear with the Tunneled MCP target type.

Tunneling connects the platform to a private backend. To restrict the client-facing hosted URL to a tailnet, see Tailscale private access. These controls can be used together.

PlanTunneled MCP servers
Free0
Pro10
Enterprise25

Creating a server past the limit fails with “tunneled mcp server limit reached”. Limits are counted per organization across all projects, and an override can be configured for a specific organization.

Add a tunneled server from the MCP page: click Add new and choose Reachable through a tunnel, described as connecting a server running inside the organization’s own network through a tunnel. The New tunneled MCP server form asks for a display name; Add server creates it. The Tunneled MCP server added screen that follows issues a tunnel key and generates ready-to-copy setup commands for Docker, Kubernetes, or the CLI. Under Tunnel endpoint, choose Existing server to template the snippet against an MCP server already running in the private network, or New server to deploy a sample hello-world MCP server alongside the tunnel agent. New server is the fastest way to see the flow end to end.

Creating a tunneled source also links an MCP server with a default endpoint, so MCP clients connect to it like any other hosted server. When the tunnel was started from a gateway endpoint, the key screen ends with Add to gateway, which attaches the new server and returns to the gateway.

The tunnel key is shown once, at creation, and once more when it is rotated. Only a hash of it is stored, so it cannot be retrieved later. Losing it means rotating the key and reconfiguring the tunnel agent.

The tunnel agent reads its configuration from environment variables. There is no config file and there are no command-line flags.

VariableRequiredPurpose
TUNNEL_GATEWAY_URLYesThe gateway to dial
TUNNEL_KEYYesThe tunnel key issued at creation
TUNNEL_LOCAL_MCP_URLYesThe private MCP endpoint to proxy to
TUNNEL_SERVICE_VERSIONYesA version string recorded with the session
TUNNEL_METADATANoA JSON object of string values, up to 1024 bytes serialized

Two constraints apply when deploying:

  • The gateway URL must use wss:// or https://. Plain ws:// is accepted only for localhost, host.docker.internal, or a loopback address, so an unencrypted tunnel to a real gateway is not possible.
  • The local MCP URL is pinned when the tunnel agent starts. The gateway cannot redirect the traffic of the tunnel agent to a different local address.

The tunnel agent reconnects on its own if the connection drops, backing off from half a second up to 30 seconds with jitter, and resetting the backoff once a session has been stable for 30 seconds.

The same snippets are available later from the Agent Setup section of the server’s Settings tab, with a placeholder where the tunnel key goes, because the key itself is shown only when issued or rotated.

When a tunnel agent drops its session while requests are in flight, each affected tool call comes back to the client as a JSON-RPC error rather than an HTTP 502. The message reads “The connection to the MCP server was interrupted before it responded. The request may have already run.” with the code upstream_disconnected and retryable: false, because the call may have executed before the connection broke. A tunnel that is saturated instead answers with the code service_unavailable and retryable: true, and a client can safely retry that one.

The source’s detail view shows connection status as Connected, Never connected, or Inactive, along with live tunnel sessions, their heartbeats and tunnel agent versions, and any active request streams. The server’s Overview tab shows the same live connections panel above the usage dashboard, with a link to Agent Setup when nothing has connected yet.

A newly created server stays in a created state until a tunnel agent connects for the first time. That first connection flips it to active and records the tunnel agent version.

Rotate the key from the Tunnel Key section of the server’s Settings tab. Rotating the tunnel key issues a new key and invalidates the old one. Running tunnel agents disconnect and need the new key to reconnect. Rotation also clears the tunnel’s runtime state and purges any anonymous sessions.

Deleting the source removes its linked MCP servers and endpoints along with it.

Public visibility is the one thing tunneled servers can do that other backends cannot, and it takes two separate opt-ins:

  • The tunnel source owner enables public access in the Public Access section of the server’s Settings tab. The section warns that anyone who can reach the endpoint URL can then call every tool the tunneled source exposes, and enabling it requires typing a confirmation phrase.
  • The MCP server’s visibility is set to Public from the status control in the detail sidebar. Until the first step is done, the option stays disabled with the note “Enable public access on the tunnel source first to allow anonymous serving.”

A public tunneled server is rate limited for anonymous callers. The Requests per second limit defaults to 50 and the Burst defaults to twice the rate, and both can be changed on the Settings tab.

Until the source owner consents, the public option on the server stays disabled. Both conditions are checked before a request is dispatched, so a caller is never sent through an OAuth challenge for a server that would refuse to serve them anyway.

A public tunneled server accepts anonymous callers and skips the authentication gate entirely. Every tool the private server exposes becomes reachable by anyone with the URL, and any credential attached to the server is spent on their behalf. Turning the source’s consent back off immediately purges existing anonymous sessions.

A tunneled server’s Settings tab carries a few sections that other backends lack:

  • Resource Identifier sets an optional protected resource identifier, such as the private URL of the upstream, advertised to clients that need it. Clearing the field unsets it.
  • Network access chooses whether clients connect through public MCP routes, the organization’s private network ingress, or both. When private routes are enabled, the detail sidebar shows a Private URL next to the Public URL, and private endpoints get their own installation page hosted on the platform domain, available to organization admins. See Custom Domains for the organization-level network settings this depends on.
  • Tool Filtering and the Authentication section work as they do on a remote server.