JavaRush /Курси /ChatGPT Apps /Локальне налагодження: журнали, MCP‑інспекція та Dev Mode...

Локальне налагодження: журнали, MCP‑інспекція та Dev Mode

ChatGPT Apps
Рівень 7 , Лекція 1
Відкрита

1. Навіщо окрема лекція про локальне налагодження

У попередніх модулях ми вже розібрали, як улаштований стек Apps SDK і MCP. Тепер поговорімо про те, навіщо взагалі потрібна окрема лекція про локальне налагодження.

У багатьох шлях такий: «Ну, я просто відкрию ChatGPT, напишу “використовуй мій App”, а далі дивитимуся, що він скаже. Якщо не працює — перепишу код навмання». Це приблизно як лагодити бекенд, дивлячись лише на HTML‑сторінку в браузері, і жодного разу не відкривши журнали сервера.

Із ChatGPT Apps особливо легко зісковзнути в «магію»: є GPT, він сам вирішує, викликати інструмент чи ні, і має власну логіку помилок. Якщо ви не бачите, що відбувається «під капотом», налагодження перетворюється на шаманство з бубном.

Наша мета: перетворити це на нормальний інженерний процес:

  • ви знаєте, де дивитися журнали Next/MCP;
  • ви вмієте вручну викликати MCP‑сервер через інспектор;
  • ви розумієте, що саме перевіряє Dev Mode і як упевнитися, що ChatGPT узагалі може підʼєднатися до вашого сервера.

І найважливіше: ви перестаєте налагоджувати через «вгадайку з GPT» і починаєте спершу перевіряти нижчі рівні стеку — сервер і протокол, а вже потім UI та поведінку моделі.

2. Ментальна модель: три рівні налагодження

Щоб не тонути в хаосі, домовмося мислити про налагодження як про три рівні. Це наш маленький «листковий пиріг»:

Рівень Що там «живе» Типові симптоми Чим налагоджуємо
UI (віджет) React‑компоненти, стан, window.openai Порожній або сірий віджет, некоректне відображення, кнопки не працюють DevTools браузера
Бекенд / MCP‑сервер інструменти, доступ до БД/API помилки 500, «інструмент упав», дивні дані журнали сервера, MCP Inspector
Протокол MCP JSON‑RPC, tools/list, tools/call, схеми GPT пише «не зміг викликати інструмент», invalid params інспектор і журнали запитів

На другому рівні нас цікавить, що робить сам MCP‑сервер (інструменти, БД, API). На третьому — уже «дроти» й формат MCP‑повідомлень (JSON‑RPC, схеми тощо).

Ця трійка — основа плану лекції та всього підходу до налагодження.

Для наочності можна подивитися на потік запиту:

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 (через Тунель)
    Next->>Next: Викликаємо MCP tool, звертаємось до БД/API
    Next-->>ChatGPT: JSON-RPC result + ToolOutput
    ChatGPT-->>User: Відповідь + рендер віджета

Зламатися може в будь‑якій точці: тунель, ендпойнт, MCP‑логіка, JSON‑схема, React‑віджет. Ваше завдання під час налагодження — зрозуміти, на якому саме шарі сталася помилка, а не одразу переписувати все підряд.

3. Журнали Next.js і MCP: основа всього

Почнемо з найнуднішого й водночас найкориснішого — журналів.

Де «живуть» журнали під час локальної розробки

У стандартному шаблоні Apps SDK на Next.js MCP‑сервер зазвичай загорнутий в API‑маршрут (/api/mcp або подібний). Ви запускаєте npm run dev, і в одному терміналі бачите:

  • dev‑сервер Next.js;
  • обробник для MCP‑ендпойнта, який приймає JSON‑RPC‑запити tools/list, tools/call тощо;
  • вивід усього «цікавого» через console.log/console.error.

Якщо ви винесли MCP в окремий процес, буде другий термінал. Але ідея та сама: усе важливе видно в консолі.

Важливо розрізняти:

  • помилки збірки/запуску — не стартує next dev, падає TypeScript, неправильний імпорт тощо;
  • помилки виконання — усе запустилося, але конкретний запит на /api/mcp призводить до падіння інструмента.

Next.js у режимі розробки показує помилки виконання ще й гарною накладкою, а також пише трасування стека в консоль.

Що логувати в MCP‑сервері

Хоча MCP використовує протокол JSON‑RPC, для налагодження вам не потрібно друкувати буквально весь 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;
  }
}

Тут є дві важливі тонкощі.

По‑перше, не варто зберігати в журналах повні адреси електронної пошти, телефони, номери карток і токени. Це не лише неохайно, а й суперечить базовим практикам безпеки MCP.

По‑друге, traceId — ваш найкращий друг. Коли ви дивитеся журнали Next.js і MCP разом, за ним легко повʼязати події: конкретний запит tools/call, відповідний React‑рендер і мережевий журнал віджета.

Як зрозуміти з журналів, де «впало»

У вас є термінал, і в нього летять JSON‑рядки від logToolEvent. Типовий сценарій:

  • прийшов phase: "start" з tool: "search_gifts";
  • немає phase: "end", зате є phase: "error" і трасування стека;
  • із цього видно, що інструмент дійшов до вашої логіки, але щось зламалося всередині — наприклад, запит до зовнішнього API, парсинг або робота з БД.

Якщо ж ви взагалі не бачите журналів для цього імені інструмента, отже, запит навіть не дійшов до інструмента. Тоді підіймайтеся вище по стеку: тунель, ендпойнт /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

Типовий сценарій локального налагодження виглядає так:

  1. Запускаєте npm run dev, щоб Next.js + MCP‑ендпойнт піднялися.
  2. Запускаєте MCP Inspector, наприклад:
npx @modelcontextprotocol/inspector

(конкретна команда залежить від інструмента, який ви використовуєте).

  1. В Inspector зазначаєте URL вашого MCP‑ендпойнта, наприклад http://localhost:3000/api/mcp (або HTTPS‑тунель, якщо хочете перевірити й його).
  2. Дивитеся, чи пройшов handshake: сервер має відповісти підтримуваними capabilities, списком tools, ресурсів тощо.
  3. Викликаєте потрібний інструмент вручну: обираєте 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("Навчальна помилка для демонстрації налагодження");
  }
  // ... нормальна логіка ...
  return [];
}

Далі:

  1. В Inspector викликаєте search_gifts з аргументом {"q": "зламайся"}.
  2. У журналах бачите phase: "error" і трасування стека.
  3. Переконуєтеся, що MCP‑сервер чесно повертає помилку.

Потім, коли ви підʼєднаєте все це до ChatGPT Dev Mode і попросите модель «підібрати подарунок зі словом "зламайся"», вона спробує викликати інструмент і покаже користувачу повідомлення на кшталт «I encountered an error running the tool». Тобто помилка зʼявляється не через модель, а через ваш явний виняток.

Такий прийом добре тренує мислення: ви чітко відділяєте бізнес‑помилку (ми самі кинули Error) від протокольної (зламали JSON, неправильна назва інструмента тощо).

5. Налагодження віджета: DevTools, стан і «debug‑банер»

Коли MCP‑сервер більш‑менш зрозумілий, переходимо до фронтенду — віджета Apps SDK.

Де і як дивитися помилки віджета

Ваш віджет відображається всередині ChatGPT в iframe‑пісочниці. Але є хороша новина: для цього iframe доступні ті самі браузерні DevTools.

Міні‑процедура:

  1. Відкриваєте ChatGPT у браузері (Chrome/Edge/Firefox).
  2. Відкриваєте DevTools (зазвичай F12 або Ctrl+Shift+I).
  3. На вкладці Console обираєте контекст фрейму, у якому «живе» ваш віджет (часто це домен web-sandbox.oaiusercontent.com).
  4. Оновлюєте чат або надсилаєте повідомлення, щоб 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 узагалі не дійшов до браузера. Причина може бути різною: помилка збірки, проблема з ендпойнтом або те, що віджет просто не зареєстрований у 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>
)}

Для налагодження важливо, щоб:

  • ви не «ковтали» винятки (інакше в консолі буде порожньо, а UI просто «зависатиме»);
  • ви явно показували помилку в UI — інакше користувачу здаватиметься, що App «помер».

6. Dev Mode як частина налагодження: що він робить і як не звинувачувати його дарма

Тепер додамо до картини ChatGPT Dev Mode. До цього ми розглядали лише ваш код. Але інколи все працює локально, в Inspector усе чудово, а ChatGPT все одно відповідає «Error talking to [AppName]» або взагалі не пропонує ваш App.

Що робить Dev Mode

Dev Mode — це режим ChatGPT, у якому ви можете:

  • створювати й редагувати свої Apps;
  • вказувати ендпойнт MCP‑сервера (зазвичай https://ваш-домен/mcp або /api/mcp);
  • швидко оновлювати маніфест і метадані без публікації в Store.

Із погляду налагодження Dev Mode — це просто ще один шар конфігурації:

  • якщо там зазначено неправильний URL;
  • якщо ви забули /mcp у кінці;
  • якщо тунель видав новий домен, а ви не оновили налаштування

— ChatGPT просто не може підʼєднатися до вашого сервера.

Типовий сценарій збою Dev Mode

Класика жанру:

  1. Ви підняли тунель https://abcd.ngrok.io, указали його в Dev Mode — усе працювало.
  2. Наступного дня перезапустили ngrok і отримали https://efgh.ngrok.io.
  3. У Dev Mode досі https://abcd.ngrok.io/mcp.
  4. 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, ендпойнт буде жорсткіше зафіксованим, і такі фокуси стануть поганою ідеєю. Але до Store нам іще кілька модулів, тож поки спокійно ламаємо й лагодимо в Dev Mode.

7. Міні‑алгоритм налагодження: що робити, коли «нічого не працює»

Тепер зберемо все в практичний алгоритм. По суті, це ті самі три рівні налагодження з початку лекції, тільки записані як послідовність кроків.

Припустімо, ви відкрили ChatGPT, вибрали GiftGenius, попросили «Підбери подарунок до 30 $ для друга‑гіка», і:

  • GPT нічого не пише про App;
  • або пише «Error talking to GiftGenius»;
  • або відкривається порожній/сірий віджет.

Як не впасти у відчай?

Крок 1 (рівень MCP/сервер). Перевірити MCP через Inspector і журнали

Спочатку ігноруємо GPT і UI. Нас цікавить лише сервер.

  1. Переконайтеся, що npm run dev запущено, а ендпойнт (/api/mcp) відповідає.
  2. Підʼєднайте MCP Inspector до http://localhost:3000/api/mcp або до вашого тунелю.
  3. Перевірте handshake — список tools має відображатися.
  4. Викличте вручну той самий інструмент, який, за задумом, має викликати GPT (наприклад, search_gifts), зі схожими аргументами.

Якщо вже тут усе падає — лагодьте MCP: схеми, бізнес‑логіку, мережеві виклики. Використовуйте журнали і traceId, щоб зрозуміти, що саме ламається.

Крок 2 (рівень протоколу/Dev Mode). Перевірити Dev Mode і URL

Якщо в Inspector усе прекрасно, а ChatGPT досі не бачить ваш App або пише про проблеми зі зʼєднанням:

  1. Відкрийте налаштування Dev Mode для вашого App.
  2. Подивіться, який URL там вказано для MCP.
  3. Звірте його з тим, що реально слухає ваш сервер/тунель (і не забудьте перевірити, що в кінці є /mcp, якщо ваш сервер цього потребує).

Дуже часто проблема саме тут.

Крок 3 (рівень UI). Перевірити віджет через DevTools

Якщо ChatGPT успішно викликає інструменти (це видно з журналів MCP), але сам віджет поводиться дивно:

  1. Відкрийте DevTools у браузері на сторінці ChatGPT.
  2. На вкладці Console оберіть iframe‑контекст вашого віджета.
  3. Подивіться на JS‑помилки.
  4. На вкладці Network переконайтеся, що:
    • JS‑бандл віджета завантажується без 404/500;
    • додаткові запити (через fetch/window.openai.fetch) повертають змістовні відповіді.

Паралельно дивіться на ваш DebugBanner: якщо він не зʼявився, значить, до React‑дерева ви взагалі не дійшли.

Крок 4. Використовувати Dev Mode для відтворення баг‑репорту

Коли ви отримали від колеги/користувача баг‑репорт, намагайтеся зберегти точний промпт, на якому все зламалося. У Dev Mode можна дуже швидко відтворити сценарій:

  1. Запустити npm run dev, підняти тунель.
  2. У Dev Mode вибрати App.
  3. Вставити проблемний промпт.
  4. Паралельно:
    • дивитися, які JSON‑запити приходять на MCP за журналами;
    • в Inspector за потреби повторити tools/call з тими самими аргументами.

Так ви перетворюєте «іноді щось не працює» на відтворюваний сценарій.

8. Невеликі кодові штрихи для зручного налагодження

Щоб закріпити матеріал, додамо ще кілька корисних фрагментів у наш застосунок GiftGenius.

Конфігурація оточення і рівнів логування

Десь у конфігурації сервера зручно явно прописати ендпойнт 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. Типові помилки під час локального налагодження ChatGPT App

Помилка №1: налагоджувати «через GPT», а не через сервер та інспектор.
Дуже спокусливо просто дивитися, що відповідає модель, і намагатися вгадати, де у вас баг. Але модель — це найвищий шар. Якщо MCP‑сервер не працює сам по собі (вручну, через Inspector), нічого чекати чудес від GPT. Спочатку досягніть стабільної роботи MCP, а вже потім підʼєднуйте ChatGPT.

Помилка №2: узагалі не дивитися журнали або записувати в них усе підряд.
Відсутність журналів призводить до повної сліпоти: ви не знаєте, який інструмент викликався, з якими аргументами й чим усе закінчилося. Надмірне логування, навпаки, перетворює консоль на «матрицю» з неповʼязаних рядків. Краще мати компактний, структурований журнал із tool, args (знеособленими), traceId, status і часом виконання.

Помилка №3: зберігати в журналах чутливі дані.
Логувати токени, повні адреси електронної пошти й номери карток — погана практика і з погляду безпеки, і з погляду політики OpenAI. У журналах має бути лише інформація, яка справді допомагає налагодженню. Персональні дані маскуйте або не пишіть узагалі.

Помилка №4: звинувачувати Dev Mode у всіх гріхах.
Dev Mode часто стає козлом відпущення: «Ну, це OpenAI щось зламав». Насправді ж дуже часто проблема в тому, що ви забули оновити URL після перезапуску тунелю або вказали не той шлях (/ замість /mcp). Перш ніж писати в підтримку, зазирніть у налаштування Dev Mode і звірте ендпойнт із фактичною адресою сервера.

Помилка №5: ігнорувати DevTools і помилки у віджеті.
Порожній або сірий віджет майже завжди означає JavaScript‑помилку на клієнті. Якщо ви дивитеся лише на журнали MCP, але не відкриваєте DevTools у ChatGPT, ви бачите лише половину картини. Звичка автоматично натискати F12 і переглядати Console/Network заощадить години життя.

Помилка №6: намагатися «полагодити» баг магічними затримками.
Іноді хочеться зробити setTimeout або затримку в стилі Thread.sleep, «щоб усе встигло завантажитися». У світі MCP/Next/React це майже завжди неправильне лікування: проблема зазвичай у схемі, неправильному ендпойнті або помилці коду, а не в тому, що «сервер не встиг». Краще зрозуміти, де саме розрив (Inspector → Dev Mode → віджет), ніж маскувати його затримками.

Помилка №7: розгортати на Vercel, не переконавшись, що локально все працює.
Бажання «швидше в продакшн» зрозуміле, але переносити зламаний MCP на Vercel — ідеальний спосіб отримати два рівні проблем: локальний і продакшн. У цьому модулі ми свідомо вимагаємо: спочатку MCP Jam/Inspector → усе гаразд, Dev Mode → базові сценарії працюють, і лише потім деплой.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ