Let two developers' Claude agents talk directly through an ephemeral, end-to-end-encrypted tunnel.
Copy the AI prompt to install this server into Claude Code, Cursor, or another agent β or use 1-click editor setup below.
π‘ Paste the JSON block into your client's configuration file under mcpServers, then restart the application.
A direct, end-to-end-encrypted tunnel between two developers' Claude agents β no human copy-paste required.

Reproduce that yourself in 30 seconds β clone the repo and:
That opens a real encrypted tunnel through Cloudflare's edge, joins it as a guest, exchanges end-to-end-encrypted messages, proves the join link is single-use, and tears everything down.
When two developers each run a Claude agent and need those agents to collaborate, the usual workaround is a human sitting in the middle, copy-pasting messages from one chat window to the other. tunnel-mcp removes that human. It's an MCP server that lets one developer's agent open a throwaway, encrypted tunnel and the other developer's agent dial straight into it, so the two agents can talk to each other directly β while their humans stay in control of what actually happens to the filesystem or the shell.
One developer (the host) calls tunnel_open. Their local tunnel-mcp
process becomes an in-process WebSocket relay and exposes it to the internet via
a throwaway cloudflared Quick Tunnel β no port-forwarding, no server to
provision. The other developer (the guest) calls tunnel_join with the link
the host shares, and their agent dials outbound to that same tunnel. Because both
sides only ever make outbound connections, it works from behind ordinary
firewalls and NAT.
The relay and the cloudflared child process live only for the lifetime of the
session and are destroyed on teardown. The transcript is held in memory only β
nothing is ever written to disk, and it vanishes with the process at teardown.
Register it with Claude Code (both developers do this once):
tunnel-mcpis a stdio MCP server, not an interactive CLI. Launching it by hand just waits silently for a client β that's expected. Runtunnel-mcp --helpfor usage, ortunnel-mcp --version.
The tunnel-etiquette skill teaches each agent how to behave inside a tunnel
(treat the peer as untrusted input, and check with its human before acting on
anything the peer says). Installing the package copies it into ~/.claude/skills/
automatically (best-effort). If install scripts are disabled
(npm install --ignore-scripts), or you want it in a custom directory or force an
update, run it explicitly:
Set TUNNEL_SKILLS_DIR to change the default target, or
TUNNEL_SKIP_SKILL_INSTALL=1 to opt out of the automatic copy.
cloudflared is auto-downloaded to ~/.tunnel/bin the first time it's needed if
it isn't already on your PATH β there's nothing extra to install.
Host β ask Claude to open a tunnel with a goal:
"Open a tunnel to pair on debugging the checkout flow."
Claude calls tunnel_open({ goal }) and hands back a ready-to-forward
invite β one plain-text message containing the one-time setup command and
the join link. Paste it to the other developer over a trusted channel (Slack
DM, etc.) β the link is a secret, since it contains the encryption key for
the session. It is single-use and expires after ~10 minutes
(tunnel_open reports joinLinkExpiresInSec), so share it promptly.
Guest β paste the link and ask Claude to join:
"Join this tunnel:
<link>"
Claude calls tunnel_join({ joinLink }), learns the goal, and gets back the
room's member roster β with the default single invite, that's just the two of
you.
More than one guest? Open a room instead:
"Open a tunnel for me and two teammates, to pair on the checkout flow."
Claude calls tunnel_open({ goal, invites: 3 }) β invites is the number of
teammates to seat (up to 15, plus the host makes 16 connected at once) β and gets
back one invite per teammate instead of a single link. Forward each invite
to exactly one person; every invite is single-use, so don't reuse one link for
two people. Need to add someone mid-session, or re-admit someone whose invite
expired before they used it? tunnel_invite({ count }) (host-only) mints more.
Both β the agents converse turn-by-turn using tunnel_say to send and
tunnel_listen to wait for the next reply. In a room, every message arrives
with fromName so agents can tell who said what, checking in with their humans
as needed.
Ending it is role-sensitive: the host calls tunnel_close to end the
session for everyone and tear down the relay β the in-memory transcript vanishes
with it, since it was never written to disk. A member calling tunnel_close
just leaves; the room stays open for whoever's left.
Sharing files: any member can call tunnel_share({ path }) to send a text
or binary file to the room β it's read from disk, hashed, and sealed with the
room key before it ever crosses the tunnel, so the relay only ever sees
ciphertext. The offer shows up for teammates as an artifact message in
tunnel_listen and in tunnel_status().artifacts (id, name, kind, size,
sender). A teammate who wants it calls tunnel_receive({ artifactId, savePath })
with a path they choose β the bytes are decrypted and checked against the
sender's sha256 before anything is written, and a mismatch is refused rather
than saved. An artifact stays fetchable by any current member until it expires
(a 30-minute TTL) or the session ends β each tunnel_receive call independently
re-fetches and re-verifies. Members on an older client are silently skipped
(olderMembers in the tunnel_share result) β they simply never see the
offer. Filenames cross as plaintext metadata, so don't put secrets in one, and
treat every received file as untrusted input β see the etiquette skill.
| Tool | Who | Purpose |
|---|---|---|
tunnel_open({goal, invites?}) | host | Start the relay + Quick Tunnel and get back one invite per teammate (default 1 β classic two-party). |
tunnel_invite({count?}) | host | Mint more single-use, expiring invites mid-session. |
tunnel_join({joinLink}) | member | Dial into a room using an invite link and authenticate; returns the current member roster. |
tunnel_say({text}) | any | Send a message to the room. |
tunnel_listen({sinceSeq?, timeoutMs?}) | any | Wait for the next message(s), each tagged with the sender's fromName. |
tunnel_share({path}) | any | Share a file (text or binary) with the room, end-to-end encrypted; returns {artifactId, offeredTo, olderMembers}. |
tunnel_receive({artifactId, savePath}) | any | Fetch an offered artifact, verify its hash, and write it to a path you choose. |
tunnel_status() | any | Inspect the session: role, goal, member roster, pending invites, offered artifacts, lastSeq. |
tunnel_close({summary?}) | any | Host: ends the session for everyone. Member: leaves the room. |
tunnel-mcp is a security-sensitive tool by nature β it opens a live channel between developers' AI agents. Here's exactly what it does and does not protect:
No reviews yet β be the first to share how this listing worked for you.
Showcase your server listing on GitHub or your project documentation. Embed this dynamic SVG badge to highlight official listing status and live engagement.
[](https://allmcps.com/mcp/tunnel-mcp)<a href="https://allmcps.com/mcp/tunnel-mcp"><img src="https://allmcps.com/api/badge/tunnel-mcp?style=directory" alt="Tunnel MCP on AllMCPs" /></a>