1. “随机”隧道的问题
当你第一次运行 ngrok http 3000 或者快速创建 Cloudflare Quick Tunnel 时,感觉就像魔法:啪的一下——你的 http://localhost:3000 变成了 https://random-1234.tunnelprovider.com。把这个 URL 复制到 ChatGPT 的 Dev Mode,GPT 就会开心地加载你的 App。
然后你重启了隧道……又得到一个新域名。ChatGPT 中 Dev 应用设置里的旧 URL 突然变成了“坏链路”,GPT 很诚实地写着 “App unavailable”,你又去设置里改 URL,点 Save,等它同步,然后对整套栈默默生无可恋。
一次性的“晚上玩玩”还能忍。但当你:
- 每天都在迭代 App;
- 想把中间版本给同事/经理看看;
- 同时还要开 staging 和 production,
为每个新的随机 URL 去重连 Dev Mode 就变成了纯粹的折磨。
此外,如果你已经在应用里加了依赖域名的东西(比如 OAuth redirect URI 或 webhook),每一个新 URL 也会把它们一起搞坏。于是出现连锁反应:换了隧道——你就得去改 App 配置、OAuth 提供方的 redirect‑URL,以及 webhook 接收端的设置。
由此得出本讲的核心观点:稳定的 dev‑URL 不是奢侈品,而是开发者心理健康的守护工具。
Insight
ChatGPT 对你的应用有非常严格的超时限制,这很容易被低估。MCP 工具调用有时间上限:最多 2 分钟——之后平台就直接视为调用失败,即使你的服务器还在处理。
对于应用注册(Store 或 Dev Mode)则更严格:ChatGPT 在读取清单、资源与工具描述时大约只给 20 秒。如果在这段时间内你的 MCP 服务器还没初始化完成、没返回 tools/resources 等,App 的注册就会超时失败。
建议:所有耗时的初始化都应当发生在你去 Dev Mode 或 Store 之前。例如预热数据库连接、加载大型配置、构建懒加载缓存——这些最好提前完成,比如先通过 MCP Jam 或一段内部脚本“打”一次服务器。从平台视角看,MCP 服务器必须是“热”的,几秒内响应,而不是在注册期间“醒来”。
2. 什么是“成熟级”隧道
先明确,“成熟级”隧道与课程最开始你用的那种有什么不同。
早期模式(模块 2)大致是这样:
# ngrok 示例
ngrok http 3000
# 得到: https://random-abc123.ngrok-free.app
你把这个一次性 URL 填进 Dev Mode。下次启动 ngrok,URL 已经变了,ChatGPT 的配置也就过期了。
在“成熟级”的做法里,你会:
- 拥有一个静态子域(来自隧道提供方,或使用你自己的域名);
- 同一个域名总是被转发到你的 localhost:3000;
- 你可以重启隧道、机器、路由器,但 URL 始终不变。
例如,这样的静态子域可用在:
- ngrok —— 账号层面的免费静态域名;
- Cloudflare Tunnel —— 通过命名隧道并绑定到你自己的域名。
而 ChatGPT 的 Dev 应用恰好只配置了这一个 URL,从此不再打扰你。
从形式上讲,我们“成熟级”隧道的要求是:
- 稳定且唯一的公共 HTTPS 域名;
- 有效的 TLS 证书(由提供方代办);
- 一个配置,表达“凡是到达 https://dev.yourdomain.com 的请求,都转发到 http://localhost:3000”;
- 可选——最起码的安全措施(别把 URL 晒到 StackOverflow 就行)。
3. 配置稳定的 dev‑URL:以 Cloudflare Tunnel 为例
本课程推荐把 Cloudflare Tunnel 作为主力工具,因为它在开发与更严肃的场景里都很好用。你在模块 2 已经见过基础配置,现在把它“拧紧”到一个永久的 dev‑URL。
假设我们的示例应用叫 GiftGenius,我们希望有稳定的 URL giftgenius-dev.yourdomain.com。
最小化步骤(简化,不依赖 Cloudflare 的 UI):
- 把你的域名接入到 Cloudflare 账号(一次性,通过他们的面板)。
- 在本地安装 cloudflared 并登录。
brew install cloudflare/cloudflare/cloudflared # macOS
cloudflared login # 会打开浏览器进行授权
3. 创建一个命名隧道:
cloudflared tunnel create giftgenius-dev
4. 在 ~/.cloudflared/config.yml 中配置路由:
tunnel: giftgenius-dev
credentials-file: /Users/you/.cloudflared/giftgenius-dev.json
ingress:
- hostname: giftgenius-dev.yourdomain.com
service: http://localhost:3000 # 我们的 Next.js 开发服务器
- service: http_status:404
5. 启动隧道:
cloudflared tunnel run giftgenius-dev
现在,只要 npm run dev 和 cloudflared tunnel run 在运行,你的本地 Next.js 就能通过稳定的 URL https://giftgenius-dev.yourdomain.com 访问。你应当在 ChatGPT 的 Dev Mode 设置中填写的就是这个地址。
这与我们的应用如何契合
如果你在浏览器里打开你在 ChatGPT 里为 Dev 应用填写的 URL:
https://giftgenius-dev.yourdomain.com/mcp
你会看到一个响应(错误)——类似这样:
{"jsonrpc":"2.0","error":{"code":-32000,"message":"Method not allowed."},"id":null}
这完全正常,因为 /mcp 端点并不接受 GET 请求。应用的其他部分——小部件、MCP 端点 /mcp、API 路由——也都走同一个隧道,你不需要每次记住一个新域名。
4. 备选方案:ngrok 的静态子域
如果你已经习惯 ngrok,也可以用相同思路把它“升级”为 static domain。从 2023 年开始,ngrok 即使在免费方案也允许绑定一个静态子域,例如 myapp-dev.ngrok-free.app。
最简配置:
# ~/.config/ngrok/ngrok.yml
authtoken: <你的令牌>
tunnels:
giftgenius-dev:
addr: 3000
proto: http
domain: giftgenius-dev.ngrok-free.app
启动:
ngrok start giftgenius-dev
最终 https://giftgenius-dev.ngrok-free.app 会是一个固定不变的 URL,你把它作为应用的基础 URL 提供给 ChatGPT Dev Mode 即可。
理念相同:
- 不再有“随机”地址;
- 只更改隧道的内部状态(运行/未运行),域名不变;
- 无需重连 Dev Mode。
Cloudflare 与 ngrok 在这方面更像不同口味的冰淇淋。有些人喜欢自有域名与对 DNS 的“精细”掌控(Cloudflare),另一些人偏爱“写个 YAML 就能跑”(ngrok)。对本课程而言两种方式都可行,关键是——稳定的 URL。
5. 示意图:ChatGPT Dev Mode ↔ 隧道 ↔ 本地栈
为了稍微形式化一下流程,我们画一张图。
flowchart TD
ChatGPT["ChatGPT (Dev Mode)"]
AppCfg["Dev App (配置: https://giftgenius-dev...)"]
Tunnel["Cloudflare/ngrok 隧道 (giftgenius-dev...)"]
Next["Next.js 开发服务器 localhost:3000 + MCP 处理器"]
ChatGPT --> AppCfg
AppCfg -->|"配置中指定 https://giftgenius-dev.../.well-known/openai-app"| Tunnel
Tunnel -->|"HTTPS → HTTP 代理"| Next
ChatGPT 从来不知道你笔记本上到底跑了什么。对它而言只有一个 HTTPS 端点。这个端点背后到底是 Vercel、本地隧道还是 Kubernetes,则是你的事。而在本讲中,我们关注的是该 HTTPS 端点在本地开发中的稳定性。
接下来要做的是:让我们的应用内部也把这个地址作为统一的“真相来源”,而不是在硬编码字符串里七零八落——下一节就讲这个。
6. 环境变量与代码里的 baseURL
为了让一切稳定可控,建议在 Next.js 里定义一次“应用对外的基础 URL”,后续所有地方都依赖它。
例如,在我们的 GiftGenius 应用中,在 app/lib/config.ts 里这样定义:
// app/lib/config.ts
export const baseUrl =
process.env.NEXT_PUBLIC_APP_URL ?? "http://localhost:3000"; // 回退
export const mcpEndpoint = `${baseUrl}/mcp`; // MCP 服务器的 URL
而在开发时的 .env.local 里填写:
NEXT_PUBLIC_APP_URL=https://giftgenius-dev.yourdomain.com
这样:
- 在小部件与任何链接中,你总是使用 baseUrl;
- 对 ChatGPT Dev Mode 与浏览器而言,行为一致;
- 如果明天你迁到 Vercel 的 staging,域名变为 https://giftgenius-staging.vercel.app, 只需修改环境变量即可。
这对以下场景尤其重要:
- 回调 URL(例如 OAuth、webhook 处理器);
- 你在小部件中给用户展示的链接(通过 openExternal 的“在浏览器中打开”按钮);
- 应用逻辑中的任何绝对 URL。
此处我们只谈 dev‑URL,但“baseUrl 的单一事实来源”这一架构思想同样适用于 staging/production。
7. 在 ChatGPT Dev Mode 中更新 URL
好,我们已经拥有一个漂亮且稳定的域名。那在 Dev Mode 里如何使用它?
逻辑如下:
- 在 Dev 应用设置里,你只需填写一次根 URL:https://giftgenius-dev.yourdomain.com/
- ChatGPT 会据此拉取清单(.well-known/openai-app), 后续也基于同样的根路径访问 MCP(/mcp)、静态资源等。
- 若你只改动代码(React 小部件、MCP 处理、样式),完全不需要修改 URL。 只要隧道在运行、Next.js 服务器有响应即可。
- 若你更改了域名本身(少见,比如从 ngrok 换到 Cloudflare),则需要进入 Dev Mode 修改一次 endpoint。
某些情况下 ChatGPT 会缓存清单,清单中的变更可能不会立刻生效。Dev Mode 界面通常有“Reload configuration / Refresh App”之类按钮;最糟情况下,断开再用同一个 URL 重新连接也能解决。
重点:只要你不改 URL,Dev Mode 就会自动“拾取”新代码版本。对 App 而言最重要的触发器是域名,而不是 commit‑hash。
8. 在 Dev Mode 中切换 dev / staging / prod
稳定的 dev 域名只是第一步。为了避免随着项目增长在一堆 URL 中迷失,最好及早想清楚 dev 隧道如何融入你的整体环境(dev/staging/prod)与 Dev Mode。尽管 staging 和 prod 更像下一讲关于 Vercel 的主题,但 Dev Mode 现在就能配合多个环境。
下面这张表有助于理解:
| 环境 | 基础 URL | 代码运行位置 |
|---|---|---|
| Local | |
本地 Next.js + 通过隧道的 MCP |
| Staging | |
Vercel 预览 / staging 部署 |
| Prod | |
Vercel 生产环境 |
和 Dev Mode 配合有两种方式。
第一种——只用一个 Dev 应用,但你会不时在它的设置里切换 URL 来测试 staging 或 prod(需要非常小心)。这种方式早期勉强可用,但很容易混淆:今天你测的是本地,明天是 staging,后天忘了切回来,结果通过 Dev 应用把请求发到了生产。
第二种——更健康:多个 Dev 应用,每个都明确绑定到一个环境:
- GiftGenius Dev → giftgenius-dev.yourdomain.com;
- GiftGenius Staging → giftgenius-staging.vercel.app;
- GiftGenius(正式版,通过 Store)→ giftgenius.vercel.app。
本讲我们按部就班,先把 dev‑URL 整理好。下一讲你将看到如何把 Vercel 与预览部署自然地纳入 staging/production。
9. 团队协作:多开发者与一个隧道
当只有一个 dev 域名和一个独行开发者时,隧道是你的私人好伙伴。但一旦团队加入项目,隧道与环境就会产生交叉,此时务必要避免“抢一个子域”的混战。
设想两个开发者决定共用一个静态子域,比如 giftgenius-dev.ngrok-free.app。两人都在本地运行 ngrok start giftgenius-dev。最好的结果是其中一个隧道直接起不来(域名冲突);最坏的情况是你们轮流“顶掉”彼此的会话,ChatGPT 时而连到甲、时而连到乙。
这里有几种策略。
最简单的是个人 dev 域名:
- alex.dev.giftgenius.app;
- maria.dev.giftgenius.app。
并在 ChatGPT 中为每人建一个 Dev 应用,例如 GiftGenius Dev (Alex) 和 GiftGenius Dev (Maria)。这样大家各自开发互不干扰。
更“团队化”的路线——共享的 staging 端点:
- 每位开发者都有个人 dev 隧道(用于本地调试);
- 再配一个 Vercel 上的 staging,合并 feature 分支后部署,全体通过 GiftGenius Staging 这个 Dev 应用来验证。
现实团队里常见的流程:
- 功能在本地诞生,并通过个人隧道调试;
- pull request 合并后,大家通过 staging 一起测试(无需隧道,直接访问 Vercel URL)。
10. dev 隧道的安全性(简短且不偏执)
隧道是把你的本地服务器暴露到互联网的便捷方法。而我们都知道,互联网主要由机器人、扫描器和喜欢检查你是否忘了 admin/admin 密码的人构成。
在开发阶段就该记住的基础事项:
- 隧道会为该端口上所有服务提供外部访问;别顺手把数据库控制台、phpMyAdmin 或“我没设密码的测试 CRM”也挂上去;
- 不要把隧道 URL 公开贴在开源仓库或群聊里;
- 不工作时关闭隧道(笔记本也偶尔关一关——它也需要休息)。
更严肃的措施,如 Basic Auth、校验特定请求头或在 URL 中使用 token,我们会在安全模块里讲。现在只需记住一点:隧道是开发工具,不是加固过的服务器。生产流量会跑在像 Vercel 这样的正规托管上,我们将在下一讲深入。
11. 实操:为我们的应用配置稳定的 dev‑URL
现在从理论与注意事项回到实践:把上述内容落地到我们的 Next.js(Apps SDK 模板)示例应用上。
假设项目结构如下:
apps/
web/ # Next.js 应用 + 小部件
mcp-server/ # (可选)独立 MCP,或在 web 中提供 /mcp 处理器
实际中你可以把 MCP 直接放在 Next.js 中,比如 app/api/mcp/route.ts,原则是一样的。
步骤 1:修改 .env.local
把稳定的 dev 隧道 URL 填进去:
NEXT_PUBLIC_APP_URL=https://giftgenius-dev.yourdomain.com
在开发代码中我们已经通过 baseUrl 使用了该变量(见上文)。如果还没有用——现在就该抽取出来。
步骤 2:启动开发服务器与隧道
cd apps/web
npm run dev # Next.js 在 localhost:3000
# 另一个终端
cloudflared tunnel run giftgenius-dev
在浏览器中检查 https://giftgenius-dev.yourdomain.com 是否能打开并显示你的 App。
步骤 3:在 ChatGPT Dev Mode 中连接
在 ChatGPT 的开发者界面中:
- 创建或编辑 GiftGenius Dev;
- 在 URL/Endpoint 字段填写 https://giftgenius-dev.yourdomain.com/;
- 保存。
之后 ChatGPT 会访问 /.well-known/openai-app 获取清单,然后基于该域名启动你的 App。
此后你可以:
- 修改小部件代码、MCP 处理逻辑、样式;
- 重启 npm run dev;
- 重启 cloudflared tunnel run giftgenius-dev;
同时,只要域名不变,你再也不用去改 Dev 应用设置。
12. 代码中的呈现:以 openExternal 为例
为了与前面写过的内容呼应,我们在小部件中加一个“在浏览器中打开完整界面”的按钮,它同样使用稳定的 dev‑URL。
假设我们有一个名为 GiftWidget 的 React 小部件组件:
// app/components/GiftWidget.tsx
"use client";
import { baseUrl } from "../lib/config"; // 从环境变量获取 baseUrl
export function GiftWidget() {
const handleOpenFull = () => {
window.openai.openExternal({
// 在新标签页中打开应用页面
url: `${baseUrl}/full`,
label: "打开完整界面",
});
};
return (
<div>
<button onClick={handleOpenFull}>
完整模式
</button>
</div>
);
}
如果 NEXT_PUBLIC_APP_URL 指向隧道,那么:
- 在本地开发时会打开 https://giftgenius-dev.yourdomain.com/full;
- 部署到 staging 后会是 https://giftgenius-staging.vercel.app/full;
- 在 prod 则是正式域名。
再次强调——域名的单一事实来源:切换环境,不必改代码。
13. 迷你策略:如何“成熟地”看待隧道
将一切收拢为简单心智模型,可以认为:
- 隧道只是连接你的笔记本与稳定公共域名之间的一根临时导线;
- ChatGPT Dev Mode 只知道域名,不关心代码物理上在哪里运行;
- 域名改得越少,你在 ChatGPT 与各类 OAuth 提供方设置里浪费的时间就越少;
- dev 隧道只是你整体环境地图中的一条记录,旁边还有 staging(Vercel 预览)与 prod(Vercel 生产)。
下一讲将展示如何用 Vercel 的正式托管替换这根导线,并把它与 Git 分支、预览部署和生产环境串联起来。
14. 使用“成熟级”隧道的常见错误
错误 №1:“我已经配置了静态域名,但还是继续用随机 URL”。
有时开发者明明已经配好了漂亮的 giftgenius-dev.yourdomain.com,却习惯性地直接跑 ngrok http 3000。结果是 ChatGPT 看着一个域名,代码却跑在另一个后面。如果你已经有了稳定的 dev‑URL——只用它,并通过配置(命名隧道/配置文件)来启动隧道。
错误 №2:在代码里硬编码 localhost:3000。
常见于在 React 组件或 MCP 处理器中写 fetch("http://localhost:3000/api/...")。本地似乎还能跑,但在 Dev Mode,尤其是 staging/prod 上立刻崩。务必把基础 URL 抽到配置中(baseUrl、NEXT_PUBLIC_APP_URL),凡是需要绝对链接的地方都使用它。
错误 №3:总是在 Dev Mode 中改 URL,而不是配置稳定隧道。
如果你发现自己在想“算了,我再改一次设置里的 URL 吧”,——这就是警钟。给 ngrok/Cloudflare 设一个静态子域只需 10–15 分钟,但能在开发过程中帮你省下成倍的时间。
错误 №4:没有规则地共用一个团队静态域名。
两个开发者、一个域名 giftgenius-dev.ngrok-free.app,两人想什么时候开隧道就什么时候开。结果是隧道冲突、Dev Mode 的响应“神秘”消失,调试变成“我这儿明明好好的”。团队要么每人一个 dev 域名,要么使用托管在真实主机上的统一 staging 域名。
错误 №5:把隧道当作“准生产”。
有人会想:“既然我通过隧道有了稳定的 HTTPS‑URL,不如让真实用户/支付也走它吧。”这会带来痛苦:笔记本关机——应用就没了;网络掉线——一样;安全性也最多是象征性的。隧道是 dev 工具。处理生产流量应使用 Vercel 等成熟基础设施,我们将在下一讲到达那里。
错误 №6:环境变量与 Dev Mode 设置不同步。
经常有人在 .env.local 里改了 NEXT_PUBLIC_APP_URL,却忘了在 Dev Mode 中更新 URL(或反过来)。结果小部件生成的链接指向一个域名,而 ChatGPT 请求的是另一个。维护一张简单的“环境 ↔ 域名 ↔ ChatGPT 中的 App”对照表,变更时同步更新——比猜现在哪个 URL 才是真的要省心得多。
GO TO FULL VERSION