MCP servers typically run locally. Your filesystem tools, database connectors, and RAG pipelines live on localhost:3000 or speak stdio. Cloud-hosted agents (ChatGPT, Claude, Cursor) cannot reach them. MCPTunnels solves this by giving any stdio MCP server a public HTTPS URL with OAuth 2.1 gating, no account required. One command spawns the server, bridges stdio to Streamable HTTP, and prints a URL that expires in 24 hours.
This is the first public ngrok-style solution purpose-built for MCP. It removes the stdio-to-HTTP bridging step, generates ephemeral OAuth credentials, and tears down the tunnel when you hit Ctrl-C. The hosted relay at tunnel.mcptunnels.xyz is the default, but you can self-host tunneld if you control the transit path.
The security model is simple: all tunnel traffic transits the relay operator, and OAuth passwords are the only boundary. This makes MCPTunnels useful for demos, development, and throwaway tool servers. It is explicitly not for production workloads or private data.
How the Tunnel Works
MCPTunnels wraps your MCP server in three layers:
-
Stdio bridge: The CLI spawns your server command (e.g.,
npx @modelcontextprotocol/server-everything) and translates stdio JSON-RPC to HTTP. -
Relay connection: The CLI opens a persistent WebSocket to the relay, which assigns a public URL like
https://tunnel.mcptunnels.xyz/t/q-3k9x2mab7c/s/mcp. -
OAuth gate: The relay generates a password (e.g.,
9f2c1ab4e7d03815a6c02b94) and enforces OAuth 2.1 client credentials flow. Clients like ChatGPT discover the flow automatically via MCP'soauthcapability.
When a remote agent calls a tool:
- The agent hits the public URL with an OAuth token.
- The relay validates the token, forwards the request over the WebSocket to your CLI process.
- Your CLI translates the HTTP request back to stdio JSON-RPC, sends it to the local MCP server, and streams the response back through the relay.
The tunnel dies when you Ctrl-C the CLI or after 24 hours, whichever comes first. The relay deletes the URL and password server-side immediately.
OAuth Scope and Token Lifetime
MCPTunnels uses OAuth 2.1 client credentials flow. The relay generates a single password per tunnel, and the agent exchanges it for an access token. The token is scoped to the tunnel URL, not to individual tools or sessions.
Token lifetime: The relay does not publish token expiration details in the documentation. In practice, the 24-hour tunnel TTL is the effective upper bound. If the token expires before the tunnel does, the agent must re-authenticate.
Scope granularity: There is no per-tool or per-session revocation. The password grants access to all tools exposed by the MCP server for the life of the tunnel. If you want finer-grained control, you must run multiple tunnels with separate passwords or implement tool-level authorization inside your MCP server.
Revocation: Stopping the CLI process (Ctrl-C) deletes the tunnel and invalidates the password. There is no web dashboard or API to revoke a tunnel remotely. If you lose control of the CLI process, the tunnel stays live until the 24-hour TTL expires.
Failure Modes and State Management
Tunnel dies mid-session: If the CLI crashes or the WebSocket drops, the relay marks the tunnel as dead. The agent's next tool call returns an HTTP error (likely 502 or 503). The agent's retry behavior depends on its implementation. Claude and ChatGPT typically retry once, then surface an error to the user. Cursor may retry more aggressively.
Orphaned state on the local MCP server: If the agent sends a stateful tool call (e.g., starting a database transaction) and the tunnel dies before the commit, the local server may hold locks or partial state. MCPTunnels does not track or clean up server-side state. Your MCP server must implement idempotency or timeout-based cleanup.
Relay outage: If tunnel.mcptunnels.xyz goes down, all tunnels die. Self-hosting tunneld moves this risk to your infrastructure. The relay is a single point of failure by design.
Security Boundaries and Blast Radius
MCPTunnels makes three security trade-offs:
- Transit visibility: The relay operator sees all tool calls and responses in plaintext. The WebSocket is TLS-encrypted, but the relay terminates TLS and can log or inspect traffic.
- Password entropy: The generated password is 24 hex characters (96 bits of entropy). This is strong enough to resist brute force during the 24-hour window, but not strong enough to treat as a long-term secret.
- No per-tool ACLs: The password grants access to every tool the MCP server exposes. If your server includes both read-only tools (list files) and write tools (delete files), the agent can call both.
| Risk | Mitigation | Residual Exposure |
|---|---|---|
| Relay operator logs tool calls | Self-host tunneld
|
Requires infrastructure and TLS cert management |
| Compromised password | 24-hour TTL, Ctrl-C revocation | No remote revocation API |
| Agent calls destructive tools | Implement tool-level auth in MCP server | Requires custom server logic |
| Tunnel URL leaked | OAuth password still required | Password may be in same leak (e.g., screenshot) |
When to use --no-auth: The CLI supports --no-auth for open URLs with no OAuth gate. This is useful for public demos where you want anyone to call your tools. The blast radius is total: anyone with the URL can call any tool until the tunnel expires.
Comparison to Generic Tunnels
MCPTunnels is MCP-shaped. Generic tunnels (ngrok, cloudflared, bore) forward raw TCP or HTTP. They do not understand stdio, JSON-RPC, or MCP capabilities.
| Feature | MCPTunnels | ngrok | cloudflared | bore |
|---|---|---|---|---|
| Takes MCP server command | Yes | No (ports only) | No | No |
| stdio → HTTP bridging | Yes | No | No | No |
| No account required | Yes | No | No (named tunnels) | Yes |
| Self-hostable relay | Yes | No | No | Yes |
| Ephemeral URLs by default | Yes (24h) | No | No | Yes |
If your MCP server already listens on a port with its own HTTP transport, a generic tunnel works fine. MCPTunnels removes the bridging step and adds MCP-aware OAuth.
Deployment Shape
Local development: Run mcptunnel expose -- npx -y @modelcontextprotocol/server-everything. The CLI spawns the server, prints a URL, and blocks until you Ctrl-C.
CI or ephemeral environments: Wrap the CLI in a process manager (systemd, Docker, Kubernetes Job). The tunnel lives as long as the process does. No persistent state to manage.
Self-hosted relay: Clone the repo, build tunneld, and run it behind a reverse proxy with a wildcard TLS cert (e.g., *.tunnel.yourdomain.com). The relay stores active tunnels in memory. Restarting the relay kills all tunnels.
Observability: The CLI logs tunnel creation, OAuth handshakes, and tool calls to stderr. The relay does not expose metrics or structured logs by default. You must add instrumentation if you self-host.
Code Example: Spawning a Tunnel Programmatically
import { spawn } from 'child_process';
const tunnel = spawn('mcptunnel', [
'expose',
'--',
'npx',
'-y',
'@modelcontextprotocol/server-everything'
]);
let tunnelUrl: string | null = null;
let password: string | null = null;
tunnel.stderr.on('data', (data) => {
const line = data.toString();
// Parse tunnel URL from CLI output
const urlMatch = line.match(/https:\/\/tunnel\.mcptunnels\.xyz\/t\/[\w-]+\/s\/mcp/);
if (urlMatch) {
tunnelUrl = urlMatch[0];
}
// Parse OAuth password
const pwMatch = line.match(/password: ([\w]+)/);
if (pwMatch) {
password = pwMatch[1];
}
if (tunnelUrl && password) {
console.log(`Tunnel ready: ${tunnelUrl}`);
console.log(`OAuth password: ${password}`);
}
});
tunnel.on('exit', (code) => {
console.log(`Tunnel exited with code ${code}`);
});
// Graceful shutdown
process.on('SIGINT', () => {
tunnel.kill('SIGTERM');
});
This spawns the tunnel as a child process, parses the URL and password from stderr, and forwards SIGINT to tear down the tunnel cleanly.
Technical Verdict
Use MCPTunnels when:
- You need to demo a local MCP server to a remote agent (ChatGPT, Claude, Cursor) without deploying infrastructure.
- You are developing an MCP server and want to test it against a real client quickly.
- The tools you are exposing are read-only or operate on throwaway data.
- You trust the relay operator (or self-host the relay).
Avoid MCPTunnels when:
- Your MCP server accesses private data, credentials, or production systems.
- You need per-tool or per-session access control.
- You need audit logs, metrics, or compliance guarantees.
- You need tunnels that live longer than 24 hours or survive CLI restarts.
- You cannot tolerate the relay operator seeing tool calls in plaintext.
For production workloads, run your MCP server with its own HTTP transport behind a reverse proxy with mTLS, API keys, or OAuth tied to your identity provider. MCPTunnels is a development and demo tool, not a production security boundary.
Top comments (0)