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