Skip to main content

Secure MCP Tunnels

Cloud AI clients, ChatGPT / OpenAI connectors, Claude / Anthropic connectors, hosted agent platforms, often need to reach an MCP server that runs on your laptop or inside a private network. A tunnel gives that server a public URL. But reachability is not authorization: once the server is reachable, anything holding the URL can call any tool it exposes.

The fix is to split the two concerns:

  • The tunnel makes the server reachable.
  • Vengtoo decides what each caller is allowed to do: every tools/call is authorized before it executes, and every call is logged.

Tunnel and policy brain are complementary. This guide wires them together.

Vengtoo does not provide the tunnel: you use Cloudflare Tunnel (or ngrok) for reachability. Vengtoo is the authorization checkpoint the tunnel routes through, not the tunnel itself.

How it works

The authorization point speaks the same trivial contract every Vengtoo gateway uses, 2xx = ALLOW, non-2xx = DENY, so an unauthorized tool call is stopped before it reaches your MCP server. The decision itself is fail-closed: if the PDP is unreachable, the call is denied, not waved through.

note

A tunnel changes where the server is reachable from, nothing else. The Vengtoo wiring below is identical whether the MCP server runs on localhost, in a container, or on a private VPC host.

Option A: Cloudflare Tunnel → Vengtoo MCP Gateway

Run the MCP Gateway over HTTP and point a Cloudflare Tunnel at it. The gateway proxies your MCP server, authorizes every call, and is already hardened for public exposure. Use this when you don't already run a gateway of your own.

1. Run the gateway over HTTP

vengtoo-mcp-gateway --config gateway.config.json --http --port 8808
{
"vengtoo": {
"cloudUrl": "https://pdp.vengtoo.com/access/v1/evaluation",
"apiKey": "vgt_..."
},
"transport": "http",
"http": {
"port": 8808,
"host": "127.0.0.1",
"path": "/mcp",
"callers": [
{ "token": "<caller-token>", "subject": "agent:chatgpt" }
]
},
"servers": {
"database": { "command": "node", "args": ["./my-database-mcp-server.js"] }
}
}

Because a tunnel fronts it, you can keep the gateway bound to 127.0.0.1: only cloudflared (running on the same host) needs to reach it. Configure http.callers so each bearer token authorizes as its own subject; that identity is what Vengtoo evaluates policies against and what shows up in the Decision Log.

tip

If you ever bind the gateway to a non-loopback interface directly (no tunnel), it refuses to start without caller auth; see Securing a public endpoint. Behind a tunnel, loopback + callers is the clean default.

2. Expose it with Cloudflare Tunnel

Install cloudflared and authenticate:

brew install cloudflared # or: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/
cloudflared tunnel login

The quickest path, an ephemeral tunnel to your gateway port:

cloudflared tunnel --url http://localhost:8808
# prints a public https URL, e.g. https://random-name.trycloudflare.com

Your cloud client reaches the gateway at https://<hostname>/mcp.

For a stable, named hostname (anything beyond a quick demo), create a named tunnel and route a DNS record to it:

cloudflared tunnel create vengtoo-mcp
cloudflared tunnel route dns vengtoo-mcp mcp.example.com
# ~/.cloudflared/config.yml
tunnel: vengtoo-mcp
credentials-file: /Users/you/.cloudflared/<tunnel-id>.json

ingress:
- hostname: mcp.example.com
service: http://localhost:8808
- service: http_status:404
cloudflared tunnel run vengtoo-mcp

Your MCP endpoint is now https://mcp.example.com/mcp, backed by the gateway, and every tool call is authorized by Vengtoo before it runs.

3. Point your cloud client at the tunnel URL

Give the client the tunnel hostname as the MCP server URL, plus the bearer token you configured in http.callers. See Cloud MCP clients below.

Option B: Tunnel to a gateway that speaks ext_authz

If you already front your MCP server with Kong or agentgateway, keep them: they call Vengtoo's ext_authz authorizer directly, and the tunnel simply exposes the gateway's public port.

The gateway POSTs each MCP tools/call body to POST https://pdp.vengtoo.com/mcp/v1/ext-authz and enforces the verdict (2xx → proxy to the upstream, non-2xx → 403, upstream never hit). This is the same argument-level path: the deny can be on a destructive SQL statement, not just a tool name.

Point cloudflared at the gateway's listen port instead of 8808; everything else in Option A applies.

Option C: Enforce at the edge with a Cloudflare Worker

For teams already on Cloudflare, you can authorize at the edge before the request even enters the tunnel: a Worker in front of the tunnel calls the PDP and passes or blocks the request.

// Worker (sketch): authorize the MCP tool call, then forward to the tunneled server.
export default {
async fetch(request, env) {
const body = await request.clone().text();
const decision = await fetch("https://pdp.vengtoo.com/mcp/v1/ext-authz", {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": `Bearer ${env.VENGTOO_API_KEY}`,
},
body, // the raw MCP JSON-RPC message
});
if (!decision.ok) return new Response("Forbidden", { status: 403 }); // non-2xx = DENY
return fetch(request); // ALLOW → forward to the origin/tunnel
},
};
note

This is the standard ext_authz contract expressed as a Worker: the same 2xx/DENY semantics as Option B, just enforced at Cloudflare's edge. Tenant is resolved from your API key, never from the request.

Cloud MCP clients

Hosted assistants connect to a remote MCP server by URL and act as MCP clients. Point them at the tunnel hostname (Option A: https://mcp.example.com/mcp); the request lands on the Vengtoo authorization point, which authorizes and proxies to your real MCP server. Nothing about the client changes: it just sees a normal remote MCP server.

ChatGPT / OpenAI connectors

Add the tunnel URL as a custom MCP connector / server and supply the bearer token as the connector's auth. Tool calls flow through the tunnel to the gateway, and Vengtoo authorizes each one.

Claude / Anthropic connectors

Add the tunnel URL as a custom connector (remote MCP server) with the bearer token. Same wiring: the authorization boundary sits at the tunnel edge, in front of your MCP server.

warning

The exact place to paste a remote MCP server URL, and how auth headers are entered, changes as these products evolve; follow the current OpenAI / Anthropic connector docs for the UI. The Vengtoo side (tunnel → gateway → ext_authz → MCP server) is unaffected by those UI changes. (The connector-UI specifics are the one part of this guide not verifiable from the Vengtoo references: treat them as directional.)

Alternatives

ngrok works the same way: ngrok http 8808 gives you a public URL in front of the gateway. Cloudflare Tunnel is preferred here because named tunnels give you a stable hostname, no inbound ports, and edge controls (Option C). Any tunnel that forwards HTTP to your gateway port is compatible; the Vengtoo wiring is identical.

Hardening checklist

  • Fail closed. Confirm the authorization point denies on PDP errors (the Vengtoo gateway and the ext_authz path both do). Never fail open on a tunnel.
  • Caller auth. Require a bearer token (http.callers) so each caller is a distinct, policy-evaluated subject, not one shared identity.
  • Named tunnel + DNS for anything persistent, so the URL is stable and revocable.
  • Keep the origin on loopback. Behind a tunnel the gateway only needs 127.0.0.1; don't also expose the port publicly.
  • Per-IP limits on the decision plane are enforced at pdp.vengtoo.com (average 50 req/s, burst 100); add Cloudflare rate-limiting at the edge for the tunnel hostname if you expect abuse.

See also

  • MCP Gateway: the proxy you tunnel to in Option A
  • Vengtoo Agent: evaluate decisions locally, so tool arguments never leave the host