Francis Du.

💻Data Engineer | 🦀 Rustacean | 📷 Photographer | 🤖Vibe Coder

23 2026年9月

固定隧道的 MCP:Cloudflare Tunnel、Tailscale Funnel 和 wcode

本文目录10 个章节

wcode 自带 Tunnel,所以临时跑一个仓库时,我一般懒得配任何东西:启动以后拿公网 MCP 地址,丢进 ChatGPT、Claude 或其他支持 Remote MCP 的客户端就行。

真正开始长期用以后,我反而不想让 MCP 地址跟着每次 Tunnel 重启一起变。

原因很实际:地址一变,客户端配置要改,OAuth resource URL 也跟着变。对一个偶尔开的开发服务无所谓,对每天都用的本地代码入口就挺烦。

所以我现在会给常用的 wcode 留一个固定隧道入口。Cloudflare Tunnel 和 Tailscale Funnel 我都在用,结构其实一样:

ChatGPT / Claude / other MCP client
               │  stable HTTPS /mcp
    Cloudflare Tunnel / Tailscale Funnel
        127.0.0.1:8765
             wcode

本机还是只监听 loopback,不开路由器端口,也不需要把 MCP Server 暴露给整个局域网。

wcode 只需要知道外面那个稳定的 HTTPS origin 是什么。

这个就是 –public-url

wcode 本地其实不用改什么#

当前 wcode 的 Remote MCP 默认监听:

127.0.0.1:8765

所以如果 cloudflared 和 wcode 跑在同一台机器上,Tunnel 的 origin 直接指:

http://127.0.0.1:8765

就够了。

我反而不建议为了 Tunnel 把 wcode 改成监听 0.0.0.0。Cloudflare 可以直接访问 loopback,没有必要顺手把本地 MCP Server 暴露给整个局域网。

真正需要改的是启动参数。假设最后的域名是:

https://mcp.example.com

启动 wcode:

wcode --public-url https://mcp.example.com

之后给 MCP Client 的地址就是:

https://mcp.example.com/mcp

–public-url 的作用不是关掉 OAuth,也不是把一个 URL 单纯显示在 TUI 里。它告诉 wcode:这个地址是外部已经准备好的稳定入口,不要再替我启动随机的 managed tunnel,OAuth 和 MCP 对外都按这个 origin 工作。

这也是我拿 ChatGPT “白嫖模型”的一种方式#

我做 wcode 最早其实就有一个很现实的动机:我已经在 ChatGPT Web 端用模型了,就不想为了让它碰本地代码,再单独开一份 API Token 账单。

固定隧道把这件事变得比较顺:

ChatGPT
   │ Remote MCP
https://固定地址/mcp
wcode
本地仓库

严格说这当然不是真的“免费模型”。

如果我本来就在付 ChatGPT 的套餐,或者账户本来就有可用额度,那模型算力还是来自 ChatGPT;我省掉的是同一件 coding 工作再走一遍 OpenAI API、再按 API Token 计费。对我自己来说,这就是很朴素的“白嫖 Web 端模型”。

这里还有一个现在必须写清楚的限制。

截至 2026 年 9 月 23 日,OpenAI 的公开文档写的是:ChatGPT 连接的是 remote MCP,不能直接拿 127.0.0.1 当 MCP 地址;Pro 用户在 Developer Mode 里可以接 read/fetch 权限的 MCP,而完整的 write/modify MCP 仍然主要在 Business、Enterprise 和 Edu 的 beta 范围里。

所以“ChatGPT 能不能直接让 wcode 改代码”取决于你当前的套餐和 Workspace 权限,这个产品矩阵以后也肯定还会变。

但网络这一层是一样的:ChatGPT 必须能稳定访问到 wcode 的 Remote MCP 地址。

这就是固定 Tunnel 真正解决的问题。

我现在会优先用 Cloudflare Dashboard 管 Tunnel#

Cloudflare 目前更推荐 remotely-managed tunnel。配置存在 Cloudflare 侧,本机拿一个 Tunnel Token 跑 connector,不需要自己维护 credentials JSON 和完整的 ingress 配置。

先装 cloudflared。macOS 最简单:

brew install cloudflared

然后去 Cloudflare Dashboard:

Networking
  → Tunnels
  → Create Tunnel

名字随便,我一般会直接叫 wcode

Tunnel 建好以后,加一个 Published application:

Hostname
  mcp.example.com

Service
  http://127.0.0.1:8765

域名本身要已经托管在 Cloudflare。

这里有个我第一次用 Tunnel 时容易想复杂的地方:不用自己再去 DNS 页面手搓一条记录。

从 Tunnel 里添加 Published application 时,Cloudflare 会把 hostname 路由到这个 Tunnel,并建立对应的 DNS 记录。

接下来在跑 wcode 的机器上安装 connector。Dashboard 会直接给一条带 Token 的命令,形式大概是:

sudo cloudflared service install <TUNNEL_TOKEN>

Token 是凭据,别提交到 Git,也别顺手贴到博客、Issue 或终端截图里。

cloudflared 连上以后,再启动:

wcode --public-url https://mcp.example.com

到这里固定地址就有了。

如果我想把 Tunnel 配置留在本机#

Dashboard 方式比较省心,但有时候我就是想看见完整配置,或者一台机器上还有别的本地服务需要一起挂。

这种情况我会用 locally-managed tunnel。

先登录:

cloudflared tunnel login

创建一个 named tunnel:

cloudflared tunnel create wcode

它会生成一个 Tunnel UUID,以及类似下面的 credentials 文件:

~/.cloudflared/<TUNNEL-UUID>.json

然后写 ~/.cloudflared/config.yml

tunnel: <TUNNEL-UUID>
credentials-file: /Users/your-name/.cloudflared/<TUNNEL-UUID>.json

ingress:
  - hostname: mcp.example.com
    service: http://127.0.0.1:8765

  - service: http_status:404

最后那个 404 不是装饰。Cloudflare 的 ingress 规则要求最后有一条 catch-all,不匹配前面 hostname 的请求就到这里结束。

先检查配置:

cloudflared tunnel ingress validate

再把域名路由到 Tunnel:

cloudflared tunnel route dns wcode mcp.example.com

然后运行:

cloudflared tunnel run wcode

这时候再开另一个终端:

wcode --public-url https://mcp.example.com

固定域名这条链就完整了。

macOS 上我会直接让 cloudflared 常驻#

如果每次都手动开一个 cloudflared tunnel run wcode,固定域名是固定了,但进程还是得自己记着启动。

本地管理的 Tunnel 在 macOS 上可以直接装成 LaunchAgent:

cloudflared service install

这种方式使用当前用户的:

~/.cloudflared/config.yml

登录以后自动启动,对我这种开发机最合适。

如果需要开机就跑、不依赖用户登录,也可以装成系统 LaunchDaemon:

sudo cloudflared service install

但这时 Cloudflare 默认读取的是 /etc/cloudflared 下的配置,不是当前用户 Home 里的那份。这个区别挺容易把自己坑一下:明明 config.yml 写好了,一加 sudo 就提示找不到配置。

开发机上没必要把事情搞复杂,我一般就让它跟用户登录一起起来。

Tailscale Funnel 更省事,固定的是 ts.net 地址#

如果机器本来就在跑 Tailscale,我其实更喜欢 Funnel 这一条。

Cloudflare Tunnel 的优势是可以直接用自己的域名;Tailscale Funnel 的优势是基本不用碰 DNS,设备会拿到一个稳定的 *.ts.net 地址。

这里要说清楚:Funnel 不能随便绑我自己的域名。 它公开出来的 DNS 名必须在当前 tailnet 的 ts.net 域下面。

先保证这台机器已经登录 Tailscale,然后直接把 wcode 的本地端口公开出去:

tailscale funnel --bg 8765

当前 Funnel CLI 会把本机的:

http://127.0.0.1:8765

代理成一个公网 HTTPS 地址,大概长这样:

https://my-mac.my-tailnet.ts.net

看实际地址:

tailscale funnel status

然后启动 wcode:

wcode --public-url https://my-mac.my-tailnet.ts.net

MCP Client 填:

https://my-mac.my-tailnet.ts.net/mcp

就可以了。

Tailscale 官方现在把 Funnel 的定位写得很直接:本地服务通过稳定的 HTTPS ts.net 地址公开到互联网,机器在线、Tailscale 在线,Funnel 就继续工作。

第一次跑时,如果 tailnet 还没开 HTTPS、MagicDNS 或 Funnel 权限,CLI 会带你去网页确认。

这条路对我最大的好处就是少配一层东西。没有 Tunnel Token,没有 Cloudflare DNS route,也没有 config.yml

代价也很明确:域名是 Tailscale 的 ts.net,不是我自己的域名;而且 Funnel 是公网入口,不是只在 tailnet 里可见。只想让自己的设备访问,应该用的是 tailscale serve,不是 Funnel。

不管用哪种 Tunnel,我都会先看 /healthz#

Cloudflare 的例子:

curl -sS https://mcp.example.com/healthz | jq

Tailscale 的例子:

curl -sS https://my-mac.my-tailnet.ts.net/healthz | jq

正常情况下能看到 ok: true,同时还有当前 wcode 实例的信息。

这个检查比直接拿 MCP Client 试省事很多:

/healthz 都不通
    → Tunnel / DNS / origin 有问题

/healthz 正常,MCP Client 连不上
    → 再查 OAuth / Client 兼容

还有一个容易误判的地方。

直接访问:

curl -i https://mcp.example.com/mcp

在还没完成 OAuth 时看到 401 Unauthorized 不代表 Tunnel 坏了。

wcode 的 MCP endpoint 本来就受 OAuth 保护。Tunnel 只负责把请求送到本机,不会绕过认证。

一个 Tunnel 可以挂多个本地服务#

Cloudflare Tunnel 不要求一个 Tunnel 只服务一个域名。

比如同一台机器还有别的东西:

ingress:
  - hostname: mcp.example.com
    service: http://127.0.0.1:8765

  - hostname: dashboard.example.com
    service: http://127.0.0.1:3000

  - hostname: api.example.com
    service: http://127.0.0.1:8080

  - service: http_status:404

每个 hostname 再各自建 DNS route 就行。

不过 wcode 我还是会给它单独一个 hostname,不和其他 Web App 混 path。

原因不是 Cloudflare 做不到 path routing,而是 wcode 下面还有 OAuth metadata、authorize、token、health 和 MCP endpoint。独立 hostname 少很多“为什么这个 well-known 路径被前面的代理规则吃掉了”的问题。

真正要固定的是 public origin#

wcode 不在乎外面到底是 Cloudflare 还是 Tailscale。

我现在常用的就是两种:

Cloudflare Tunnel
https://mcp.example.com

Tailscale Funnel
https://my-mac.my-tailnet.ts.net

然后分别告诉 wcode:

wcode --public-url https://mcp.example.com

或者:

wcode --public-url https://my-mac.my-tailnet.ts.net

只要这个 public origin 能完整转发到本机 wcode,并且 HTTPS、Host、OAuth metadata 和 MCP 路径没被代理层改坏,后面的逻辑就是同一套。

Cloudflare 适合我想用自己域名的时候;Tailscale Funnel 适合机器本来就在 tailnet 里、我只想最快拿一个稳定 HTTPS 地址的时候。

Quick Tunnel 我也还是会留着。临时仓库一条命令拿随机地址最舒服。

但常用实例我更愿意固定下来。

尤其是当它后面接的是 ChatGPT 这种 Web 端模型时,我不想每次 wcode 重启以后再去改一次 MCP 配置。固定 Tunnel 以后,它才真的像一个长期存在的本地代码入口。

相关文档#