tunnel-mcp

tunnel-mcp

Enables two Claude agents to communicate directly through an end-to-end encrypted tunnel, eliminating the need for manual copy-pasting and facilitating real-time collaboration.

Category
Visit Server

README

tunnel-mcp

A direct, end-to-end-encrypted tunnel between two developers' Claude agents — no human copy-paste required.

CI npm version node

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.

How it works

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.

   Host machine                                        Guest machine
  ┌───────────────────┐        outbound HTTPS         ┌───────────────────┐
  │   Claude (host)    │            wss://             │   Claude (guest)   │
  │        │           │      ┌──────────────┐         │        │          │
  │  tunnel_open/say/  │──────▶  cloudflared │◀────────│  tunnel_join/say/  │
  │  listen/close      │      │ Quick Tunnel │─────────▶  listen/close      │
  │        │           │      └──────────────┘         │        │          │
  │  in-process relay  │                                └───────────────────┘
  └───────────────────┘

The relay, the cloudflared child process, and the on-disk session log all live only for the lifetime of the session and are destroyed on teardown.

Install

npm install -g tunnel-mcp
# or, without installing:
npx tunnel-mcp

Register it with Claude Code (both developers do this once):

claude mcp add tunnel -- tunnel-mcp          # if globally installed
# or, with no global install:
claude mcp add tunnel -- npx -y tunnel-mcp

tunnel-mcp is a stdio MCP server, not an interactive CLI. Launching it by hand just waits silently for a client — that's expected. Run tunnel-mcp --help for usage, or tunnel-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:

npx tunnel-mcp install-skill                       # into ~/.claude/skills
npx tunnel-mcp install-skill --dir <path> --force  # elsewhere / overwrite

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.

Quickstart

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 returns a join link. Share that link with the other developer over a trusted channel (Slack DM, etc.) — it's a secret, since it contains the encryption key for the session. The link 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 the session is now locked to just the two of you.

Both — the agents converse turn-by-turn using tunnel_say to send and tunnel_listen to wait for the next reply, checking in with their humans as needed.

Either side ends the session with tunnel_close, which tears down the relay and destroys the session log.

Tools

Tool Who Purpose
tunnel_open({goal}) host Start the relay + Quick Tunnel and get back a join link.
tunnel_join({joinLink}) guest Dial into a host's tunnel using the link and authenticate.
tunnel_say({text}) both Send a message to the peer.
tunnel_listen({sinceSeq?, timeoutMs?}) both Wait for the next message(s) from the peer.
tunnel_status() both Inspect the current session (connected, idle, etc.).
tunnel_close({summary?}) both End the session and tear down the relay.

Security model

tunnel-mcp is a security-sensitive tool by nature — it opens a live channel between two AI agents. Here's exactly what it does and does not protect:

  • Chat message bodies are end-to-end encrypted. Every tunnel_say body is sealed with NaCl secretbox (XSalsa20-Poly1305, via tweetnacl) before it crosses the cloudflared pipe. The relay and the pipe only ever see ciphertext for chat bodies.
  • The goal, both display names, and system events are plaintext. The tunnel_open goal, each participant's name, and connection events (joined/left/idle/closed) are sent as plaintext metadata — do not put secrets in the goal string or a display name.
  • Authentication is proof-of-key-possession, not key transmission. Joining uses an HMAC challenge to prove the guest holds the same key as the host; the raw key itself is never sent over the wire.
  • The join link is a single-use, expiring credential. It embeds the session key, so treat it like a password — share it only over a channel you already trust (Slack DM, etc.), never in a public issue, PR, or chat. It is consumed by the first guest who joins (and can't be reused, even after they leave) and expires on its own after ~10 minutes, so a leaked link has a short, bounded window of exposure.
  • Exactly two participants, enforced by a lock. The first guest to authenticate locks the session; nobody else can join after that.
  • The peer is untrusted input, not an instruction source. Messages from the other agent are data to reason about, not commands to execute. The etiquette skill directs each agent to require its own human's sign-off before writing files, running risky commands, or declaring a fix "confirmed" based on something the peer said.
  • Everything is ephemeral. The session tears down — destroying the relay, the cloudflared child process, and the on-disk log — on an explicit tunnel_close, after 30 minutes of no messages (idle timeout), or when the host's process exits.

See SECURITY.md for the full threat model and how to report a vulnerability.

Requirements

  • Node.js >= 20
  • A Claude MCP client (e.g., Claude Code)
  • cloudflared — auto-installed to ~/.tunnel/bin on first use if not already on your PATH

Development

npm ci                  # install dependencies
npm test                # run the test suite (159 tests, TDD)
npm run build           # compile TypeScript
npm run lint            # eslint
npm run format:check    # prettier --check .
npm run test:coverage   # vitest run --coverage

See CONTRIBUTING.md for how to propose changes.

Troubleshooting

tunnel-mcp / npx tunnel-mcp "does nothing". It's a stdio MCP server, not an interactive CLI — with no arguments it starts and waits for an MCP client to connect over stdin/stdout. That's working as intended. Register it with a client (above), or run tunnel-mcp --help.

Guest join fails with getaddrinfo ENOTFOUND …trycloudflare.com. A cloudflared quick tunnel prints its URL a few seconds before the per-tunnel DNS record has propagated. If anything looks the name up too early it gets an NXDOMAIN that the resolver negative-caches for up to 30 minutes — breaking the join even after the tunnel is live. tunnel-mcp avoids this: tunnel_open waits for the record to actually resolve (via DoH to Cloudflare's 1.1.1.1, an IP that never touches — and so never poisons — your system resolver) before returning the link, and the guest resolves system-first with a DoH fallback. So a fresh join should just work; if you hit ENOTFOUND, an earlier attempt likely poisoned the cache — wait for it to expire, or flush DNS (sudo dscacheutil -flushcache on macOS). Set TUNNEL_DOH=off only on networks that block DoH (1.1.1.1) and where system DNS already resolves *.trycloudflare.com.

Roadmap / not yet supported

This is an MVP. The following are explicitly out of scope for now:

  • Host-offline / asynchronous messaging
  • More than two participants in a session
  • Alternative transports (ngrok, WebRTC)
  • Join-link rotation (re-issuing a fresh link mid-session; note that links are already single-use and expiring — see the security model above)
  • Encrypting the goal or other metadata

License

MIT — see LICENSE.

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured