实践

用 Caddy + ChmlFrp 暴露 DevSpace 给 ChatGPT

一份可直接照抄的配置教程:在 WSL 中运行 DevSpace,用 Caddy 提供本地 HTTPS,再通过 ChmlFrp 暴露为公网地址,最终接入 ChatGPT。

作者:黄撑 更新于 2026-07-07

这篇文章只做一件事:把 WSL 里的 DevSpace 通过 Caddy 和 ChmlFrp 暴露成公网 HTTPS 地址,然后接入 ChatGPT。

本文假设你已经安装好 DevSpace、Caddy 和 ChmlFrp,并且已经准备好 ChmlFrp 隧道、独立域名和对应的 DNS 解析。下面只讲配置和验证,可以从上往下直接照抄。

为什么我选择这套方案

Cloudflare Tunnel 确实更省事,但对我这种「个人开发机 + WSL + DevSpace」场景,核心需求不是 CDN 或全球加速,而是让 ChatGPT 直接使用本机项目,同时把代码、运行环境和网络链路掌握在自己手里。WSL 提供 Linux 环境,DevSpace 让 ChatGPT 获得读取、搜索、修改和执行项目的能力,Caddy + ChmlFrp 负责提供公网入口。这样 VS Code、DevSpace 和 ChatGPT 操作的是同一份本地代码,链路也更直接。简单说:AI 获得了操作能力,但代码和运行环境仍然在自己的机器上。

最终架构

先看清楚整条链路,后面的所有配置都围绕这四层展开。

FINAL ARCHITECTURE

ChatGPT 到 WSL 的最终连接链路

公网请求先进入 ChmlFrp,再转发到 Caddy,最后由 Caddy 代理到 DevSpace。

公网调用 ChatGPT /mcp
HTTPS
公网隧道 ChmlFrp HTTPS tunnel
HTTPS
本地 HTTPS Caddy 127.0.0.1:7677
HTTP
MCP 服务 DevSpace 127.0.0.1:7676
01 ChatGPT 使用 HTTPS
https://your-domain.example.com/mcp

最终填到 ChatGPT 里的公网地址。

02 Caddy 本地监听 HTTPS
127.0.0.1:7677

ChmlFrp 在 WSL 内部转发到这里。

03 DevSpace 本地监听 HTTP
127.0.0.1:7676

Caddy 最终反向代理到真正的 MCP 服务。

这套配置来自一台已经实际跑通的 WSL 环境,但正文只保留可迁移的部分:不要求固定 Node 安装方式、不绑定特定用户目录,也不要求复制某一台机器的启动脚本。

开始前先替换这些占位符

全文统一使用以下占位符。复制配置后,把它们替换成自己的真实值。

占位符替换内容
YOUR_WSL_USERWSL 用户名
YOUR_CHMLFRP_USERChmlFrp 用户 ID
YOUR_CHMLFRP_TOKENChmlFrp Token
YOUR_CHMLFRP_NODE_IPChmlFrp 节点 IP
YOUR_TUNNEL_NAMEChmlFrp 面板中的隧道名称
your-domain.example.com自己的公网域名
/home/YOUR_WSL_USER/projects/example想让 DevSpace 访问的项目目录
YOUR_NODE_BIN_DIRnode 所在目录,例如 /usr/bin 或 NVM 的实际 bin 目录
YOUR_DEVSPACE_BINdevspace 可执行文件的完整路径
YOUR_CHMLFRP_SERVER_PORTChmlFrp 节点信息提供的服务端口
YOUR_FRPC_BINfrpc 可执行文件的完整路径
YOUR_FRPC_CONFIGfrpc.ini 的完整路径

配置顺序

每一层通过验收,再继续下一层

Step 1 初始化

WSL 启用 systemd,确认 Node 和 DevSpace 实际路径,再完成 DevSpace 初始化。

Step 2 DevSpace

启动 7676,本地请求 /mcp 返回 401。

Step 3 Caddy

启动 7677,本地 HTTPS 请求 /mcp 返回 401。

Step 4 ChmlFrp

公网 https://your-domain.example.com/mcp 返回 401。

Step 5 ChatGPT

完成授权后,实际读取 allowedRoots 中的测试文件。

第一步:配置 WSL

编辑:

/etc/wsl.conf

写入:

[boot]
systemd=true

[interop]
appendWindowsPath = true

保存后,在 Windows PowerShell 中完全关闭 WSL:

wsl --shutdown

然后重新打开 Ubuntu。

检查 systemd 是否可用:

systemctl is-system-running

appendWindowsPath 必须放在 [interop] 下,不要写进 [boot]

第二步:确认 Node / DevSpace 路径,并完成初始化

不同机器的 Node 可能来自系统包、NVM、Volta、asdf 或其他环境。不要照抄别人的 Node 路径,先检查自己的实际路径:

command -v node
command -v devspace

例如可能返回:

/usr/bin/node
/usr/local/bin/devspace

也可能是:

/home/YOUR_WSL_USER/.nvm/versions/node/vXX/bin/node
/home/YOUR_WSL_USER/.nvm/versions/node/vXX/bin/devspace

记录这两个结果:

YOUR_NODE_BIN_DIR = node 所在目录
YOUR_DEVSPACE_BIN = devspace 的完整路径

例如 command -v node 返回 /usr/bin/node,那么:

YOUR_NODE_BIN_DIR=/usr/bin

先确认当前版本的启动命令:

devspace --help

本文以这个启动命令为例:

devspace serve

然后初始化 DevSpace:

devspace init

完成初始化后确认这两个文件都存在:

ls ~/.devspace/config.json ~/.devspace/auth.json

必须看到:

~/.devspace/config.json
~/.devspace/auth.json

auth.json 属于私密文件,不要提交到公开仓库。

第三步:配置并启动 DevSpace

编辑:

~/.devspace/config.json

完整配置:

{
  "host": "127.0.0.1",
  "port": 7676,
  "allowedRoots": [
    "/home/YOUR_WSL_USER/projects/example"
  ],
  "publicBaseUrl": "https://your-domain.example.com",
  "allowedHosts": [
    "localhost",
    "127.0.0.1",
    "::1",
    "your-domain.example.com"
  ]
}

这里主要修改三处:

  1. YOUR_WSL_USER
  2. allowedRoots 里的项目目录
  3. your-domain.example.com

配置 DevSpace systemd 服务

新建:

/etc/systemd/system/devspace.service

写入:

[Unit]
Description=DevSpace MCP server
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=YOUR_WSL_USER
WorkingDirectory=/home/YOUR_WSL_USER
Environment=DEVSPACE_TOOL_MODE=codex
Environment=DEVSPACE_TRUST_PROXY=1
Environment=PATH=YOUR_NODE_BIN_DIR:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
ExecStart=YOUR_DEVSPACE_BIN serve
Restart=always
RestartSec=2

[Install]
WantedBy=multi-user.target

YOUR_NODE_BIN_DIRYOUR_DEVSPACE_BIN 替换成上一步实际检测到的值。

systemd 不会自动继承你平时终端里的 Node 环境,所以这里显式写入 Node 所在目录,并直接使用 DevSpace 可执行文件的完整路径。

这里只保留和接入目标直接相关的环境变量。日志格式、Widget 等偏好属于个人配置,不写进通用教程。

  • DEVSPACE_TOOL_MODE=codex:本文使用编码工作区工具模式。
  • DEVSPACE_TRUST_PROXY=1:DevSpace 前面还有反向代理,需要信任代理转发信息。

执行:

sudo systemctl daemon-reload
sudo systemctl enable --now devspace.service

检查状态:

systemctl status devspace.service --no-pager -l

目标:

active (running)

验收 DevSpace:7676 必须返回 401

执行:

curl -sS -o /dev/null -w '%{http_code}\n' \
  http://127.0.0.1:7676/mcp

预期:

401

这个 401 是好结果:DevSpace 已经启动,/mcp 路径存在,只是当前请求没有认证。

第四步:配置并验收 Caddy

编辑:

/etc/caddy/Caddyfile

写入:

{
    https_port 7677
}

https://your-domain.example.com {
    bind 127.0.0.1
    reverse_proxy 127.0.0.1:7676
}

这段配置只做一件事:

Caddy 127.0.0.1:7677 / HTTPS

DevSpace 127.0.0.1:7676 / HTTP

重启并启用 Caddy:

sudo systemctl restart caddy
sudo systemctl enable caddy

检查状态:

systemctl status caddy.service --no-pager -l

目标状态:

active (running)

确认 7676 和 7677 都在监听

执行:

ss -ltn | grep -E ':(7676|7677)\b'

应该同时看到:

127.0.0.1:7676
127.0.0.1:7677

验收 Caddy:本地 HTTPS 必须返回 401

执行:

curl -sk \
  --resolve your-domain.example.com:7677:127.0.0.1 \
  -o /dev/null \
  -w '%{http_code}\n' \
  https://your-domain.example.com:7677/mcp

预期:

401

做到这里,可以确认:

DevSpace 7676 正常
+
Caddy 7677 HTTPS 正常

如果这一步还没成功,不要先排查 ChmlFrp。

第五步:配置并验收 ChmlFrp

编辑:

~/.chmlfrp/frpc.ini

完整模板:

[common]
server_addr = YOUR_CHMLFRP_NODE_IP
server_port = YOUR_CHMLFRP_SERVER_PORT
tls_enable = false
login_fail_exit = false
user = YOUR_CHMLFRP_USER
token = YOUR_CHMLFRP_TOKEN

[YOUR_TUNNEL_NAME]
type = https
local_ip = 127.0.0.1
local_port = 7677
custom_domains = your-domain.example.com

最关键的是这两行:

type = https
local_port = 7677

这里的 local_port 必须指向 Caddy,不要指向 DevSpace。

这一段只有 local_ip = 127.0.0.1local_port = 7677type = https 是本文架构里的固定关系;服务端地址和端口按你自己的 ChmlFrp 节点信息填写。

另外:

[YOUR_TUNNEL_NAME]

必须替换成 ChmlFrp 面板里的真实隧道名称。

新建:

/etc/systemd/system/chmlfrp-frpc.service

写入:

[Unit]
Description=ChmlFrp FRPC
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=YOUR_WSL_USER
ExecStart=YOUR_FRPC_BIN -c YOUR_FRPC_CONFIG
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

把上面的占位符都替换成自己的实际值后执行:

sudo systemctl daemon-reload
sudo systemctl enable --now chmlfrp-frpc.service

检查:

systemctl status chmlfrp-frpc.service --no-pager -l

再看最近日志:

journalctl -u chmlfrp-frpc.service -n 50 --no-pager -l

正常时应该看到类似:

login to server success
proxy added: [YOUR_TUNNEL_NAME]
[YOUR_TUNNEL_NAME] start proxy success

验收公网 MCP:必须返回 401

执行:

curl -sS -o /dev/null -w '%{http_code}\n' \
  https://your-domain.example.com/mcp

预期:

401

如果公网也返回 401,说明这条链路已经真正打通:

公网 HTTPS
→ ChmlFrp
→ Caddy 7677
→ DevSpace 7676

再验证 OAuth metadata

执行:

curl -sS https://your-domain.example.com/.well-known/oauth-protected-resource/mcp

正常时应该返回类似:

{
  "resource": "https://your-domain.example.com/mcp",
  "authorization_servers": ["https://your-domain.example.com/"],
  "scopes_supported": ["devspace"],
  "resource_name": "DevSpace"
}

第六步:让 Windows 登录后保持 WSL 运行

WSL 退出后,里面的 DevSpace、Caddy 和 frpc 都会停止。

最简单的做法是在 Windows 启动目录放一个 VBS 脚本,让 WSL 在登录后保持运行。

文件路径:

%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\DevSpace-WSL-KeepAlive.vbs

文件内容:

Set shell = CreateObject("WScript.Shell")
shell.Run """C:\Windows\System32\wsl.exe"" -d Ubuntu --exec sleep infinity", 0, False

这里的:

Ubuntu

要和下面命令显示的发行版名称一致:

wsl -l -v

例如看到:

NAME      STATE      VERSION
Ubuntu    Running    2

那 VBS 中就继续使用:

-d Ubuntu

第七步:一次性检查所有服务

先检查 WSL:

wsl -l -v

目标:

Ubuntu    Running    2

再检查三个 systemd 服务:

systemctl status devspace.service caddy.service chmlfrp-frpc.service --no-pager -l

三个都应该是:

active (running)

第八步:在 ChatGPT 中连接,并完成实际调用验收

最终填写:

https://your-domain.example.com/mcp

不要填写:

https://your-domain.example.com:7677/mcp

也不要填写:

http://127.0.0.1:7676/mcp

ChatGPT 只需要公网 HTTPS 地址。

填写地址后,完成 DevSpace 的授权流程。

然后在 allowedRoots 中准备一个最简单的测试文件:

printf 'devspace-ok\n' > \
  /home/YOUR_WSL_USER/projects/example/DEVSPACE_VERIFY.txt

接着让 ChatGPT 通过 DevSpace 读取:

DEVSPACE_VERIFY.txt

预期内容:

devspace-ok

常见的坑

正文配置照抄后,如果仍然连不上,优先检查下面几项。

先检查这三个通用问题

systemd 找不到 Node 或 DevSpace

终端里能运行 devspace,不代表 systemd 也能找到 Node。

重新检查:

command -v node
command -v devspace

然后确认 devspace.service 中:

Environment=PATH=...
ExecStart=...

使用的就是这两个实际路径。

DevSpace 启动命令和本文不同

先执行:

devspace --help

确认当前版本是否使用:

devspace serve

不要把其他机器或其他版本的自定义启动脚本直接复制过来。

auth.json 不存在

检查:

ls ~/.devspace/auth.json

如果不存在,先确认已经执行:

devspace init

不要只手工创建 config.json 就直接启动服务。

PORT

ChmlFrp 指错端口

`local_port` 写成了 DevSpace 的 7676,而不是 Caddy 的 7677。

  • 错误:local_port = 7676
  • 正确:local_port = 7677
URL

publicBaseUrl 多写了 /mcp

DevSpace 的 `publicBaseUrl` 只写公网 Origin,不包含 MCP 路径。

  • 错误:https://domain/mcp
  • 正确:https://domain
TUNNEL

隧道名称不一致

`frpc.ini` 的 section 名称必须和 ChmlFrp 面板中的隧道名称一致。

  • 检查 [YOUR_TUNNEL_NAME]
  • 再看日志是否有 start proxy success
PUBLIC

把 7677 写进公网 URL

7677 只在 WSL 本地使用,不属于 ChatGPT 的连接地址。

  • 错误:https://domain:7677/mcp
  • 正确:https://domain/mcp
WSL

WSL 已经退出

WSL 停止后,里面的 DevSpace、Caddy 和 frpc 会一起停止。

  • 先执行 wsl -l -v
  • 确认 Ubuntu 状态是 Running
VERIFY

只看面板,不看公网验证

ChmlFrp 面板显示 HTTPS,不代表整条链路已经真的可用。

  • 先请求 OAuth metadata
  • 再请求 /mcp 看是否返回 401

最终检查清单

这套配置真正需要记住的只有一条:公网 HTTPS 先到 ChmlFrp,再进入 Caddy 的 7677,最后由 Caddy 转发到 DevSpace 的 7676。前三层都返回 401,最后 ChatGPT 能实际读取测试文件,才算整条链路真正完成。