1. Проблема «рандомных» туннелей
Когда вы первый раз запускаете ngrok http 3000 или быстрый Cloudflare Quick Tunnel, это ощущается как магия: бац — и ваш http://localhost:3000 превратился в https://random-1234.tunnelprovider.com. Можно скопировать URL в ChatGPT Dev Mode, и GPT радостно грузит ваш App.
А потом вы перезапускаете туннель… и получаете новый домен. Старый URL в настройках Dev‑приложения в ChatGPT внезапно превращается в «битую ссылку», GPT честно пишет «App unavailable», а вы снова идёте в настройки, меняете URL, жмёте Save, ждёте, пока оно там обновится, и молча ненавидите весь этот стек.
Для одноразового «поиграться вечером» это терпимо. Но когда вы:
- каждый день допиливаете App;
- хотите показать промежуточную версию коллеге/менеджеру;
- параллельно заводите ещё staging и production,
переподключение Dev Mode под каждый новый случайный URL становится чистым страданием.
Плюс, если вы уже добавили в приложение что‑то, зависящее от домена (например, OAuth redirect URI или вебхуки), каждый новый URL ломает и их тоже. Появляется каскад: поменял туннель — и приходится править конфигурацию App, redirect‑URL в OAuth‑провайдере и настройки webhook‑приёмника.
Из этого вырастает ключевая идея лекции: стабильный dev‑URL — это не роскошь, а средство сохранения психического здоровья разработчика.
Insight
У ChatGPT есть очень жёсткие таймауты на работу с вашим приложением, и их легко недооценить. MCP-tool-call имеет лимит по времени: максимум 2 минуты — после этого платформа просто считает вызов неудачным, даже если ваш сервер всё ещё что-то делает.
Ещё строже обстоят дела с регистрацией приложения (Store или Dev Mode): на чтение манифеста, ресурсов и описаний инструментов ChatGPT даёт около 20 секунд. Если за это время ваш MCP-сервер не успел инициализироваться, отдать список tools/resources и т.п, регистрация App отвалится по timeout.
Рекомендация: вся тяжёлая инициализация должна происходить до того, как вы идёте в 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 — бесплатный static domain на аккаунт;
- в Cloudflare Tunnel — через именованный туннель и привязку к своему домену.
А ChatGPT‑приложение в Dev Mode настроено ровно на этот один URL и больше о вас не беспокоит.
С формальной точки зрения требования к нашему «взрослому» туннелю такие:
- стабильно один и тот же публичный HTTPS‑домен;
- валидный TLS‑сертификат (провайдер делает это за нас);
- конфиг, который описывает: «всё, что пришло на https://dev.yourdomain.com, прокинь на http://localhost:3000»;
- опционально — минимальные меры безопасности (хотя бы не светить URL на StackOverflow).
3. Настраиваем стабильный dev‑URL: пример с Cloudflare Tunnel
В курсе мы рекомендуем Cloudflare Tunnel как основной инструмент, потому что он хорошо ложится и на dev, и на более серьёзные сценарии. Уже в модуле 2 вы видели пример базовой настройки, теперь «доворачиваем» это до постоянного dev‑URL.
Предположим, у нас учебное приложение GiftGenius, и мы хотим стабильный URL giftgenius-dev.yourdomain.com.
Минимальные шаги (упрощённо, без привязки к UI Cloudflare):
- Привязываем домен к аккаунту 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 dev-сервер
- 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.
Как это вяжется с нашим приложением
Если вы откроете в браузере URL вашего приложения, который вы вводите в ChatGPT при подключении Dev‑приложения:
https://giftgenius-dev.yourdomain.com/mcp
вы увидите ответ (ошибку) — что‑то типа такого:
{"jsonrpc":"2.0","error":{"code":-32000,"message":"Method not allowed."},"id":null}
Это абсолютно нормально, ведь сервер по /mcp не ждёт GET-запрос. Все остальные части приложения — виджет, MCP endpoint /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
В результате URL https://giftgenius-dev.ngrok-free.app будет постоянным, и его же вы даёте ChatGPT Dev Mode как базовый URL приложения.
Философия та же:
- никаких «рандомных» адресов;
- изменился только внутренний статус туннеля (запущен/не запущен), а не домен;
- Dev Mode не нужно переподключать.
Cloudflare и ngrok в этом смысле просто разные вкусы мороженого. Некоторым нравятся свои домены и «тонкий» контроль DNS (Cloudflare), другие предпочитают «завёл YAML — готово» (ngrok). Для курса оба подхода валидны, главное — стабильный URL.
5. Схема: ChatGPT Dev Mode ↔ туннель ↔ локальный стек
Чтобы чуть формализовать происходящее, нарисуем картинку.
flowchart TD
ChatGPT["ChatGPT (Dev Mode)"]
AppCfg["Dev App (конфиг: httрs://giftgenius-dev...)"]
Tunnel["Туннель Cloudflare/ngrok (giftgenius-dev...)"]
Next["Next.js dev-сервер localhost:3000 + MCP handler"]
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 приложения» и дальше опираться только на него.
Например, в папке app/lib/config.ts в нашем приложении GiftGenius можно завести:
// app/lib/config.ts
export const baseUrl =
process.env.NEXT_PUBLIC_APP_URL ?? "http://localhost:3000"; // fallback
export const mcpEndpoint = `${baseUrl}/mcp`; // URL MCP-сервера
А в .env.local при разработке указать:
NEXT_PUBLIC_APP_URL=https://giftgenius-dev.yourdomain.com
Тогда:
- внутри виджета и любых ссылок вы всегда используете baseUrl;
- для ChatGPT Dev Mode и браузера всё выглядит консистентно;
- если завтра вы переедете на Vercel staging с доменом https://giftgenius-staging.vercel.app, достаточно поменять только переменную окружения.
Это особенно важно для:
- callback‑URL (например, для OAuth, webhook‑обработчиков);
- ссылок, которые вы показываете пользователю в виджете (кнопка «Открыть в браузере» через openExternal);
- любых абсолютных URL в логике приложения.
Пока мы говорим только про dev‑URL, но сама архитектурная идея «один источник правды для baseUrl» отлично работает и спокойно переедет с вами и на staging/production.
7. Обновление URL в ChatGPT Dev Mode
Ну окей, мы сделали себе красивый стабильный домен. Как теперь с ним жить в Dev Mode?
Логика такая:
- В настройках Dev‑приложения вы один раз указываете корневой URL: https://giftgenius-dev.yourdomain.com/
- ChatGPT ходит по нему за манифестом (.well-known/openai-app) и дальше использует те же корни для обращения к MCP (/mcp), статике и т.д.
- Если вы меняете только код (React‑виджет, MCP‑handlers, стили), URL менять не нужно вообще. Достаточно, чтобы туннель был запущен и Next.js‑сервер отвечал.
- Если вы меняете сам домен (редко, например с ngrok на Cloudflare), нужно один раз зайти в Dev Mode и поменять endpoint.
В некоторых случаях ChatGPT кэширует манифест, и изменения в нём могут появляться не мгновенно. В интерфейсе Dev Mode обычно есть кнопка вроде «Reload configuration / Refresh App», но в худшем сценарии поможет даже тривиальное «отключить и заново подключить App по тому же URL».
Важно: пока вы не меняете URL, Dev Mode автоматически «подхватывает» новые версии кода. Главный триггер для App — домен, а не commit‑hash.
8. Переключение между dev / staging / prod в Dev Mode
Стабильный dev‑домен — это только первая ступень. Чтобы не утонуть в хаосе URL‑адресов по мере роста проекта, полезно сразу понимать, как dev‑туннель вписывается в общую схему окружений (dev/staging/prod) и Dev Mode. Хотя staging и prod — тема скорее следующей лекции про Vercel, Dev Mode уже сейчас умеет работать с несколькими окружениями.
Для понимания рассуждений дальше полезна такая таблица:
| Окружение | Базовый URL | Где крутится код |
|---|---|---|
| Local | |
Локальный Next.js + MCP через туннель |
| Staging | |
Vercel Preview / staging‑деплой |
| Prod | |
Vercel Production |
Варианта работы с Dev Mode два.
Первый — один Dev‑App, но вы время от времени обновляете в его настройках URL, чтобы потыкать staging или prod (аккуратно). Такой подход годится на ранних стадиях, но его легко перепутать: сегодня вы тестировали локалку, завтра staging, послезавтра забыли переключить и случайно гоняете через Dev‑App запросы в прод.
Второй — более здоровый: несколько Dev‑приложений, каждое с чёткой привязкой к окружению:
- GiftGenius Dev → giftgenius-dev.yourdomain.com;
- GiftGenius Staging → giftgenius-staging.vercel.app;
- GiftGenius (боевой, через Store) → giftgenius.vercel.app.
В рамках этой лекции мы делаем шаг за шагом, наводя порядок хотя бы в dev‑URL. В следующей лекции вы увидите, как логично привязать Vercel и preview‑деплои к staging/production.
9. Работа в команде: несколько разработчиков и один туннель
Пока у нас один dev‑домен и один разработчик‑интроверт, туннель — ваш личный друг. Но как только в проект приходит команда, туннели и окружения начинают пересекаться, и здесь важно не устроить «войну за один поддомен».
Представим, что два разработчика решили использовать один и тот же статический поддомен, скажем giftgenius-dev.ngrok-free.app. Оба запускают у себя ngrok start giftgenius-dev. В лучшем случае один туннель просто не поднимется (конфликт домена), в худшем — вы будете по очереди «перебивать» друг другу сессию, и ChatGPT то попадает к одному, то к другому.
Здесь есть несколько стратегий.
Самая простая — персональные dev‑домены:
- alex.dev.giftgenius.app;
- maria.dev.giftgenius.app.
И каждому свой Dev‑App в ChatGPT, например GiftGenius Dev (Alex) и GiftGenius Dev (Maria). Тогда каждый спокойно крутит локалку и не мешает соседу.
Более «командный» путь — общий staging‑endpoint:
- У всех разработчиков есть личный dev‑туннель (для личной отладки).
- Плюс есть staging на Vercel, куда мержатся feature‑ветки и куда смотрит общий Dev‑App GiftGenius Staging.
Такой подход часто встречается в реальных командах:
- фича рождается на локалке и отлаживается через личный туннель;
- после pull‑request и мёрджа она тестируется всеми через staging (без туннелей, просто через Vercel URL).
10. Безопасность dev‑туннеля (коротко и без паранойи)
Туннель — это удобный способ вытащить ваш локальный сервер в интернет. А интернет, как мы знаем, в основном состоит из ботов, сканеров и людей, которые любят проверять, не забыли ли вы пароль admin/admin.
Базовые вещи, о которых стоит помнить уже на этапе dev:
- туннель даёт внешний доступ ко всему, что висит на этом порту; не поднимайте туда ещё и админку БД, phpMyAdmin и «мою тестовую CRM без пароля»;
- не публикуйте URL туннеля в открытых репозиториях и чатах;
- выключайте туннель, когда не работаете (и ноутбук тоже иногда выключайте — ему тоже нужен отдых).
Более серьёзные меры вроде Basic Auth, проверки спец‑заголовков или токена в URL мы будем разбирать уже в модулях про безопасность. Важно сейчас лишь одно: туннель — инструмент разработки, а не защищенный сервер. Продакшен будет жить на нормальном хостинге, вроде Vercel, с другими механизмами защиты.
11. Практика: настраиваем стабильный dev‑URL для нашего приложения
Теперь от теории и предостережений — к практике: давайте привяжем всё это к нашему учебному приложению на Next.js (шаблон Apps SDK).
Предположим, структура проекта выглядит так:
apps/
web/ # Next.js App + виджет
mcp-server/ # (опционально) отдельный MCP, или /mcp хэндлер в web
На практике вы можете держать MCP прямо в Next.js, в app/api/mcp/route.ts, но принцип тот же.
Шаг 1. Правим .env.local
Добавим туда стабильный dev‑URL туннеля:
NEXT_PUBLIC_APP_URL=https://giftgenius-dev.yourdomain.com
В дев‑коде мы уже использовали baseUrl из этой переменной (см. выше). Если не использовали — самое время выделить.
Шаг 2. Запускаем dev‑сервер и туннель
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‑handlers, стили;
- перезапускать npm run dev;
- перезапускать cloudflared tunnel run giftgenius-dev;
и при этом никогда больше не лазить в настройки Dev‑App, пока домен остаётся тем же.
12. Как это выглядит в логике кода: пример с openExternal
Чтобы замкнуть пример на то, что мы уже писали в предыдущих лекциях, добавим в виджет кнопку «Открыть полный интерфейс в браузере», которая тоже будет использовать стабильный dev‑URL.
Предположим, у нас есть React‑компонент виджета GiftWidget:
// app/components/GiftWidget.tsx
"use client";
import { baseUrl } from "../lib/config"; // берём baseUrl из env
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 preview) и prod (Vercel production).
Следующая лекция как раз покажет, как этот провод заменяется полноценным хостингом на Vercel, и как связать всё это с Git‑ветками, preview‑деплоями и продакшеном.
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: Постоянная правка URL в Dev Mode вместо стабильного туннеля.
Если вы ловите себя на мысли «ну ладно, ещё раз поменяю URL в настройках, чего уж там», — это звоночек. Настройка статического поддомена в ngrok/Cloudflare занимает один раз 10–15 минут, зато экономит часы по ходу разработки.
Ошибка №4: Общий статический домен на команду без правил.
Два разработчика, один домен giftgenius-dev.ngrok-free.app, и оба запускают туннель, когда захотят. Результат — конфликт туннелей, «мистически» пропадающие ответы в Dev Mode и отладка в стиле «у меня же работало». Для команды всегда либо личные dev‑домены, либо один staging‑домен на реальном хостинге.
Ошибка №5: Туннель как «почти продакшен».
Иногда кто‑то додумывается: «Ну если у меня стабильный HTTPS‑URL через туннель, давайте пускать через него настоящих пользователей/платежи». Это путь к боли: ноутбук выключили — приложение умерло, интернет отвалился — то же самое, а безопасность в лучшем случае символическая. Туннель — это dev‑инструмент. Для боевого трафика предназначены Vercel и прочая взрослая инфраструктура, до которой мы доберёмся в следующей лекции.
Ошибка №6: Забытая синхронизация переменных окружения и Dev Mode.
Часто меняют NEXT_PUBLIC_APP_URL в .env.local, но забывают изменить URL в Dev Mode (или наоборот). В итоге виджет генерирует ссылки на один домен, а ChatGPT обращается к другому. Держите простую табличку «окружение ↔ домен ↔ App в ChatGPT» и обновляйте её при изменениях — это дешевле, чем гадать, какой URL сейчас настоящий.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ