用 Tailscale Funnel 暴露 DevSpace 给 ChatGPT
让 DevSpace 和 Tailscale Funnel 运行在同一个本地环境,用公网 HTTPS MCP 地址把本机项目接入 ChatGPT。
这篇教程解决一个很具体的问题:DevSpace 已经能在自己的电脑或服务器上运行,怎么给它一个 ChatGPT 能访问的公网 HTTPS MCP 地址。
这里不再把 WSL 当成前提。你可以使用 Linux、WSL、Windows、macOS,甚至一台远程 Linux 开发机。真正重要的只有一个原则:
Tailscale Funnel 必须能够通过本机回环地址访问 DevSpace。最简单的做法,就是让 Tailscale 和 DevSpace 运行在同一个系统环境里。
最终架构与验收顺序
这套方案的核心链路始终一样:
ChatGPT -> Tailscale Funnel -> 127.0.0.1:7676 -> DevSpace
不同系统的区别,只在于 DevSpace 怎么启动、Tailscale 怎么安装、服务怎么常驻。Funnel、公网域名、OAuth 验收和 ChatGPT 连接方式不变。
先本地,再公网,最后做真实调用
哪一层没有达到预期,就停在这一层排查。不要在本地还没通时先查 ChatGPT。
https://...ts.net/mcp 127.0.0.1:7676/mcp 401 端点存在,只是当前请求还没有授权。
/.well-known/... 200 × 2 受保护资源和授权服务器信息都能访问。
funnel status Funnel on 公网域名已经代理到本地的 7676。
读取测试文件 真实调用 能读取 allowedRoots 中的文件,整条链路才算完成。
看到 `401` 不代表失败;这里真正的失败,是某一层没有得到它应该出现的信号。
普通 Tailscale 的 100.x 地址只给 Tailnet 内的设备访问。ChatGPT 不在你的 Tailnet 里,所以不能填 http://100.x.x.x:7676/mcp,必须使用 Funnel 提供的公网 HTTPS 地址。
先决定 DevSpace 和 Tailscale 放在哪里
不要先复制命令。先确认两者是不是处于同一个本地环境。
Linux / 远程开发机
DevSpace 和 Tailscale 都直接运行在同一台 Linux 机器上。
- Funnel 直接代理
127.0.0.1:7676 - 需要常驻时可使用 systemd
WSL
DevSpace 和 Tailscale 都装进同一个 WSL 发行版。
- 避免 Windows 到 WSL 的跨系统转发
- Funnel 和 DevSpace 共用同一个回环地址
Windows
DevSpace 和 Tailscale 都直接运行在 Windows 主机。
- 先确认
tailscaleCLI 可用 - 不要再把服务转发进另一个虚拟环境
macOS
DevSpace 和支持 Funnel CLI 的 Tailscale 客户端运行在同一台 Mac。
- 先确认当前客户端变体支持 Funnel
- 端口共享和文件共享的限制并不相同
开始前准备
先确认下面几件事:
- DevSpace 已经安装,并且至少能在当前环境里手动启动一次。
- 当前环境能运行
tailscaleCLI。 - 已有 Tailscale 账号,并有权限为这台设备启用 Funnel。
- 已经确定 DevSpace 只允许访问哪些项目目录。
开始前替换这些占位符
全文使用下面的占位符,复制命令时先替换成自己的真实值。
| 占位符 | 替换内容 |
|---|---|
devspace-device | 这台 Tailscale 设备的 hostname |
your-tailnet.ts.net | 你的 Tailscale Funnel 域名后缀 |
https://devspace-device.your-tailnet.ts.net | Funnel 提供的公网 Origin |
/absolute/path/to/project | 允许 DevSpace 访问的项目绝对路径 |
YOUR_DEVSPACE_START_COMMAND | 这台机器真正能启动 DevSpace 的命令 |
第一步:先确定 DevSpace 的真实启动方式
这一步不要先配置开机启动。先手动启动一次 DevSpace,确认这台机器真正使用的命令。
Linux / WSL / macOS
先检查:
command -v node
command -v devspace
devspace --help
Windows PowerShell
可以检查:
Get-Command node
Get-Command devspace
devspace --help
情况 A:devspace CLI 可以直接运行
如果当前版本支持下面的方式启动:
devspace serve
就把这个真实可用的命令记录下来。后续需要常驻时,再让系统启动机制执行它。
情况 B:找不到全局 devspace,但 DevSpace 已经能运行
这不一定是安装失败。Node 可能来自 NVM、Volta、asdf、某个工具内置环境或其他受管目录,DevSpace 也可能通过 Node 脚本启动。
这种情况不要强行改全局 PATH。直接记录已经验证可用的启动命令,例如:
/absolute/path/to/node /absolute/path/to/start-script.mjs
核心原则只有一个:后续自动启动时,使用的必须是已经手动验证成功的真实命令,不要假设每台机器都有全局 node 和 devspace。
第二步:安装并登录 Tailscale
不同系统的安装方式不同,这里不把某一个系统写成唯一标准。你只需要在 运行 DevSpace 的同一个环境 里完成安装和登录。
Linux / WSL 可以直接使用官方安装脚本:
curl -fsSL https://tailscale.com/install.sh | sh
如果当前环境使用 systemd,再启动服务:
sudo systemctl enable --now tailscaled
没有 systemd 的环境不要硬套这条命令,按当前系统实际支持的方式启动 Tailscale。判断是否成功的标准不是用了哪种服务管理器,而是 tailscale status 能正常返回设备状态。
Windows 和 macOS 使用对应客户端安装后,确认终端里能够执行:
tailscale status
登录,并给设备设置一个容易识别的 hostname:
tailscale up --hostname=devspace-device
如果当前平台需要管理员权限,就按平台要求执行;已经登录的设备不一定需要重新 up,重点是最后能看到设备在线。
确认:
tailscale status
这一步看到 100.x.x.x 很正常,但不要把它填进 ChatGPT。100.x 是 Tailnet 内网地址,不是公网 MCP 地址。
接着确认这台设备的完整 DNS 名称,例如:
devspace-device.your-tailnet.ts.net
从下一步开始,用这个真实域名替换全文占位符。到这里仍然没有打开 Funnel,只是先拿到 DevSpace 需要的公网 Origin。
第三步:配置 DevSpace
打开 DevSpace 配置文件。
Linux、WSL 和 macOS 通常是:
~/.devspace/config.json
Windows 则使用当前用户目录下对应的 .devspace/config.json。不要照抄别人机器的绝对路径。
核心配置类似这样:
{
"host": "127.0.0.1",
"port": 7676,
"allowedRoots": [
"/absolute/path/to/project"
],
"publicBaseUrl": "https://devspace-device.your-tailnet.ts.net",
"allowedHosts": [
"localhost",
"127.0.0.1",
"::1",
"devspace-device.your-tailnet.ts.net"
]
}
publicBaseUrl 只写公网 Origin,不带 /mcp。ChatGPT 里填写连接地址时,才加 /mcp。
allowedRoots 必须使用这台机器上 DevSpace 真正能识别的绝对路径。Linux、WSL、macOS 和 Windows 的路径格式不同,不要把示例路径原样复制到所有系统。
第四步:启动 DevSpace,并完成本地 OAuth 验收
先用第一步已经验证过的命令启动 DevSpace。
YOUR_DEVSPACE_START_COMMAND
这时先不要打开 Funnel。先确认本地端点和 OAuth discovery 都是完整的。
先看 /mcp 是否可达
curl -sS -o /dev/null -w '%{http_code}\n' \
http://127.0.0.1:7676/mcp
预期:
401
这个 401 是好结果:说明 /mcp 存在,只是当前请求还没有授权。
Windows PowerShell 也可以用自己习惯的 HTTP 请求工具验证,判断标准仍然是相同的状态码。
再看 OAuth metadata 是否完整
curl -sS -o /dev/null -w '%{http_code}\n' \
http://127.0.0.1:7676/.well-known/oauth-protected-resource/mcp
curl -sS -o /dev/null -w '%{http_code}\n' \
http://127.0.0.1:7676/.well-known/oauth-authorization-server/mcp
理想结果是:
200
200
只有这三个本地信号全部成立,才继续打开公网:
/mcp -> 401
protected resource metadata -> 200
authorization server metadata -> 200
如果 authorization server metadata 不是 200
不要先去排查 Funnel,也不要直接在 ChatGPT 里反复重连。先处理 DevSpace 本地 OAuth discovery。
优先顺序:
- 先确认当前 DevSpace 版本是否已经原生提供这个路由。
- 如果当前安装方式仍然缺少该路由,再加一个很薄的本地启动包装。
这个包装不负责公网代理,只是在 DevSpace app 开始监听前补上。下面只展示兼容路由的核心逻辑,不是完整启动脚本:
const publicBaseUrl = config.publicBaseUrl.replace(/\/+$/, '');
app.get('/.well-known/oauth-authorization-server/mcp', (_req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.json({
issuer: `${publicBaseUrl}/`,
authorization_endpoint: `${publicBaseUrl}/authorize`,
response_types_supported: ['code'],
code_challenge_methods_supported: ['S256'],
token_endpoint: `${publicBaseUrl}/token`,
grant_types_supported: ['authorization_code', 'refresh_token'],
scopes_supported: config.oauth.scopes,
revocation_endpoint: `${publicBaseUrl}/revoke`,
registration_endpoint: `${publicBaseUrl}/register`,
});
});
这段路由必须注册在 app.listen(...) 之前。至于 config 和 app 怎么导入,要按你的 DevSpace 安装方式处理,不要照抄别人的 Node 目录或包路径。
如果改成包装脚本启动,记得同步更新第一步记录的真实启动命令,然后重新验收本地三个信号。
可选:让 DevSpace 长期常驻
常驻不是 Funnel 的前提。 第一次配置时先手动跑通整条链路,确认没有问题后,再根据系统选择常驻方式。
| 系统 | 常见方式 |
|---|---|
| Linux / WSL | systemd |
| macOS | launchd 或登录项 |
| Windows | 任务计划程序或系统服务 |
Linux / WSL 的 systemd 示例:
[Unit]
Description=DevSpace MCP server
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=YOUR_USER
WorkingDirectory=/home/YOUR_USER
Environment=DEVSPACE_TOOL_MODE=codex
Environment=DEVSPACE_TRUST_PROXY=1
ExecStart=YOUR_DEVSPACE_START_COMMAND
Restart=always
RestartSec=2
[Install]
WantedBy=multi-user.target
这里的 User、WorkingDirectory 和 ExecStart 都只是占位符,必须替换成当前机器的真实值。其他平台同样遵守一个原则:自动启动机制只负责执行已经验证成功的 DevSpace 启动命令。
第五步:开启 Funnel 指向 DevSpace
直接把 Funnel 指向 DevSpace 的本地端口:
tailscale funnel --bg --yes 7676
查看状态:
tailscale funnel status
目标结构类似:
https://devspace-device.your-tailnet.ts.net
|-- / proxy http://127.0.0.1:7676
如果命令提示需要在 Tailscale 管理后台启用 Funnel,就按提示完成授权,再回到当前环境重新执行命令。
第六步:验证公网和 OAuth metadata
公网 /mcp 应该返回 401:
curl -sS -o /dev/null -w '%{http_code}\n' \
https://devspace-device.your-tailnet.ts.net/mcp
预期:
401
再验证 OAuth protected resource:
curl -sS -o /dev/null -w '%{http_code}\n' \
https://devspace-device.your-tailnet.ts.net/.well-known/oauth-protected-resource/mcp
预期:
200
再验证 authorization server metadata:
curl -sS -o /dev/null -w '%{http_code}\n' \
https://devspace-device.your-tailnet.ts.net/.well-known/oauth-authorization-server/mcp
预期:
200
这一步只是把第四步的三个本地信号搬到公网再验收一次。如果本地已经是 401 / 200 / 200,公网却不是,就只查 Funnel、域名和 Host 校验,不要回头改 DevSpace 业务逻辑。
刚启用 Funnel 时,公网 DNS 可能需要一点时间生效。一次解析失败不代表 Tailscale 安装坏了,先看 tailscale funnel status 是否正确,再继续判断。
第七步:在 ChatGPT 中连接
在 ChatGPT 的自定义 App / MCP 配置中填写:
https://devspace-device.your-tailnet.ts.net/mcp
认证使用 DevSpace 提供的 OAuth 流程。
不要填写:
http://100.x.x.x:7676/mcp
http://127.0.0.1:7676/mcp
https://devspace-device.your-tailnet.ts.net
最后做一次真实调用验收:在 allowedRoots 里创建一个测试文件,让 ChatGPT 读取它。能读到文件,才说明网络、OAuth、DevSpace 权限和项目根目录全部闭环。
常见问题
把 100.x IP 填给 ChatGPT
100.x 是 Tailnet 内地址,ChatGPT 默认访问不到。
- 错误:
http://100.x.x.x:7676/mcp - 正确:
https://devspace-device.your-tailnet.ts.net/mcp
publicBaseUrl 多写 /mcp
DevSpace 的 publicBaseUrl 只写 Origin,MCP 路径由客户端请求。
- 错误:
https://domain/mcp - 正确:
https://domain
Funnel 没真正打开
本地一切正常,但公网地址不可用时,先看 Funnel 状态和权限。
tailscale funnel status必须显示 Funnel 已开启- 首次使用时按提示启用 Funnel 权限
allowedHosts 没加 Funnel 域名
Funnel 已经可达,但 DevSpace 仍可能拒绝外部 Host。
- 加入
devspace-device.your-tailnet.ts.net - 修改后重启 DevSpace
OAuth metadata 缺路由
公网 /mcp 返回 401,不代表 ChatGPT 的 OAuth discovery 一定完整。
- 先在本地检查两个 metadata 端点
- 缺路由时回到第四步处理兼容层
Tailscale 和 DevSpace 不在同一环境
宿主机、WSL、虚拟机和容器里的 127.0.0.1 不一定指向同一个地方。
- 优先把两者放到同一个运行环境
- WSL 场景尤其不要默认依赖 Windows 到 WSL 转发
最终检查清单
这套方案真正要记住的只有一句:DevSpace 负责 MCP,Tailscale Funnel 负责公网 HTTPS;两者尽量放在同一个运行环境里。 Linux、WSL、Windows 和 macOS 的启动方式可以不同,但验收路径始终是同一条。