mytunnel binary. Every step has a 1:1 equivalent in ngrok, Cloudflare Tunnel, or any other tunneling service — substitute as you like. Step 5 (the master-key pattern) is currently unique to 21tunnel; see the longer post on master-key delegation for the pattern itself.Exposing an MCP server to the internet: a tunneling guide for Claude Desktop, Cursor, and AI dev tools
You wrote an MCP (Model Context Protocol) server on localhost — maybe it wraps your company's internal API, maybe it exposes a database, maybe it surfaces a custom tool you want Claude to call. By default it's only reachable on your machine. To use it from a hosted Claude session, from Cursor on a different laptop, or from a teammate, you need a public URL. This tutorial walks through the five steps end-to-end — tunneling, config for Claude Desktop, config for Cursor, and the master-key pattern that keeps it safe.
TL;DR
- Start your MCP server in HTTP mode on a local port.
mytunnel http 3000 --subdomain mcp-yourname→ public URL.- Add the URL to
claude_desktop_config.json. - Add the URL to
.cursor/mcp.json. - Don't paste your full-access tunneling token anywhere — use a master key, let the agent mint child keys.
Why expose an MCP server publicly?
MCP servers default to running as child processes of the AI tool that uses them — Claude Desktop launches your MCP server, talks to it over stdio, kills it when the session ends. That works for a single user on a single machine. It breaks for three common cases:
- Hosted Claude (claude.ai) sessions. claude.ai can't spawn a process on your laptop. The only way for hosted Claude to use your local MCP server is via a public HTTPS endpoint.
- Multi-machine workflows. You run the MCP server on your beefy desktop, you want to call it from your laptop while travelling. Stdio-mode means you'd have to copy the server to every machine. A tunnel means one canonical server, reachable from any device.
- Teammate sharing. You wrote an MCP server that integrates with your team's internal tool. Your teammate wants to use it from her Cursor without installing your dependencies. Tunnel + auth, done.
If you only ever use the MCP server from one Claude Desktop on one machine, you don't need this — stdio mode is simpler and faster. The rest of this tutorial assumes you've decided you need a public endpoint.
Before you start
You'll need:
- An MCP server you can run in HTTP mode. The official MCP SDKs (Python
mcp, TypeScript@modelcontextprotocol/sdk) both support HTTP transport in addition to stdio. If your server only supports stdio, swap to HTTP first — most servers handle this with a one-line change. - A tunneling tool. This tutorial uses
mytunnel(the 21tunnel CLI). Substitutengrok,cloudflared,pinggy, or any other tunnel binary in steps 1–2 — they all expose the same shape. - Claude Desktop and/or Cursor installed.
The five steps
1. Start the MCP server locally
Run your MCP server in HTTP-transport mode on a free local port. For a Python server using the official SDK:
# server.py
from mcp.server import Server
from mcp.server.http import run_http_server
app = Server("my-mcp-server")
@app.tool()
def get_user_count() -> int:
"""Returns the current number of users."""
return 42
if __name__ == "__main__":
run_http_server(app, host="127.0.0.1", port=3000) $ python server.py
MCP HTTP server listening on http://127.0.0.1:3000 For a Node / TypeScript server:
// server.ts
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { HttpServerTransport } from "@modelcontextprotocol/sdk/server/http.js";
const app = new Server({ name: "my-mcp-server", version: "0.1.0" });
app.setRequestHandler(/* ... */);
const transport = new HttpServerTransport({ port: 3000 });
await app.connect(transport); Test it before tunneling:
$ curl http://127.0.0.1:3000/mcp/tools
{"tools": [{"name": "get_user_count", ...}]} 2. Tunnel it to a public URL
With the MCP server running on localhost:3000, in a separate terminal:
$ mytunnel http 3000 --subdomain mcp-yourname
Tunnel established:
https://mcp-yourname.21t.dev → http://127.0.0.1:3000 Verify the public URL works:
$ curl https://mcp-yourname.21t.dev/mcp/tools
{"tools": [{"name": "get_user_count", ...}]} If you're using ngrok instead: ngrok http 3000. If you're using Cloudflare Tunnel: cloudflared tunnel --url http://localhost:3000. Same shape; the URL format differs.
Note the URL — you'll paste it into Claude Desktop and Cursor in the next two steps.
3. Wire it into Claude Desktop
Claude Desktop reads MCP server configuration from claude_desktop_config.json. The file lives at:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json - Linux:
~/.config/Claude/claude_desktop_config.json
Add an mcpServers entry pointing to the tunnel URL:
{
"mcpServers": {
"my-mcp-server": {
"transport": "http",
"url": "https://mcp-yourname.21t.dev/mcp"
}
}
} Restart Claude Desktop. In a new conversation, ask Claude: "What tools do you have access to?" — it should list the MCP server's tools. If it doesn't, see the troubleshooting section.
4. Wire it into Cursor
Cursor reads MCP config from .cursor/mcp.json in your project root (or ~/.cursor/mcp.json globally). Same JSON shape as Claude Desktop:
{
"mcpServers": {
"my-mcp-server": {
"transport": "http",
"url": "https://mcp-yourname.21t.dev/mcp"
}
}
} Reload Cursor (or restart). Open the agent panel; the MCP server's tools should appear in the available-tools list.
Cursor and Claude Desktop both follow the same MCP spec, so once you have one working, the other is identical except for the file location.
5. Secure with the master-key pattern
The setup above works but has one problem: anyone who guesses your tunnel URL can call your MCP server's tools. If those tools touch sensitive data (your company API, your files, your database), that's a leak waiting to happen. Two layers of defense:
- Auth at the MCP server. The MCP spec supports bearer tokens. Wrap your tools in a check that requires
Authorization: Bearer <token>. Generate a strong random token, put it inclaude_desktop_config.json'sheaders:
{
"mcpServers": {
"my-mcp-server": {
"transport": "http",
"url": "https://mcp-yourname.21t.dev/mcp",
"headers": {
"Authorization": "Bearer sk_mcp_a1b2c3d4..."
}
}
}
} - Don't paste your full tunneling token anywhere except where the tunnel agent reads it. If the tunnel itself requires an auth token (e.g. to keep the subdomain reserved), use the master-key pattern:
# Mint a long-lived master key, but for tunnel control only
$ mytunnel projects create mcp-server
Project created: proj_mcp
Master key: mtk_master_a1b2c3d4...
# Save the master key in an env var on the machine running the tunnel
$ export MTK_MASTER=mtk_master_a1b2c3d4...
# Run the tunnel — it auto-mints a 24h child key for itself
$ mytunnel http 3000 --subdomain mcp-yourname --project mcp-server Now the tunnel uses a short-lived auto-rotating credential, and the long-lived master key never leaves the machine running the tunnel. If you suspect any compromise, cascade-revoke the master key:
$ mytunnel keys revoke --cascade mtk_master_a1b2c3d4...
Revoked 1 master key.
Revoked N child keys (cascade). For a deeper dive on the pattern, see the delegation post we wrote at the same time as this tutorial.
Anti-patterns to avoid
Four common ways this tutorial setup goes wrong:
- Exposing a stdio-mode MCP server. Stdio MCP servers were designed for parent-process isolation. Wrapping them in a TCP listener with no auth is a recipe for accidentally letting anyone on the internet read your filesystem. Always use HTTP transport with explicit auth when going public.
- Skipping auth because "the subdomain is hard to guess". Security through obscurity doesn't survive Shodan-style internet scanning. Tunnel subdomains get probed within hours. Always add bearer-token auth at the MCP server layer.
- Hardcoding your full-access tunneling token in
claude_desktop_config.json. Claude Desktop's config file is read by every Claude session; it's also synced to iCloud / OneDrive if you're not careful. Tokens there leak. Use the master-key pattern. - Exposing tools that mutate state without read-only fallbacks. An MCP tool called
delete_all_filesexposed to a public URL will eventually be called by something that shouldn't have. Either gate destructive tools behind a per-call confirmation, or keep them out of the publicly-tunneled server entirely (run them on a stdio-mode local server instead).
Troubleshooting
Claude Desktop doesn't see the MCP server after config edit. Make sure you fully quit Claude Desktop (not just close the window) and relaunch. The config is loaded only at startup.
The tunnel URL returns 502. Your MCP server probably isn't running on the port you tunneled. Check with curl http://127.0.0.1:3000/mcp/tools before tunneling.
The MCP server returns 401 from Claude. Bearer-token mismatch. Make sure the token in claude_desktop_config.json's headers exactly matches the one your MCP server validates.
Tools appear but calls hang. The MCP server might be using long-polling or SSE in a way the tunnel doesn't fully support. Try a tunnel service that explicitly supports WebSocket / SSE (21tunnel and Cloudflare Tunnel both do; some basic SSH-based services don't).
Frequently asked questions
What is MCP and why do I need to tunnel it?
MCP (Model Context Protocol) is Anthropic's open standard for connecting AI models to external tools, data sources, and capabilities. MCP servers run locally by default. Tunneling exposes one to the public internet so a hosted Claude, a teammate's Cursor, or Claude Desktop on a different machine can use it.
Can I just use ngrok for this?
Yes, ngrok works fine for steps 1–4. The differentiator is step 5: ngrok doesn't ship a first-class master-key delegation primitive yet. If your MCP server is sensitive and you want bounded blast radius on compromise, you'll want a tunneling service that does (we built 21tunnel for this case).
Is it safe to expose an MCP server publicly?
Safe if you add auth at the MCP server layer (step 5). Never expose an MCP server with stdio-only mode, no auth, or admin-level capabilities (file system write, shell execution) to a public URL. The anti-patterns section lists the common footguns.
Do I need to tunnel MCP servers if I only use them locally?
No. Claude Desktop and Cursor both spawn stdio-mode MCP servers as child processes — no tunneling needed. You only need tunneling when you want the same MCP server reachable from multiple machines, hosted AI sessions, or teammates.
Does this work with the hosted claude.ai web app?
At the time of writing (2026-05-11), hosted claude.ai doesn't support arbitrary MCP server URLs directly — only the integrations Anthropic ships. Claude Desktop does (the steps above). Watch the MCP spec repository for updates as web-app support evolves.
Can I share one tunneled MCP server with a team?
Yes, that's one of the main reasons to tunnel. Add the same URL to every teammate's claude_desktop_config.json or project-level .cursor/mcp.json. Give each teammate their own bearer token at the MCP server layer so you can audit who called what.
Want a tunneling tool with first-class master-key delegation for your MCP server? 21tunnel is free on Hobby (3 tunnels, 10 Mbps per tunnel, 20,000 requests/month, custom domain on signup). Read the longer post on the delegation pattern or the scored comparison of every ngrok alternative we published alongside this tutorial.