Most write-ups about exposing an MCP server stop at the step where you get a public URL. That's the easy half.
The half that actually costs you time is deciding who's allowed to call it — and being able to cut them off without a redeploy.
If you run an MCP server on your laptop, you've probably noticed it works in Claude Desktop and nowhere else. stdio transport means the client spawns the process locally. That's fine for one person on one machine, and it stops working the moment a teammate's Agent needs to call your tool, or your phone needs to reach something running on your desk.
This post walks through five options, what each one actually costs you, and when to pick something else instead. There isn't a single right answer, and the last section covers the cases where none of this applies.
What "reachable" actually requires
Before comparing tools, it's worth naming the four things you need:
- A stable address — something that doesn't change every time you reconnect
-
Transport —
stdiowon't work across machines, so you need HTTP or SSE - Authentication — whoever connects needs to prove they're allowed
- Revocation — the ability to cut someone off without restarting anything
Most tools solve 1 and 2 and leave you to hand-roll 3 and 4. That's where the real work is.
Option 1: ngrok (and similar tunnels)
The default answer. One command and you have a public HTTPS URL.
ngrok http 3000
What you get: transport and a temporary address, in about three seconds.
What you don't get:
A stable address. Free-tier URLs change on every restart. Every caller has to be re-told the new one.
Per-caller auth. There's one URL. If two people have it, they have the same access.
Granular revocation. You can kill the tunnel, which cuts everyone off.
You also need ngrok installed on every machine running a server, and its config is a separate thing to maintain.
For a quick demo this is unbeatable. For anything with more than one caller, it gets thin fast.
Option 2: Cloudflare Tunnel
Better on the address problem — you get a stable hostname on a domain you control.
cloudflared tunnel create my-mcp
cloudflared tunnel route dns my-mcp mcp.example.com
What you get: stable address, transport, and Cloudflare's auth layer if you wire up Access.
What it costs: a domain, DNS management, and a Cloudflare account. Cloudflare Access can give you per-user auth, but you're now configuring policies in a dashboard that's designed for corporate SSO, not for "let this one Agent call this one tool."
It's a solid choice if you already run Cloudflare. If you don't, you're adopting a whole platform to solve one problem.
Option 3: Just open a port
Don't. Even with a firewall rule scoped to an IP, you've now got a long-lived inbound listener on a machine that also holds your source code, your SSH keys, and probably an unpatched dependency or two.
This is the option people fall back to when the others feel heavy, and it's the one that shows up in incident reports.
Option 4: Deploy it to a VPS
Move the server off your laptop entirely. Now it's always on, has a real IP, and you can put nginx in front of it.
The catch is that half the reason the server was local is that it talks to local things — a Postgres instance, files on disk, a GPU you paid for. Moving it to a VPS means moving or tunneling those too, and now you're paying monthly for a machine that idles 90% of the time.
Option 5: Outbound-only connection with a stable subdomain
This is the model I ended up using, via a service called UUMit. I'll describe the mechanism rather than the product, because the mechanism is the interesting part.
The server on your machine opens an outbound connection to a relay. Nothing listens on your machine. Your address is assigned at signup — yourname.uumit.ai — and stays attached to you even if you move to a different computer.
Inbound requests arrive through the relay and are pushed down the existing outbound connection. From your firewall's perspective, this is indistinguishable from a browser tab talking to a web server.
The parts that matter beyond transport:
Identity is signed. Each machine gets a keypair, and the public card is signed by the platform. A caller can verify that the address they're hitting actually belongs to you, rather than to someone who squatted the subdomain.
Permissions are per-caller, not per-network. Four levels — off, only me, invite only, everyone — set per exposed service. Opening one thing doesn't open the others.
Revocation is a state change, not a restart. You flip it, the next request is rejected. No tunnel to kill, no process to bounce.
Calls can be metered. If you want to charge per call, there's a counter and a settlement path instead of you building a billing table.
Setting it up is one prompt into your Agent, or one command:
Install UUMit and claim the address xiaolong
Then you point it at the services already running locally. The desktop client supports macOS, Windows 10/11, Linux, and Docker.
When none of this applies
Three cases where I'd tell you not to bother:
Your machine isn't always on. A relay can't reach a laptop that's asleep. If your server needs to be up 24/7, put it somewhere that is.
You need sub-100ms latency. Every hop through a relay adds some. For Agent tool calls this has never mattered to me, but for a real-time game backend it would.
You're serving thousands of callers. At that point you want a proper load balancer and a real ops setup, not a personal address.
There's also the honest limitation that this is a young product. If you need an SLA and a support contract, use Cloudflare.
What I'd actually recommend
| Your situation | Use |
|---|---|
| Demo, one-off, nobody else needs it | ngrok |
| Already on Cloudflare, want per-user SSO | Cloudflare Tunnel |
| Server is stateful and tied to local resources | Outbound relay with a stable address |
| Needs 24/7 uptime and an SLA | Deploy to a VPS |
The thing I'd push back on is treating "reachable" as the whole problem. Transport is the easy half. Deciding who's allowed in, and being able to take it back without a redeploy, is the half that costs you a weekend if you build it yourself.
Top comments (1)
This is incorrect from an ngrok perspective. Free tier gets a static URL. There is also auth and traffic policy to lock it down.