
Tunnel Mcp
io.github.zachlikefolio由 GitHub 帳號 zachlikefolio 發布v0.3.0更新於 Oct 25, 2026
Direct end-to-end-encrypted tunnel between Claude agents — two-party or a room of up to 16.
概覽
讓兩個或多個 Claude 代理建立一次性、端對端加密的通道並直接對話,省去人工在聊天視窗之間複製貼上。
- 可以做什麼
- 以主機身分開啟通道並產生一次性、會過期的邀請連結,或以訪客身分加入並查看成員名單;房間最多 16 人,主機可在工作階段中途追加邀請。逐輪收發訊息,每則訊息帶有傳送者名稱,並可等待下一則回覆。以端對端加密方式分享文字或二進位檔案,再取得對方提供的檔案、驗證雜湊並存到自選路徑。查看工作階段角色、目標、成員、待用邀請與檔案清單。主機可結束整個工作階段,成員則只是離開房間。
- 適用情境
- 當兩名或多名開發者各自執行 Claude 代理,希望這些代理就除錯等任務直接協作,而不是由人來回轉述訊息時使用。適合短暫、一次性的配對工作階段,而非長期共用的工作空間。
- 執行需求
- 本機 stdio MCP 伺服器,需 Node.js 20 或更新版本與 Claude MCP 用戶端(例如 Claude Code),透過 npm 或 npx 安裝。若 PATH 中沒有 cloudflared,首次使用時會自動下載到本機 bin 目錄。需要連線至 Cloudflare 的對外網路。選用環境變數 TUNNEL_DOH、TUNNEL_SKILLS_DIR 與 TUNNEL_SKIP_SKILL_INSTALL 可調整 DNS 與技能安裝。
安裝
在 SourceWeft 中
- 開啟 儀表板中的 Tunnel Mcp,將其新增到工作區。
- 為需要使用其工具的對話啟用該服務。
Desktop only,透過 STDIO。 STDIO 服務會啟動本機處理程序,因此需要 SourceWeft 桌面主機。
其他 MCP 客戶端
參照 儲存庫 中的啟動說明。
README
tunnel-mcp
A direct, end-to-end-encrypted tunnel between two developers' Claude agents — no human copy-paste required.
[CI] [npm version] [npm downloads] [node]
[tunnel-mcp demo — two agents talking through a real encrypted tunnel]
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.
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.
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.
Install
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.
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 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.
Tools
Security model
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:
- Chat message bodies are end-to-end encrypted. Every
tunnel_saybody is sealed with NaClsecretbox(XSalsa20-Poly1305, viatweetnacl) before it crosses thecloudflaredpipe. The relay and the pipe only ever see ciphertext for chat bodies. - The goal, every participant's display name, and system events are
plaintext. The
tunnel_opengoal, each member'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 joining member holds the same key as the host; the raw key itself is never sent over the wire.
- Each invite 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, and forward each invite to exactly one person. It is consumed by whoever redeems it first (and can't be reused, even after they leave) and expires on its own after ~10 minutes, so a leaked invite has a short, bounded window of exposure.
- Admits exactly whom you invited — two-party by default, rooms opt-in (cap 16), every invite single-use + expiring. Admission is bounded by how many invites the host chose to mint, not by who happens to have the room's key.
- Shared files are end-to-end encrypted and hash-verified.
tunnel_shareseals a file's bytes with the same room key as chat (NaClsecretbox) before they cross the tunnel, and carries a plaintext sha256 of the contents;tunnel_receivedecrypts, reassembles, and verifies that hash before writing anything to disk. The filename, size, and kind are plaintext metadata (don't put secrets in a filename), and a received file is untrusted —tunnel_receiveonly ever writes to a path the receiver chooses, never the sender's name. - The peer is untrusted input, not an instruction source. Messages from other agents are data to reason about, not commands to execute — and this applies to every member in a room, not just one. 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 a peer said.
- Everything is ephemeral. The transcript is held in memory only — nothing
is ever written to disk, and it vanishes with the process. Teardown is
role-sensitive: the host's
tunnel_close(or their process exiting, or 30 minutes of no messages) ends the session for everyone and tears down the relay +cloudflaredchild process; a member'stunnel_closejust leaves — the room stays open for whoever's left.
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/binon first use if not already on yourPATH
Development
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
- Alternative transports (ngrok, WebRTC)
- Invite rotation (replacing a specific still-valid invite mid-session; note invites are already single-use and expiring — see the security model above)
- Encrypting the goal or other metadata
License
MIT — see LICENSE.
來源:README.md,提交 eef2ccb
工具
0版本歷史
1- v0.3.0最新Sep 16, 2026
評分
已滿足適用檢查項的 50%
該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。
- 近 30 天內可連通25 分
請保持端點能夠回應 MCP 握手;SourceWeft 每 30 天探測一次。
- 已宣告認證方式10 分
請在註冊中心條目中宣告憑證請求標頭,或保持端點能夠回應探測,以便判定其認證方式。
- 已列出工具15 分
請讓伺服器在無憑證時回應 tools/list,或發布可在沙箱中無憑證啟動的軟體包。
- 描述必需15 分
- 圖示10 分
- 文件15 分
- 授權條款10 分