1. Зачем отдельная лекция про локальный debug
В прошлых модулях мы уже разобрали, как устроены стек Apps SDK и MCP. Теперь поговорим, зачем вообще нужна отдельная лекция про локальный debug.
У многих путь такой: «Ну, я просто открою ChatGPT, напишу "используй мой App", а дальше буду смотреть, что он скажет. Если не работает — перепишу код наугад». Это примерно как чинить backend, глядя только на HTML‑страницу в браузере и ни разу не открыв логи сервера.
С ChatGPT Apps особенно легко скатиться в магию: есть GPT, он сам решает, вызывать tool или нет, у него есть своя логика ошибок. Если вы не видите, что происходит под капотом, debug превращается в шаманство с бубном.
Наша цель: превратить это в нормальный инженерный процесс:
- вы знаете, где смотреть логи Next/MCP;
- вы умеете руками дергать MCP‑сервер через инспектор;
- вы понимаете, что именно проверяет Dev Mode и как убедиться, что ChatGPT вообще может достучаться до вашего сервера.
И самое важное: вы перестаёте отлаживать через «угадайку GPT» и начинаете сначала проверять низкие уровни стека — сервер и протокол, а уже потом UI и поведение модели.
2. Ментальная модель: три уровня debug
Чтобы не тонуть в хаосе, договоримся думать о debug в терминах трёх уровней. Это наш маленький «слоёный пирог»:
| Уровень | Что там живёт | Типичные симптомы | Чем отлаживаем |
|---|---|---|---|
| UI (виджет) | React‑компоненты, стейт, window.openai | Пустой/серый виджет, кривой рендер, кнопки не работают | DevTools браузера |
| Backend / MCP‑сервер | инструменты, доступ к БД/API | 500‑ки, «tool упал», странные данные | логи сервера, MCP Inspector |
| Протокол MCP | JSON‑RPC, tools/list, tools/call, схемы | GPT пишет «не смог вызвать инструмент», invalid params | инспектор + логи запросов |
Во втором уровне нас интересует, что делает сам MCP‑сервер (инструменты, БД, API), а в третьем — уже «провода» и формат MCP‑сообщений (JSON‑RPC, схемы и т.п.).
Эта тройка — основа плана лекции и курса по debug.
Для наглядности можно посмотреть на поток запроса:
sequenceDiagram
participant User as Пользователь
participant ChatGPT as ChatGPT (Dev Mode)
participant Tunnel as Туннель (ngrok/CF)
participant Next as Next.js + MCP
User->>ChatGPT: "Подбери подарок до $50"
ChatGPT->>Next: tools/call search_gifts (через Tunnel)
Next->>Next: Вызываем MCP tool, лезем в БД/API
Next-->>ChatGPT: JSON-RPC result + ToolOutput
ChatGPT-->>User: Ответ + рендер виджета
Сломаться может в любой точке: туннель, endpoint, MCP‑логика, JSON‑схема, React‑виджет. Ваша задача при debug — понять, в каком именно слое ошибка, а не сразу переписывать всё подряд.
3. Логи Next.js и MCP: основа всего
Начнём с самого скучного и самого полезного — с логов.
Где живут логи при локальной разработке
В стандартном шаблоне Apps SDK на Next.js MCP‑сервер, как правило, обёрнут в API‑роут (/api/mcp или похожий). Вы запускаете npm run dev, и в одном терминале у вас:
- дев‑сервер Next.js;
- обработчик для MCP endpoint, который принимает JSON‑RPC запросы tools/list, tools/call и т.п.;
- печать всего веселья console.log/console.error.
Если вы выделяли MCP в отдельный процесс, там будет второй терминал, но идея та же: всё интересное видно в консоли.
Важно различать:
- ошибки сборки/старта — не поднимается next dev, падает TypeScript, неправильный импорт и т.п.;
- ошибки выполнения — всё запустилось, но конкретный запрос на /api/mcp приводит к падению инструмента.
Next.js в dev‑режиме показывает runtime‑ошибки ещё и красивым оверлеем, плюс пишет stack trace в консоль.
Что логировать в MCP‑сервере
Хотя MCP использует протокол JSON‑RPC, для debug вам не нужно печатать абсолютно весь JSON. Гораздо полезнее структурированные, но короткие логи.
Хорошая практика для MCP‑логов — логировать как минимум: timestamp, request_id/traceId, имя инструмента, параметры (обезличенные), статус (ok/error) и время выполнения.
Простейший logger.ts для GiftGenius может выглядеть так:
// src/lib/logger.ts
export function logToolEvent(
phase: "start" | "end" | "error",
data: Record<string, unknown>
) {
const ts = new Date().toISOString();
console.log(JSON.stringify({ ts, phase, ...data }));
}
А в обработчике инструмента:
// src/mcp/tools/searchGifts.ts
import { logToolEvent } from "@/lib/logger";
export async function searchGiftsTool(args: { q: string }) {
const traceId = crypto.randomUUID();
logToolEvent("start", { tool: "search_gifts", traceId, args });
try {
// ... реальный поиск подарков ...
const results = []; // заглушка
logToolEvent("end", { tool: "search_gifts", traceId, count: results.length });
return results;
} catch (err) {
logToolEvent("error", { tool: "search_gifts", traceId, error: String(err) });
throw err;
}
}
Есть две важные тонкости.
Во‑первых, не нужно в логах хранить полные e‑mail, телефоны, номера карт, токены. Это не только некрасиво, но и конфликтует с базовыми практиками безопасности MCP.
Во‑вторых, traceId — ваш лучший друг. Когда вы смотрите логи Next.js и MCP вместе, по нему легко связать события: конкретный запрос tools/call, соответствующий React‑рендер и сетевой лог виджета.
Как понять по логам, где упало
У вас есть терминал, в нём летят JSON‑строчки от logToolEvent. Типовой сценарий:
- пришёл phase: "start" с tool: "search_gifts";
- нет phase: "end", зато есть phase: "error" и stack trace;
- из этого видно, что до вашей логики инструмент дошёл, но что‑то сломал внутри — например, запрос к внешнему API, парсинг, работа с БД.
Если же вы вообще не видите логов для этого tool‑имени — значит, запрос даже не дошёл до инструмента. Тогда вы идёте выше по стеку: туннель, endpoint /mcp, JSON‑запрос tools/call.
4. MCP Inspector: отладка MCP до ChatGPT
Если логи — ваши глаза, то MCP Inspector (или MCPJam Inspector) — это микроскоп.
Подробнее про MCP Inspector и зачем он нужен
В модуле про MCP мы уже подключали Inspector, чтобы проверить «Hello, MCP»‑сервер. Здесь используем его как основной инструмент отладки: сначала убеждаемся, что MCP жив сам по себе, и только потом лезем в Dev Mode и UI.
Inspector — это отдельное приложение (чаще всего веб‑UI плюс CLI), которое играет роль клиента MCP. Он подключается к вашему серверу по HTTP/SSE или stdin/stdout, выполняет tools/list, tools/call и показывает сырые JSON‑сообщения, handshake, список инструментов, ресурсы и т.п.
Главная идея: убрать ChatGPT из уравнения. Если у вас инструмент не работает, вы сначала хотите понять, жив ли сервер, корректен ли протокол и схема, прежде чем обвинять GPT.
Мини‑поток работы с Inspector
Типовой сценарий локальной отладки выглядит так:
- Запускаете npm run dev, чтобы Next.js + MCP‑endpoint поднялись.
- Запускаете MCP Inspector, например:
npx @modelcontextprotocol/inspector
(конкретная команда зависит от используемого инструмента).
- В Inspector указываете URL вашего MCP‑endpoint, например http://localhost:3000/api/mcp (или HTTPS‑туннель, если хотите проверить его тоже).
- Смотрите, прошёл ли handshake: сервер должен ответить поддерживаемыми capabilities, списком tools, ресурсов и т.д.
- Вызываете интересующий инструмент руками: выбираете search_gifts, вводите аргументы {"q": "для девушки до 30"}, жмёте «Call tool» и смотрите:
- пришёл ли ответ;
- не вернулась ли ошибка JSON‑RPC или MCP;
- что сервер пишет в логах на этот вызов.
Если в Inspector уже всё падает, не нужно даже открывать ChatGPT: чините MCP‑сервер.
Если в Inspector всё отлично, а ChatGPT всё равно жалуется — значит, проблема выше: Dev Mode URL, авторизация, поведение модели.
Пример «умышленно сломали tool»
Возьмём наш search_gifts и сломаем его нарочно:
export async function searchGiftsTool(args: { q: string }) {
if (args.q === "сломайся") {
throw new Error("Учебная ошибка для демонстрации debug");
}
// ... нормальная логика ...
return [];
}
Дальше:
- В Inspector вызываете search_gifts с аргументом {"q": "сломайся"}.
- В логах видите phase: "error" и stack trace.
- Убеждаетесь, что MCP‑сервер честно возвращает ошибку.
Потом, когда вы подключите всё это к ChatGPT Dev Mode и попросите модель «подбери подарок со словом "сломайся"», она попытается вызвать инструмент и покажет уже пользователю сообщение вроде «I encountered an error running the tool». Видно: ошибка появляется не из‑за модели, а из‑за вашего явного исключения.
Такой приём хорошо тренирует мышление: вы чётко отделяете бизнес‑ошибку (мы сами бросили Error) от протокольной (сломали JSON, неправильное имя инструмента и т.п.).
5. Debug виджета: DevTools, стейт и «debug‑баннер»
Когда MCP‑сервер более‑менее понятен, переходим на фронтенд — виджет Apps SDK.
Где и как смотреть ошибки виджета
Ваш виджет рендерится внутри ChatGPT в iframe‑песочнице. Но хорошая новость: у этого iframe всё те же браузерные DevTools.
Мини‑процедура:
- Открываете ChatGPT в браузере (Chrome/Edge/Firefox).
- Открываете DevTools (обычно F12 или Ctrl+Shift+I).
- Вкладка Console — выбираете контекст фрейма, в котором живёт ваш виджет (часто это домен web-sandbox.oaiusercontent.com).
- Обновляете чат/отправляете сообщение, чтобы GPT показал ваш App.
Если виджет:
- вообще не появился;
- показался серым/пустым;
- показывает красную ошибку в консоли
— это почти точно проблема React‑кода: недоступное свойство, неправильный импорт, кривой хук и т.п.
Вкладка Network тоже полезна. Там вы увидите:
- загрузку JS‑бандла вашего приложения (если 404/500 — проблема на стороне dev‑сервера/туннеля);
- запросы, которые ваш виджет делает наружу через window.fetch, и ответы 4xx/5xx.
Простейший debug‑баннер
Очень удобный приём — добавить в корневой компонент виджета небольшой «debug‑баннер», который в Dev Mode показывает, что это за окружение и какая версия билда.
Например:
// src/components/DebugBanner.tsx
export function DebugBanner() {
if (process.env.NODE_ENV !== "development") return null;
return (
<div style={{ padding: 4, background: "#222", color: "#0f0", fontSize: 10 }}>
ENV: dev | build: local | {new Date().toLocaleTimeString()}
</div>
);
}
И в корневом компоненте виджета:
// src/app/widget/page.tsx
import { DebugBanner } from "@/components/DebugBanner";
export default function GiftGeniusWidget() {
return (
<div>
<DebugBanner />
{/* остальной UI поиска подарков */}
</div>
);
}
Если вы открыли ChatGPT, запустили App, а баннер не видите — значит, ваш JS вообще не доехал до браузера: либо ошибка сборки, либо проблема с endpoint, либо виджет банально не зарегистрирован в MCP‑сервере.
Локальный стейт и обработка ошибок
Ваш виджет уже умеет отображать разные состояния: загрузка, успех, ошибка. Если нет — самое время добавить.
Мини‑паттерн:
const [status, setStatus] = useState<"idle"|"loading"|"error"|"success">("idle");
async function handleSearch(query: string) {
try {
setStatus("loading");
// вызываем MCP tool через window.openai.callTool или хук Apps SDK
setStatus("success");
} catch (e) {
console.error("Search failed", e);
setStatus("error");
}
}
В JSX:
{status === "error" && (
<div style={{ color: "red" }}>Что-то пошло не так, попробуйте ещё раз.</div>
)}
Для debug критично, чтобы:
- вы не проглатывали исключения (иначе в консоли пусто, а UI просто «завис»);
- вы явно отражали ошибку в UI, иначе пользователю кажется, что App умер.
6. Dev Mode как часть debug: что он делает и как его не винить зря
Теперь включим в картину ChatGPT Dev Mode. До этого мы рассматривали чисто ваш код. Но иногда всё работает локально, в Inspector всё отлично, а ChatGPT всё равно отвечает «Error talking to [AppName]» или вообще не предлагает ваш App.
Что делает Dev Mode
Dev Mode — это режим ChatGPT, где вы можете:
- создавать и редактировать свои Apps;
- указывать endpoint MCP‑сервера (обычно https://ваш-домен/mcp или /api/mcp);
- быстро обновлять манифест и метаданные без публикации в Store.
С точки зрения debug, Dev Mode — это всего лишь ещё один слой конфигурации:
- если там указан неверный URL;
- если вы забыли /mcp на конце;
- если туннель выдал новый домен, а вы не обновили настройки
— ChatGPT банально не может достучаться до вашего сервера.
Типичный сценарий поломки Dev Mode
Классика жанра:
- Вы подняли туннель https://abcd.ngrok.io, указали его в Dev Mode, всё работало.
- На следующий день перезапустили ngrok, получили https://efgh.ngrok.io.
- В Dev Mode по‑прежнему https://abcd.ngrok.io/mcp.
- ChatGPT пишет «Error talking to GiftGenius».
MCP Inspector при этом, направленный на http://localhost:3000/api/mcp, показывает, что всё окей. Это значит, что MCP жив, но ChatGPT смотрит не туда.
Решение: зайти в настройки Dev Mode, обновить URL, не забыв /mcp в конце.
Dev Mode vs Store
В этой лекции мы говорим только о Dev Mode — это ваша песочница. Здесь нормально часто менять URL, переподключать туннель, править схему инструментов.
Когда вы потом пойдёте в Store, endpoint станет более жёстко зафиксирован, и такие фокусы будут уже нехорошей идеей. Но до Store нам ещё несколько модулей, так что пока спокойно ломаем и чиним в Dev Mode.
7. Мини‑алгоритм отладки: что делать, когда «ничего не работает»
Теперь соберём всё в практический алгоритм. По сути, это те же три уровня debug из начала лекции, только записанные как последовательность шагов.
Допустим, вы открыли ChatGPT, выбрали GiftGenius, попросили «Подбери подарок до 30$ для друга‑гика», и:
- GPT ничего не пишет про App;
- или пишет «Error talking to GiftGenius»;
- или открывается пустой/серый виджет.
Как не впасть в отчаяние?
Шаг 1 (уровень MCP/сервер). Проверить MCP через Inspector и логи
Сначала игнорируем GPT и UI. Нас интересует только сервер.
- Убедитесь, что npm run dev запущен, и endpoint (/api/mcp) отвечает.
- Подключите MCP Inspector к http://localhost:3000/api/mcp или к вашему туннелю.
- Проверьте handshake — список tools должен отображаться.
- Вызовите руками тот же инструмент, который, по идее, должен дергать GPT (например, search_gifts), с похожими аргументами.
Если уже тут всё падает — чините MCP: схемы, business‑логику, сетевые вызовы. Используйте логи и traceId, чтобы понять, что именно ломается.
Шаг 2 (уровень протокола/Dev Mode). Проверить Dev Mode и URL
Если в Inspector всё прекрасно, а ChatGPT по‑прежнему не видит ваше App или пишет про проблемы с соединением:
- Откройте настройки Dev Mode для вашего App.
- Посмотрите, какой там URL указан для MCP.
- Сверьте его с тем, что реально слушает ваш сервер/туннель (и не забудьте проверить, что в конце есть /mcp, если ваш сервер этого требует).
Часто проблема именно здесь.
Шаг 3 (уровень UI). Проверить виджет через DevTools
Если ChatGPT успешно дергает инструменты (видно по логам MCP), но сам виджет ведёт себя странно:
- Откройте DevTools в браузере на странице ChatGPT.
- Вкладка Console — выберите iframe‑контекст вашего виджета.
- Посмотрите на JS‑ошибки.
- Вкладка Network — убедитесь, что:
- JS‑бандл виджета загружается без 404/500;
- дополнительные запросы (через fetch/window.openai.fetch) возвращают осмысленные ответы.
Параллельно смотрите на ваш DebugBanner: если он не появился, значит, до React‑дерева вообще не дошли.
Шаг 4. Использовать Dev Mode для воспроизведения баг‑репорта
Когда вы получили от коллеги/пользователя баг‑репорт, старайтесь сохранять точный промпт, на котором сломалось. В Dev Mode можно очень быстро воспроизвести сценарий:
- Запустить npm run dev, поднять туннель.
- В Dev Mode выбрать App.
- Вставить проблемный промпт.
- Параллельно:
- смотреть, какие JSON‑запросы приходят на MCP по логам;
- в Inspector, при необходимости, повторить tools/call с теми же аргументами.
Так вы превращаете «иногда что‑то не работает» в воспроизводимый сценарий.
8. Небольшие кодовые штрихи для удобного debug
Чтобы закрепить материал, добавим ещё пару полезных фрагментов в наше приложение GiftGenius.
Конфигурация окружения и уровней логирования
Где‑то в конфигурации сервера удобно явно прописать endpoint MCP и уровень логирования:
// src/config.ts
export const config = {
mcpEndpoint:
process.env.NODE_ENV === "development"
? "http://localhost:3000/api/mcp" // туннель накрывает это
: "https://api.giftgenius.com/api/mcp",
logLevel: process.env.NODE_ENV === "development" ? "DEBUG" : "ERROR",
};
И в logToolEvent можно учитывать logLevel, чтобы в проде не спамить лишним.
Логирование структурированных ошибок MCP
При обработке инструментов старайтесь ловить ожидаемые ошибки и возвращать понятные сообщения, а не валить всё в throw:
export async function searchGiftsTool(args: { q: string }) {
const traceId = crypto.randomUUID();
logToolEvent("start", { tool: "search_gifts", traceId, args });
try {
// ... нормальный код ...
return { content: [{ type: "text", text: "Найдено 3 подарка" }] };
} catch (err) {
logToolEvent("error", { tool: "search_gifts", traceId, error: String(err) });
return {
content: [{ type: "text", text: "Ошибка поиска подарков. Попробуйте позже." }],
isError: true,
};
}
}
Так ChatGPT увидит, что результат помечен как isError, и сможет корректно озвучить проблему пользователю, а вы — увидите, что произошло, в логах.
9. Типичные ошибки при локальном debug ChatGPT App
Ошибка №1: отлаживать «через GPT», а не через сервер и инспектор.
Очень соблазнительно просто смотреть, что отвечает модель, и пытаться угадать, где у вас баг. Но модель — самый верхний слой. Если MCP‑сервер не работает сам по себе (руками, через Inspector) — нечего ждать чудес от GPT. Сначала добейтесь стабильной работы MCP, а уже потом подключайте ChatGPT.
Ошибка №2: не смотреть логи вообще или логировать всё подряд.
Отсутствие логов приводит к полной слепоте: вы не знаете, какой инструмент вызвался, с какими аргументами и чем всё закончилось. Перелогирование, наоборот, превращает консоль в «матрицу» из несвязанных строк. Лучше иметь компактный, структурированный лог с tool, args (обезличенными), traceId, status и временем выполнения.
Ошибка №3: хранить в логах чувствительные данные.
Логировать токены, полные e‑mail и номера карт — плохая практика и с точки зрения безопасности, и с точки зрения политики OpenAI. В логах должна быть только та информация, которая реально помогает debug, а персональные данные — маскируем или не пишем вовсе.
Ошибка №4: винить Dev Mode во всех грехах.
Dev Mode часто становится козлом отпущения: «Ну это OpenAI что‑то сломал». На деле очень часто проблема в том, что вы забыли обновить URL после перезапуска туннеля или указали не тот путь (/ вместо /mcp). Перед тем, как писать в поддержку, загляните в настройки Dev Mode и сверьте endpoint с фактическим адресом сервера.
Ошибка №5: игнорировать DevTools и ошибку в виджете.
Пустой или серый виджет почти всегда означает JavaScript‑ошибку на клиенте. Если вы смотрите только на логи MCP, но не открываете DevTools в ChatGPT, вы видите только половину картины. Привычка на автомате нажимать F12 и смотреть Console/Network сэкономит часы жизни.
Ошибка №6: пытаться «починить» баг магическими задержками.
Иногда хочется сделать setTimeout или Thread.sleep‑стайл задержку «чтобы всё успело прогрузиться». В мире MCP/Next/React это почти всегда неправильное лечение: проблема обычно в схеме, неверном endpoint или ошибке кода, а не в том, что «сервер не успел». Лучше понять, где именно разрыв (Inspector → Dev Mode → виджет), чем закапывать его задержками.
Ошибка №7: деплоить на Vercel, не убедившись, что локально всё работает.
Желание «быстрее в прод» понятно, но переносить сломанный MCP на Vercel — идеальный способ получить два уровня проблем: локальный и продакшен. В этом модуле мы сознательно требуем: сначала MCP Jam/Inspector → всё ок, Dev Mode → базовые сценарии работают, и только потом деплой.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ