在 WSL2 中搭建 WebCodex + OpenAI Secure MCP Tunnel:让 ChatGPT 直接操作本地项目
隐私说明
本文已对账号、邮箱、API Key、Tunnel ID、Organization ID、Windows 用户名、WSL 用户名、真实项目名和绝对路径做脱敏处理。文中出现的
tunnel_xxx、sk-proj-xxx、my-project等均为示例占位符,不能直接作为真实凭据使用。
最终效果
这套方案的目标,是让 ChatGPT 通过 OpenAI Secure MCP Tunnel 安全地连接到运行在本机 WSL2 中的 WebCodex,再由 WebCodex Runner 去访问本地代码仓库。
最终链路如下:
ChatGPT
│
│ OpenAI Secure MCP Tunnel
▼
OpenAI Tunnel Service
│
│ control-plane poll / response
▼
tunnel-client(WSL2)
│
│ http://127.0.0.1:8080/mcp
▼
WebCodex Server
│
│ WebSocket
▼
WebCodex Runner
│
▼
本地 Git 项目完成后,ChatGPT 可以通过 MCP 工具直接执行项目级操作,例如读取文件、查看目录结构、检查 Git 状态、生成补丁、运行测试、执行构建命令等。
在当前部署中,已经验证以下链路均正常:WebCodex Server 正常监听、Runner 在线、项目已注册、MCP 会话初始化成功、Tunnel 正常启动、Tunnel readiness 返回 HTTP 200。
一、环境设计
我的部署环境是 Windows + WSL2。WebCodex Server、Runner 和 tunnel-client 全部运行在 WSL2 中,本地代码通过 /mnt/d/... 这类路径访问 Windows 磁盘。
核心端口采用:
127.0.0.1:8080 WebCodex Server / MCP
127.0.0.1:8081 tunnel-client health / admin UI这里特意把 Tunnel 的健康检查端口放在 8081,避免与 WebCodex 的 8080 冲突。
Important
OpenAI Tunnel 并不要求把 WebCodex 的
8080暴露到公网。tunnel-client在本机访问127.0.0.1:8080/mcp,再通过 OpenAI Tunnel control plane 与 ChatGPT 通信。
二、WebCodex 的职责划分
WebCodex 由两个核心部分组成。
WebCodex Server 负责提供 MCP API、项目管理接口和控制面;WebCodex Runner 则真正接触本地文件系统、Git、Shell、编译器和项目目录。
因此,真正需要注册本地项目的是 Runner,而不是 Tunnel。
例如一个脱敏后的 Runner 配置结构可能类似:
Server URL: http://127.0.0.1:8080
Transport: websocket
Allowed root: /mnt/d/workspace
Project: /mnt/d/workspace/my-project只要 Runner 显示 online,项目显示 connected=true,ChatGPT 通过 MCP 就能够访问这个项目。
三、WebCodex 认证如何传给 Tunnel
WebCodex MCP 默认启用了认证,因此直接访问:
http://127.0.0.1:8080/mcp会得到 HTTP 401,这是正常现象,并不表示服务坏了。
WebCodex 用户 Token 保存在本地配置目录。为了不把 Token 明文写进 systemd unit,我把它转换成一个仅本机用户可读的 Authorization header 文件:
TOKEN_FILE="$HOME/.config/webcodex/<server-profile>/webcodex-user-token"
AUTH_FILE="$HOME/.config/webcodex/tunnel-auth-header"
{
printf 'Bearer '
tr -d '\r\n' < "$TOKEN_FILE"
} > "$AUTH_FILE"
chmod 600 "$AUTH_FILE"然后交给 tunnel-client:
MCP_EXTRA_HEADERS=Authorization: file:$HOME/.config/webcodex/tunnel-auth-header
MCP_DISCOVERY_EXTRA_HEADERS=Authorization: file:$HOME/.config/webcodex/tunnel-auth-header这样 tunnel-client 在探测和调用 WebCodex MCP 时会自动携带认证信息。
Warning
webcodex-user-token、tunnel-auth-header、Runtime API Key 都属于敏感凭据。不要放入 Git、截图、博客、Shell History、公开日志或 Obsidian Sync 的公共仓库。
四、OpenAI Secure MCP Tunnel
OpenAI Tunnel 使用两项核心信息:
CONTROL_PLANE_TUNNEL_ID=tunnel_xxx
CONTROL_PLANE_API_KEY=sk-proj-xxxRuntime API Key 对应的组织主体需要具备 Tunnel 的 Read / Use 权限。
实际部署中最容易踩坑的一点,是 API Key 自身的 All 或 Full 权限,并不等价于组织 RBAC 中已经具备 Tunnels: Use。如果 control plane 返回:
401
code: tunnel_use_forbidden说明 API Key 已经被识别,但对应主体仍没有通过 Tunnel Use 授权检查。
另外,普通 Secure MCP Tunnel 不需要强制启用 --cloudflared.managed。如果 Tunnel 并没有配置 managed Cloudflare runtime material,访问 managed runtime endpoint 会得到:
404
Managed Cloudflare tunnel runtime material not found此时正确做法是直接使用普通 control-plane 模式:
tunnel-client run \
--health.listen-addr 127.0.0.1:8081启动成功后通常会看到:
mcp session initialized
poller started
tunnel metadata fetched
🟢 tunnel-client started这几行比单纯看到进程存在更有意义,因为它说明 MCP、本地 WebCodex 和 OpenAI control plane 都已经接通。
五、把 Tunnel 做成 systemd 系统服务
为了避免每次打开终端手动运行 tunnel-client run,我把它做成了 systemd system service。
服务文件:
/etc/systemd/system/openai-webcodex-tunnel.service一个脱敏后的配置如下:
[Unit]
Description=OpenAI Secure MCP Tunnel for WebCodex
Wants=network-online.target webcodex.socket
After=network-online.target webcodex.socket
[Service]
Type=simple
User=<wsl-user>
Group=<wsl-user>
WorkingDirectory=/home/<wsl-user>
Environment="HOME=/home/<wsl-user>"
Environment="MCP_EXTRA_HEADERS=Authorization: file:/home/<wsl-user>/.config/webcodex/tunnel-auth-header"
Environment="MCP_DISCOVERY_EXTRA_HEADERS=Authorization: file:/home/<wsl-user>/.config/webcodex/tunnel-auth-header"
ExecStart=/home/<wsl-user>/.local/bin/tunnel-client run \
--control-plane.tunnel-id=tunnel_xxx \
--control-plane.api-key=file:/home/<wsl-user>/.config/tunnel-client/runtime-api-key \
--mcp.server-url=http://127.0.0.1:8080/mcp \
--health.listen-addr=127.0.0.1:8081 \
--log.level=info \
--log.format=struct-text
Restart=always
RestartSec=5
TimeoutStopSec=20
[Install]
WantedBy=multi-user.targetRuntime API Key 单独保存在:
$HOME/.config/tunnel-client/runtime-api-key并限制权限:
chmod 600 "$HOME/.config/tunnel-client/runtime-api-key"启用服务:
sudo systemctl daemon-reload
sudo systemctl enable --now openai-webcodex-tunnel.service查看状态:
sudo systemctl status openai-webcodex-tunnel.service实时日志:
sudo journalctl -u openai-webcodex-tunnel.service -f六、WebCodex Server 与 Runner 的持久化
WebCodex Server 使用 systemd socket/service:
sudo systemctl enable --now webcodex.socketRunner 则作为用户级 systemd 服务:
systemctl --user enable --now webcodex-runner.service为了让用户级 service 在没有登录 shell 时仍能运行,需要:
sudo loginctl enable-linger <wsl-user>验证:
systemctl --user status webcodex-runner.serviceRunner 如果显示项目已经配置但 runtime check: skipped,并不等于 Runner 离线。进一步使用带 Server URL 和 Token 的状态/项目检查即可确认真实连接状态。
七、健康检查
Tunnel 的 readiness endpoint:
curl -i http://127.0.0.1:8081/readyz正常结果:
HTTP/1.1 200 OK
readyWebCodex 项目状态应至少满足:
runner_status: online
connected: true
git_available: true如果出现:
no_recommended_smoke_project这只是 WebCodex 没把当前项目标记为“推荐自动 smoke test 项目”,并不表示项目不能被 ChatGPT 正常访问。
八、在 ChatGPT 中创建自定义 MCP App
当本地服务全部正常后,在 ChatGPT 的 Apps / Connectors 中创建自定义 MCP App。
连接方式选择:
Connection: Tunnel
Tunnel: <自己的 Tunnel>
Auth: None这里选择 None 是因为 WebCodex 的 Bearer Token 已经由本机 tunnel-client 注入,ChatGPT 不需要再保存一份 WebCodex Token。
创建完成后,可以用类似下面的请求做验证:
使用 WebCodex 列出当前可用项目,并检查目标项目的 Git 状态。如果返回了正确的项目路径、分支、Git 修改状态,就说明完整链路已经建立。
九、以后能不能直接用?
可以,但有一个 WSL2 特有的前提:WSL 实例本身必须处于运行状态。
systemd 能保证 WebCodex、Runner 和 Tunnel 在 WSL 启动后自动运行,但 Windows 重启后,如果没有任何程序触发 WSL,WSL 发行版本身未必已经启动。
因此日常使用可以理解为:
Windows 已启动
↓
WSL 已启动
↓
systemd 自动启动 WebCodex + Runner + Tunnel
↓
ChatGPT 可以直接使用 WebCodex MCP如果希望 Windows 登录后完全自动拉起 WSL,可以额外建立 Windows Task Scheduler 任务,在登录时运行:
wsl.exe -d <你的发行版名称> --exec /bin/true发行版名称可以通过 PowerShell 查看:
wsl -l -q这样 Windows 登录后会主动启动 WSL,随后 systemd 服务自动拉起整套 MCP 链路。
Tip
如果平时本来就会打开 WSL、Docker Desktop、VS Code Remote WSL 等,那么通常不需要额外创建计划任务,因为这些操作本身就会启动 WSL。
十、日常维护命令
# Tunnel 状态
sudo systemctl status openai-webcodex-tunnel
# Tunnel 实时日志
sudo journalctl -u openai-webcodex-tunnel -f
# WebCodex Server
sudo systemctl status webcodex.socket
# WebCodex Runner
systemctl --user status webcodex-runner.service
# Tunnel readiness
curl -i http://127.0.0.1:8081/readyz
# 重启 Tunnel
sudo systemctl restart openai-webcodex-tunnel
# 重启 Runner
systemctl --user restart webcodex-runner.service这些命令基本足够覆盖日常维护。
十一、需要长期注意的事情
- 不要把 API Key 写进公开笔记、Git 仓库、截图或博客。 本文只保留
sk-proj-xxx占位符。 - 不要公开真实 Tunnel ID、Organization ID、用户邮箱和本地绝对路径。 单独看它们未必都等同于密码,但组合起来会增加环境指纹和攻击面。
- API Key 一旦出现在聊天记录、终端录屏、日志或截图中,应立即旋转。 新 Key 写入本地权限为
600的文件后,再重启 Tunnel 服务。 - 本地项目权限边界由 WebCodex Runner 决定。
allowed_roots不要设置得过宽,尽量只开放真正需要 ChatGPT 操作的开发目录。 - ChatGPT 能执行的实际操作取决于 WebCodex 暴露的工具。 在重要仓库中,执行删除、覆盖、大规模重构、数据库迁移、Git reset/rebase 等操作前,仍应检查 Git diff 和备份。
OAuth discovery failed在当前这种本地 Bearer Header 模式下可以是非致命警告。 如果后面已经出现mcp session initialized和tunnel-client started,就不要仅因为这一条 warning 判定服务失败。- Codex Tunnel MCP plugin 是可选项。 它主要给 Codex 自身提供更方便的 Tunnel 控制入口,不是 ChatGPT 使用 WebCodex 的必需组件。
- Windows 挂载盘要存在。 如果项目位于
/mnt/d/...,D 盘不可用、BitLocker 未解锁或挂载异常时,Runner 会看到项目不可访问。 - 升级前先记住当前可工作的版本。 WebCodex、tunnel-client、MCP 协议和 ChatGPT Connector 都可能继续迭代。升级后如果出问题,优先核对版本、服务日志和 MCP initialize 是否成功。
十二、故障排查顺序
遇到 ChatGPT 突然无法访问本地项目时,不要一上来重装。按链路从内到外检查:
项目目录是否存在
↓
Runner 是否 online
↓
WebCodex Server 是否正常
↓
127.0.0.1:8080/mcp 是否可达
↓
tunnel-client 是否 active
↓
/readyz 是否 200
↓
tunnel-client 日志是否出现 started / poller started
↓
ChatGPT MCP App 是否仍绑定正确 Tunnel推荐命令:
systemctl --user status webcodex-runner.service
sudo systemctl status webcodex.socket
sudo systemctl status openai-webcodex-tunnel.service
curl -i http://127.0.0.1:8081/readyz
sudo journalctl -u openai-webcodex-tunnel.service -n 100 --no-pager如果 readyz=200、Runner online、项目 connected,而 ChatGPT 仍不能使用工具,再去检查 ChatGPT App/Connector 本身。
结语
这套方案的核心价值,不是简单地“把一个 MCP 暴露到公网”,而是把本地开发环境拆成了清晰的权限边界:
ChatGPT
只知道 Tunnel
OpenAI Tunnel
只负责请求传输
tunnel-client
只连接本机 MCP
WebCodex Server
提供 MCP 控制面
WebCodex Runner
决定真正可以访问哪些项目和目录相比直接把本地 MCP Server 暴露到公网,这种结构更适合长期使用,也更容易做服务化、权限隔离和故障排查。
对于个人开发环境,完成 systemd 持久化和 WSL 自动启动之后,日常基本可以做到:打开 ChatGPT,直接让它进入本地项目工作,而不需要每次重新配置 Tunnel、重新启动 Runner 或重新粘贴 Token。