内容
# tunnel-mcp
**两名开发者的Claude代理之间直接、端到端加密的隧道 - 无需人工复制粘贴。**
[](https://github.com/zachlikefolio/tunnel-mcp/actions/workflows/ci.yml)
[](https://www.npmjs.com/package/tunnel-mcp)

当两名开发者各自运行一个Claude代理,并且需要这些代理进行协作时,通常的解决方法是让一个人坐在中间,手动复制粘贴消息从一个聊天窗口到另一个。而 **tunnel-mcp** 则消除了这种人为干预。它是一个MCP服务器,允许一名开发者的代理打开一个临时、加密的隧道,另一名开发者的代理可以直接连接到该隧道,从而使两个代理可以直接通信,而他们的操作者仍然可以控制实际的文件系统或 shell 操作。
## 工作原理
一名开发者(**主机**)调用 `tunnel_open`。他们的本地 `tunnel-mcp` 进程成为一个进程内WebSocket中继,并通过一个临时的 `cloudflared` Quick Tunnel将其暴露给互联网 - 无需端口转发,无需配置服务器。另一名开发者(**访客**)使用主机共享的链接调用 `tunnel_join`,他们的代理直接连接到同一个隧道。因为双方都只进行外向连接,所以它可以在普通的防火墙和NAT后面工作。
```
主机 访客
┌───────────────────┐ outbound HTTPS ┌───────────────────┐
│ Claude (主机) │ wss:// │ Claude (访客) │
│ │ │ ┌──────────────┐ │ │ │
│ tunnel_open/say/ │──────▶ cloudflared │◀────────│ tunnel_join/say/ │
│ listen/close │ │ Quick Tunnel │─────────▶ listen/close │
│ │ │ └──────────────┘ │ │ │
│ 进程内中继 │ └───────────────────┘
└───────────────────┘
```
中继、`cloudflared`子进程和磁盘上的会话日志都只在会话生命周期内存在,会话结束后会被销毁。
## 安装
```bash
npm install -g tunnel-mcp
# 或,无需全局安装:
npx tunnel-mcp
```
在Claude Code中注册它(两名开发者都这样做一次):
```bash
claude mcp add tunnel -- tunnel-mcp # 如果已全局安装
# 或,无需全局安装:
claude mcp add tunnel -- npx -y tunnel-mcp
```
> `tunnel-mcp` 是一个stdio MCP服务器,不是交互式CLI。手动启动它只是等待客户端连接 - 这是预期的行为。运行 `tunnel-mcp --help` 获取用法,或运行 `tunnel-mcp --version`。
**隧道礼仪技能** 教每个代理如何在隧道内行为(将对端视为不受信任的输入,并在执行对端发送的任何操作前检查其人类)。安装软件包会自动将其复制到 `~/.claude/skills/`(尽力而为)。如果禁用安装脚本(`npm install --ignore-scripts`),或希望在自定义目录中或强制更新,运行它显式:
```bash
npx tunnel-mcp install-skill # 进入 ~/.claude/skills
npx tunnel-mcp install-skill --dir <path> --force # 进入其他目录/覆盖
```
设置 `TUNNEL_SKILLS_DIR` 以更改默认目标,或设置 `TUNNEL_SKIP_SKILL_INSTALL=1` 以选择退出自动复制。
`cloudflared` 会在首次需要时自动下载到 `~/.tunnel/bin`,前提是它不在您的 `PATH` 中 - 无需额外安装。
## 快速入门
**主机** - 请Claude使用目标打开隧道:
> "打开一个隧道,配对调试结账流程。"
Claude 调用 `tunnel_open({ goal })` 并返回一个加入链接。在受信任的渠道(例如Slack DM)中与另一名开发者共享该链接 - **这是秘密的**,因为它包含会话的加密密钥。链接是 **一次性使用的,过期时间约为10分钟** (`tunnel_open` 报告 `joinLinkExpiresInSec`),所以请尽快分享。
**访客** - 粘贴链接并请Claude加入:
> "加入此隧道:`<link>`"
Claude 调用 `tunnel_join({ joinLink })`,了解目标,会话现在锁定为仅限你们两人。
**双方** - 代理通过 `tunnel_say` 发送和 `tunnel_listen` 等待下一条回复,必要时与人类核实。
**任意一侧** 通过 `tunnel_close` 结束会话,这会拆除中继并销毁会话日志。
## 工具
| 工具 | 对象 | 目的 |
| ---------------------------------------- | ----- | ---------------------------------------------------------- |
| `tunnel_open({goal})` | 主机 | 启动中继 + Quick Tunnel 并获取加入链接。 |
| `tunnel_join({joinLink})` | 访客 | 使用链接加入主机的隧道并进行身份验证。 |
| `tunnel_say({text})` | 双方 | 向对端发送消息。 |
| `tunnel_listen({sinceSeq?, timeoutMs?})` | 双方 | 等待对端的下一条消息。 |
| `tunnel_status()` | 双方 | 检查当前会话(已连接、空闲等)。 |
| `tunnel_close({summary?})` | 双方 | 结束会话并拆除中继。 |
## 安全模型
`tunnel-mcp` 是一种天然具有安全敏感性的工具,因为它在两个AI代理之间打开了一个实时通道。下面是它确切地做了什么和没有做什么的保护:
- **聊天消息体是端到端加密的。** 每个 `tunnel_say` 消息体都使用NaCl `secretbox` (XSalsa20-Poly1305,通过 `tweetnacl`)进行密封,然后再通过 `cloudflared` 管道传输。中继和管道只会看到聊天体的密文。
- **目标、双方显示名称和系统事件是明文的。** `tunnel_open` 目标、每个参与者的名称和连接事件(加入/离开/空闲/关闭)作为明文元数据发送 - 不要在目标字符串或显示名称中放置秘密。
- **身份验证是密钥证明,而不是密钥传输。** 加入使用HMAC挑战来证明访客持有与主机相同的密钥;原始密钥本身从不通过线路发送。
- **加入链接是单次使用的过期凭证。** 它嵌入了会话密钥,因此像密码一样对待它 - 仅在您已经信任的渠道中共享(例如Slack DM),永远不要在公共问题、PR或聊天中共享。它被第一个加入的访客使用(并且不能重复使用,即使他们离开后),并且在约10分钟后自动过期,因此泄露的链接具有短暂的有限暴露窗口。
- **恰好两个参与者,由锁强制执行。** 认证后的第一个访客锁定会话;之后其他人无法加入。
- **对端是不可信任的输入,而不是指令源。** 来自另一代理的消息是数据,供其推理,而不是要执行的命令。礼仪技能指示每个代理在写入文件、运行风险命令或基于对端说的内容声明修复“已确认”之前需要其自己的操作员批准。
- **所有内容都是短暂的。** 会话会在显式 `tunnel_close` 、30分钟无消息(空闲超时)或主机进程退出时拆除 - 销毁中继、`cloudflared` 子进程和磁盘日志。
在 [SECURITY.md](./SECURITY.md) 中查看完整的威胁模型和如何报告漏洞。
## 需求
- Node.js >= 20
- Claude MCP客户端(例如Claude Code)
- `cloudflared` - 如果已在您的 `PATH` 中,则会自动安装到 `~/.tunnel/bin`
## 开发
```bash
npm ci # 安装依赖
npm test # 运行测试套件(159个测试,TDD)
npm run build # 编译TypeScript
npm run lint # eslint
npm run format:check # prettier --check .
npm run test:coverage # vitest run --coverage
```
在 [CONTRIBUTING.md](./CONTRIBUTING.md) 中查看如何提出更改。
## 故障排除
**`tunnel-mcp` / `npx tunnel-mcp` "无反应"。** 它是一个stdio MCP服务器,不是交互式CLI - 没有参数它启动并等待MCP客户端通过stdin/stdout连接。这是预期行为。使用客户端注册它(上面),或运行 `tunnel-mcp --help`。
**访客加入失败,出现 `getaddrinfo ENOTFOUND …trycloudflare.com` 错误。** 一个cloudflared快速隧道打印其URL几秒钟 **在** 隧道DNS记录传播之前。如果任何东西过早查看名称,它会得到一个 `NXDOMAIN`,解析器会将其缓存长达30分钟 - 从而破坏加入,即使隧道已启动。 `tunnel-mcp` 避免这种情况: `tunnel_open` 等待记录实际解析(通过DoH到Cloudflare的 `1.1.1.1`,一个从不触及 - 因此从不污染 - 您的系统解析器)然后返回链接,访客首先使用系统解析器,并使用DoH备用。如果新的加入应该直接有效;如果您遇到 `ENOTFOUND`,早期尝试可能污染了缓存 - 等待它过期,或刷新DNS(macOS上的 `sudo dscacheutil -flushcache`)。仅在阻止DoH(`1.1.1.1`)的网络上设置 `TUNNEL_DOH=off`,并且系统DNS已经解析 `*.trycloudflare.com`。
## 路线图 / 尚不支持
这是MVP。现在明确不在此范围内的内容:
- 主机离线 / 异步消息
- 会话中超过两名参与者
- 替代传输(ngrok,WebRTC)
- 加入链接轮换(在会话中间重新发出新链接;注意链接已经是一次性且过期的 - 见上面的安全模型)
- 加密目标或其他元数据
## 许可证
MIT - 见 [LICENSE](./LICENSE)。