dsh-mcp-tunnel
Expose a local MCP server to remote agents through an outbound-only tunnel stack (mcp-proxy + cloudflared Quick Tunnel), then register the public URL into dsh-mcp-client streamable-http config.
Install
dsh plugin --profile web add dsh-mcp-tunnel
Sandbox test results
| Stage | Result |
|---|---|
| 1 install | ✗ |
| 2 list | — |
| 3 mount | — |
| 4 boot | — |
dsh: initialized profile web at /home/node/.dsh/profiles/webFour stages run in an isolated sandbox: install (network on) → list → mount → boot (last three with no network).
README
dsh-mcp-tunnel
[!IMPORTANT]
依赖前置:相邻dsh-src检出(link:依赖)
本项目在开发形态下使用link:依赖指向相邻的 DeepSeek Harness 源码检出(dsh-src),
与当前仓库保持同一父目录布局(<parent>/dsh-src)。克隆本仓库后:
1. 先把官方deepseek-ai/deepseek-harness检出到与本仓库同级的dsh-src/目录,并执行其pnpm install && pnpm run build;
2. 再按下方「安装」一节执行本仓库的 pnpm install --offline && pnpm build 与测试。
发布到 npm 的版本会尽量把link:依赖替换为 registry 真实版本;无法替换的内部包保持link:,见各包 README 说明。
把本机 MCP Server 安全暴露给远程 Agent 的 DSH 隧道插件:一键为本机目录中的 MCP server
拉起隧道栈(mcp-proxy + cloudflared 的 Docker Compose 栈),并把最终公开 URL 注册到
@deepseek-ai/dsh-mcp-client 的 streamable-http 配置里(待确认片段,安全护栏 1)。
设计取向(对齐上游 anthropics/claude-plugins-official#mcp-tunnels,仅参考行为、不抄源码):
隧道 = 传输层;鉴权/授权应明确标注为「由暴露方自行加」。默认提供方
cloudflare-quick(免账号、开箱即用),与上游 security model 的立场一致:
免费快速隧道仅限测试,生产流量必须自行加鉴权层。
一、架构与安全模型
远程 Agent (dsh-mcp-client, streamable-http)
│ HTTPS + Authorization: Bearer <token>
▼
https://<random>.trycloudflare.com ← Cloudflare 边缘(TLS 由 Cloudflare 提供)
│ (cloudflared 与边缘之间的出站隧道连接)
▼
┌──────────── Docker Compose 网络(共享命名空间)────────────┐
│ cloudflared ── network_mode: service:mcp-proxy ──► mcp-proxy │
│ (出站-only,与代理共享网络命名空间,无任何入站端口) │
│ mcp-proxy(校验 Bearer 令牌 → 转发到上游) │
│ │ │
│ ├─► hello-mcp(可选 FastMCP 示例:echo / uptime) │
│ └─► 你的 MCP server 的 HTTP(S) 端点(容器网络/宿主机回环) │
└──────────────────────────────────────────────────────────────┘
(宿主机仅绑定 127.0.0.1:<回环调试端口> —— 公网无任何监听端口)
关键属性:
- 出站-only:cloudflared 只主动连到 Cloudflare 边缘;公网无入站端口,
宿主机唯一端口绑定是 127.0.0.1:43180 的回环调试端口(验收标准 1)。
- 本地 TLS 终结(命名隧道模式):
certs/下的自签 CA + 服务端证书由隧道属主掌控,
与上游「证书用户掌控」做法一致;Quick Tunnel 模式公网侧 TLS 由 Cloudflare 提供,
证书仍生成并挂载供复用/自检。
- 鉴权边界(重要):mcp-proxy 校验
Authorization: Bearer <token>(令牌在部署目录
.env 中,0600)。Quick Tunnel 的随机域名不是安全边界 —— **生产流量必须由暴露方
自行加鉴权层/网关**,本插件只保证「隧道=传输层」。
二、交付物
dsh-mcp-tunnel/
├── cordis.yml # Cordis 装配(一行插件 + config 默认值;兼作 dsh.bundle.patch)
├── package.json # dsh-mcp-tunnel,声明 dsh.bundle(MIT)
├── src/index.ts # 装配 + 3 工具(create/status/stop)+ ctx.jobs 后台流程
├── src/tunnel-stack.ts # compose 栈生成(迷你 .hbs 渲染器)+ 落盘
├── src/certs.ts # 自签 CA/证书生成与目录管理(本地 openssl exec)
├── src/mcp-client-config.ts# dsh-mcp-client 追加片段:生成/校验/待确认管理
├── templates/
│ ├── docker-compose.yml.hbs # 栈布局(mcp-proxy / cloudflared / 可选 hello-mcp)
│ └── proxy-config.env.hbs # 代理非敏感环境配置(秘密一律走 .env)
├── README.md # 本文档(安全模型 + 限制)
├── LICENSE # MIT
├── tsconfig.json # tsc --noEmit 类型检查用
└── tests/
├── tunnel.smoke.ts # 离线冒烟(node:test,无需 docker)
└── tunnel.e2e.ts # 真栈 e2e(需 docker + 公网,显式 DSH_MCP_TUNNEL_E2E=1 启用)
三、安装
本地开发(scratch-plugin 风格,直接加载 TS 源码):
# 你的 profile 的 cordis.patch.yml 追加:
- insert:
- id: dsh-mcp-tunnel
name: '/绝对路径/dsh-mcp-tunnel/src/index.ts'
config: {}
作为 profile bundle 安装(dsh.bundle.patch: ./cordis.yml 已声明):
cd <你的 DSH profile> pnpm add ./dsh-mcp-tunnel # 行内引用建议改为包路径形式(随 exports 解析): # name: 'dsh-mcp-tunnel/src/index.ts'
依赖对齐(npm 上已发布的真实版本):peerDependencies 声明 @deepseek-ai/cordis ^4.0.1、
@deepseek-ai/dsh-tools ^0.1.1-rc.2、@deepseek-ai/dsh-jobs ^0.1.0-rc.8;dependencies 为
@deepseek-ai/schemastery ^3.18.1(运行时仅真正 import schemastery;其余为类型级/服务级
依赖)。peer 关系保留:目标运行时是 DSH 宿主,由宿主提供 cordis / dsh-tools / dsh-jobs 服务。
四、工具用法
mcp_tunnel_create
参数 · 类型 · 必填 · 说明
name · string · ✓ · 隧道名 = compose 项目名 = mcp-client serverName(^[A-Za-z0-9_-]{1,32}$)
serverDir · string · · MCP server 目录:优先读 .mcp.json(首个含 url 的条目作上游;command 型 stdio server 需先转成 HTTP 端点,见「限制」)
upstreamUrl · string · · 直接指定上游 MCP 端点(http/https),优先级高于 .mcp.json
provider · string · · cloudflare-quick(默认,免账号)/ anthropic(命名隧道,需自备 Cloudflare 账号与 DNS)
deployDir · string · · 覆盖部署目录(默认 <deployRoot>/<name>,deployRoot 默认 ~/.dsh/mcp-tunnels)
useHelloMcp · boolean · · 无上游可用时内置 hello-mcp 示例 server(echo/uptime);默认自动
token · string · · 自定义访问令牌;缺省自动生成 32 字节随机令牌写入 .env(0600)
hostProxyPort · integer · · 宿主回环调试端口(默认 43180)
行为:生成栈文件 + 证书(CA 存在即复用,验收标准 3)→ 写 .env(0600,秘密不进 compose)
→ 前置检查 docker(缺失给清晰提示,验收标准 5)→ 后台起容器(ctx.jobs,缺 jobs 时前台)
→ 轮询 cloudflared 日志出 Quick Tunnel URL → 写 state.json → 生成 待确认片段
<deployDir>/mcp-client.pending.yml。有 jobs 时返回 {kind:'background', jobId},用
job_output 收尾;无 jobs 时前台返回完整结果。
mcp_tunnel_status
参数 · 类型 · 说明
name · string · 隧道名;缺省列出全部隧道
输出:每隧道的部署目录、state.json、栈文件存在性、docker compose ps 结果、
cloudflared 最近日志尾部(≤50 行)、待确认片段(serverName/url)。
mcp_tunnel_stop
参数 · 类型 · 默认 · 说明
name · string(必填) · · 隧道名
keepCerts · boolean · true · false 时删除 certs/
行为:docker compose down 停栈并下线 URL(验收标准 4)→ 移除待确认片段 →
写入 state.json 失效标注(url 下线 + lastError,status 可见)→ 保留证书目录
供下次 create 复用(验收标准 3/4)。
五、mcp-client 配置片段(安全护栏 1)
create 绝不自动改写任何 profile 配置,只在部署目录写出待确认片段
(mcp-client.pending.yml),由你在显式确认后手动粘贴:
- insert:
- id: mcp-<serverName>
name: '@deepseek-ai/dsh-mcp-client'
config:
serverName: <name>
transport: streamable-http
url: <https://xxx.trycloudflare.com>
headers:
Authorization: 'Bearer <token>'
toolCallTimeoutMs: 60000
failOnStartupError: false
- 字段名与
@deepseek-ai/dsh-mcp-client的StreamableHttpConfig完全一致; - 远程工具名 =
mcp__<serverName>__<rawName>; mcp_tunnel_stop会删除该片段文件(验收标准 4)。若你已粘贴进 profile,
记得同步删除那一行,否则请把 URL 标注为失效;
- 「由 apply 注入」路径(显式确认后自动写 profile 的 cordis.patch.yml)留作后续版本。
六、安全护栏清单
1. 仅显式确认后写 mcp-client 配置:插件只产出待确认片段文件,绝不自动改写 profile;
2. 秘密不进 compose:访问令牌只写入部署目录 .env(0600,docker co
…(truncated)
Rendered from the README published on npm. Formatted only, not edited.
Versions
Published on npm::1,最后更新 2026-08-24