JavaRush /Курси /ChatGPT Apps /Формат повідомлень MCP: requests, replies, notifications,...

Формат повідомлень MCP: requests, replies, notifications, tools/resources/prompts

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

1. MCP і JSON‑RPC: «нудний» фундамент, який варто зрозуміти один раз

У минулій лекції ми говорили, навіщо взагалі потрібен MCP і як він вписується в стек Apps SDK. Тут звузимо фокус до «нудного», але критично важливого шару — формату MCP‑повідомлень. Це потрібно, щоб ви могли впевнено читати сирі JSON‑журнали (логи) і розуміти, що саме ChatGPT надсилає вашому серверу та що повертається у відповідь.

MCP використовує JSON‑RPC 2.0 як транспорт даних: усі запити, відповіді та сповіщення — це звичайні JSON‑обʼєкти з передбачуваною схемою.

Тобто замість ситуації «кожен сервіс вигадує власний формат» є базовий контракт:

  • у запиті є обовʼязкове поле jsonrpc (зазвичай "2.0"), унікальний id, рядкове імʼя методу method і обʼєкт params із параметрами;
  • відповідь зіставляється із запитом за id і містить або result, або error;
  • сповіщення (notifications) схожі на запити, але без id — і відповіді на них не буде.

Виглядає це приблизно так:


{
  "jsonrpc": "2.0",
  "id": 42,
  "method": "tools/list",
  "params": {
    "cursor": null
  }
}

Це request. А відповідь у разі успіху:

{
  "jsonrpc": "2.0",
  "id": 42,
  "result": {
    "tools": [],
    "nextCursor": null
  }
}

Якщо у вас промайнуло «та це ж звичайний RPC», — так і є. MCP просто фіксує, які саме методи існують (tools/list, tools/call, resources/list, prompts/list, …) і у якому форматі вони очікують параметри та повертають дані.

Важливо відчути різницю: JSON‑RPC — це каркас «запит — відповідь — сповіщення». MCP — це «які саме запити бувають і що в них усередині».

2. Request: як MCP просить щось зробити

Почнемо із запитів. Вони завжди означають, що «хтось хоче щось зробити». Зазвичай це клієнт → сервер (ChatGPT → ваш MCP‑сервер), але MCP допускає й зворотний напрям — коли сервер просить клієнта виконати sampling або elicitation. У цій лекції нас насамперед цікавить класичний варіант: клієнт звертається до сервера.

У будь‑якого MCP‑request є три ключові поля:

  1. jsonrpc — версія протоколу JSON‑RPC, зазвичай "2.0".
  2. id — ідентифікатор запиту; це може бути будь‑який JSON‑тип, але на практиці найчастіше — число або рядок. Головне, щоб для активних запитів id були унікальними.
  3. method — рядок на кшталт "tools/list" або "tools/call". MCP специфікує набір допустимих методів.

Далі йде обʼєкт params, у якому вже містяться параметри конкретного методу.

Приклад: запит списку інструментів

Уявімо, що ChatGPT щойно підʼєднався до вашого MCP‑сервера і хоче дізнатися, які tools він може викликати. Він надішле запит приблизно такого вигляду:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list",
  "params": {
    "cursor": null
  }
}

Поле cursor потрібне для пагінації: якщо інструментів багато, сервер може віддавати їх порціями.

Для нашого навчального застосунку (підбір подарунків) тут поки що буде нудно: один‑два інструменти. Але протокол лишається тим самим. Поки сприймайте це як інтуїтивний приклад; формальні структури розберемо пізніше — у розділі про tools.

Приклад: виклик інструмента (tools/call)

Тепер трохи цікавіше. Припустімо, у нас уже є MCP‑tool suggest_gifts, який ви плануєте реалізувати в лекції про MCP‑сервер. Він очікує параметри:

  • occasion — привід (Birthday, Wedding, …),
  • budget — число в доларах,
  • recipient — рядок з описом, кому даруємо.

ChatGPT, вирішивши скористатися цим інструментом, сформує MCP‑запит:

{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "tools/call",
  "params": {
    "name": "suggest_gifts",
    "arguments": {
      "occasion": "birthday",
      "budget": 100,
      "recipient": "friend who loves board games"
    }
  }
}

Зверніть увагу на кілька деталей.

По‑перше, імʼя інструмента береться з того, що ви оголосили на боці сервера (server.registerTool("suggest_gifts", …)). По‑друге, обʼєкт arguments має відповідати JSON Schema, яку ви додасте в описі інструмента.

Якщо GPT спробує надіслати аргументи не за схемою (наприклад, budget: "сто баксів"), сервер має право повернути помилку на рівні протоколу або бізнес‑логіки — залежно від реалізації. Поки головне — вловити загальну форму такого запиту. А в розділі про tools нижче ми подивимося на ці ж повідомлення вже системніше.

Requests для ресурсів і промптів

Аналогічно виглядають запити до ресурсів і промптів. Специфікація MCP визначає методи:

  • resources/list — перелічити доступні ресурси;
  • resources/read (або resources/get) — прочитати конкретний ресурс за URI;
  • prompts/list — отримати список доступних промптів;
  • prompts/get — отримати текст конкретного промпта.

Приклад запиту читання ресурсу з каталогом подарунків:

{
  "jsonrpc": "2.0",
  "id": 15,
  "method": "resources/read",
  "params": {
    "uri": "mcp://gift-server/resources/gift_catalog"
  }
}

Поки достатньо запамʼятати дві речі. По‑перше, для кожного примітиву є методи */list і */get/*/read. По‑друге, імʼя методу завжди живе в рядковому полі method, а весь вміст — в обʼєкті params.

3. Reply: як MCP відповідає — result і error

Відповідь (reply) завжди зіставляється із запитом за полем id. Це як correlationId у багатьох розподілених системах: ви відкриваєте журнали й бачите, що запит із id = 7 отримав відповідь із id = 7. Отже, це одна пара.

JSON‑RPC встановлює просте правило: у відповіді або result, або error, але не обидва одночасно. MCP поверх цього уточнює структуру result для різних методів (tools/list, tools/call тощо) і рекомендує коди помилок.

Успішна відповідь (result)

Подивімося на приклад успішної відповіді на tools/call нашого suggest_gifts. Сервер усе відпрацював, підібрав відповідні подарунки й повертає список у полі result:

{
  "jsonrpc": "2.0",
  "id": 7,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Here are some gift ideas for your friend..."
      }
    ],
    "structuredContent": {
      "gifts": [
        { "name": "Board game: Catan", "price": 45 },
        { "name": "Dice set", "price": 20 }
      ]
    },
    "isError": false
  }
}

Тут важливі кілька моментів.

  • По‑перше, content і structuredContent — це ті самі частини відповіді MCP‑tools, які ви вже бачили в Apps SDK. Модель використовує текст із content, а ваш віджет акуратно відображає дані з structuredContent.
  • По‑друге, прапорець isError стосується бізнес‑результату. З погляду протоколу все пройшло успішно: JSON коректний, метод існує, аргументи розібрані. Але бізнес‑логіка може сказати: «я не знайшов жодної ідеї подарунка, і з точки зору UX це вважаємо помилкою». Тоді ви ставите isError: true і описуєте проблему в content.
  • По‑третє, специфікація MCP для різних методів (tools/list, tools/call, */list, */get) детально описує, які поля мають бути в result. Наприклад, для tools/list сервер повертає масив описів інструментів з іменами, заголовками, описами та JSON Schema вхідних аргументів.

Помилкова відповідь (error)

Якщо щось пішло не так на рівні протоколу або на боці сервера, замість result повертається обʼєкт error. У ньому зазвичай є:

  • code — числовий код помилки;
  • message — зрозумілий людині опис;
  • data — опційні додаткові дані (stack trace, подробиці, …).

Приклад: модель викликала неіснуючий метод:

{
  "jsonrpc": "2.0",
  "id": 99,
  "error": {
    "code": -32601,
    "message": "Method not found: tools/col"
  }
}

Код -32601 — класичний для JSON‑RPC: «method not found».

Є тонка, але важлива межа між двома типами помилок.

Протокольна помилка — коли порушено правила MCP/JSON‑RPC: невідомий метод, неправильний тип у полі params, невалідний JSON. Тоді доречно повертати error на верхньому рівні.

Бізнес‑помилка — коли протокол дотримано, але сама операція не вдалася з доменної причини: порожній каталог, немає прав на конкретний ресурс, некоректний бізнес‑ідентифікатор. Тоді MCP зазвичай рекомендує повернути коректний result, але позначити його як isError: true і описати проблему у вмісті.

Це розділення дуже допомагає і ChatGPT, і засобам налагодження: дивлячись у журнали, ви одразу бачите, чи це була технічна поломка, чи свідома відмова бізнес‑логіки.

4. Notifications: односпрямовані повідомлення

Сповіщення (notification) — це «лист без очікування відповіді». У JSON‑RPC сповіщення виглядають як звичайні запити без поля id. Клієнт не повинен надсилати на них reply.

У MCP сповіщення використовують для подій: змін у списках tools/resources/prompts, прогресу тривалих операцій, лог‑повідомлень тощо.

Найпростіший приклад, який ви точно зустрінете, — сповіщення про те, що перелік інструментів змінився. Специфікація MCP для tools описує можливість (capability) listChanged і сповіщення tools/list_changed, яке сервер надсилає, якщо набір доступних tools змінився.

Сповіщення може виглядати так:

{
  "jsonrpc": "2.0",
  "method": "tools/list_changed",
  "params": {
    "reason": "New tool 'suggest_gift_cards' was added"
  }
}

Відповідь на нього не потрібна. Клієнт, отримавши таке сповіщення, може вирішити: «ага, треба ще раз викликати tools/list і оновити кеш інструментів».

Інші типові сповіщення MCP (про них детально говоритимемо в модулі про потоки та події):

  • події прогресу (notifications/progress) для тривалих операцій;
  • логи сервера (notifications/logging/message);
  • зміни ресурсів (resources/list_changed) і промптів (prompts/list_changed).

Поки важливе одне: сповіщення = запит без id і без очікуваної відповіді. Якщо ви бачите в журналах JSON без id, це, найімовірніше, notification.

Нотатка

На практиці наразі застосунок ChatGPT ігнорує надіслані йому повідомлення (MCP‑notification). Водночас, зважаючи на те, що ChatGPT Apps лише на початку розвитку, імовірність повної підтримки всіх частин MCP‑протоколу в найближчому майбутньому досить висока. Тож раджу все ж розібратися і з цим боком MCP‑протоколу.

5. Як виглядають tools/resources/prompts у повідомленнях

Тепер найцікавіше: як саме всередині MCP‑повідомлень описані ті самі tools, resources і prompts, про які ми так багато говоримо.

Tools: опис і виклик

На рівні протоколу у tools є два основні процеси:

  1. discovery — клієнт дізнається, які інструменти є;
  2. invocation — клієнт викликає конкретний інструмент.

Ми вже мимохідь бачили tools/list і tools/call вище. Тепер подивімося на них системніше: які саме процеси вони покривають і що саме повертається в result.

5.1.1. Список інструментів — tools/list

Ми вже бачили request для tools/list. Тепер подивімося на структуру відповіді. Специфікація MCP каже: у result.tools має повернутися масив обʼєктів, кожен з яких описує один інструмент. Інструмент обовʼязково має:

  • name — унікальне імʼя, за яким потім викликатиметься tools/call;
  • title — короткий заголовок (це бачить і людина, і модель);
  • description — розгорнутіший опис того, що робить tool, ніби ви пояснюєте це колезі;
  • inputSchema — JSON Schema для аргументів інструмента.

Для нашого suggest_gifts відповідь tools/list може виглядати так (значно спрощено):

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "tools": [
      {
        "name": "suggest_gifts",
        "title": "Gift ideas generator",
        "description": "Suggests gift ideas for a given occasion and budget.",
        "inputSchema": {
          "type": "object",
          "properties": {
            "occasion": { "type": "string" },
            "budget": { "type": "number" },
            "recipient": { "type": "string" }
          },
          "required": ["occasion", "budget"]
        }
      }
    ],
    "nextCursor": null
  }
}

Якщо ви в Apps SDK уже писали inputSchema під час реєстрації інструмента, ви фактично бачили цей обʼєкт, тільки «згори» — у вигляді TypeScript‑обʼєкта. MCP просто передає його по протоколу клієнту.

5.1.2. Виклик інструмента — tools/call

Формат виклику ми вже зачіпали. Специфікація MCP описує, що params має містити:

  • name — імʼя інструмента;
  • arguments — обʼєкт, що відповідає inputSchema.

Наприклад:

{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "tools/call",
  "params": {
    "name": "suggest_gifts",
    "arguments": {
      "occasion": "wedding",
      "budget": 150,
      "recipient": "coworker from marketing"
    }
  }
}

А у відповідь сервер віддає result з content, structuredContent і, опційно, _meta (наприклад, із зазначенням openai/outputTemplate, якщо ви хочете повʼязати цей tool із конкретним віджетом).

Ця звʼязка tools/listtools/call — базовий цикл роботи MCP‑tools: спочатку discovery, потім використання.

Resources: дані з адресою

Resources у MCP — це будь‑які фрагменти даних, до яких клієнт може звертатися за URI: файли, записи БД, конфіги, каталоги тощо.

У них стандартний набір операцій:

  • resources/list — щоб дізнатися, які ресурси є;
  • resources/read — щоб прочитати конкретний ресурс (або його частину).

Уявімо ресурс gift_catalog, який описує базовий каталог подарунків: категорії, бренди, мінімальні та максимальні ціни. Сервер може оголосити його з URI "mcp://gift-server/resources/gift_catalog".

Відповідь на resources/list може бути такою (спрощено):

{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "resources": [
      {
        "uri": "mcp://gift-server/resources/gift_catalog",	// просто унікальний рядок; це не протокол MCP.
        "name": "gift_catalog",
        "description": "Base catalog of gifts with categories and prices",
        "mimeType": "application/json"
      }
    ],
    "nextCursor": null
  }
}

А читання ресурсу — resources/read:

{
  "jsonrpc": "2.0",
  "id": 4,
  "method": "resources/read",
  "params": {
    "uri": "mcp://gift-server/resources/gift_catalog"
  }
}

Відповідь може містити сам контент і метадані:

{
  "jsonrpc": "2.0",
  "id": 4,
  "result": {
    "contents": [
      {
        "uri": "mcp://gift-server/resources/gift_catalog",
        "mimeType": "application/json",
        "text": "{\"categories\":[\"boardgames\",\"books\"]}"
      }
    ]
  }
}

Головна ідея: ресурс — це адресовані дані, а tools — це операції. І те, і інше MCP робить явним у протоколі.

Prompts: повторно використовувані шаблони

Prompts — це «заготовлені підказки» або шаблони, які сервер може надати клієнту. MCP трактує їх як примітив, у якого є:

  • імʼя;
  • зрозумілий людині заголовок/опис;
  • вміст (часто шаблон system‑промпта або набір few‑shot прикладів).

І, передбачувано, є два методи:

  • prompts/list — дізнатися, які промпти є;
  • prompts/get — отримати вміст одного промпта.

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

Відповідь на prompts/list може виглядати так:

{
  "jsonrpc": "2.0",
  "id": 10,
  "result": {
    "prompts": [
      {
        "name": "gift_congrats_style",
        "description": "Style guide for birthday congratulations in a friendly tone"
      }
    ]
  }
}

А prompts/get поверне вже сам текст (або структурований вміст), який клієнт може потім передати в LLM як частину system‑prompt. Приклад такого запиту та відповіді:

{
  "jsonrpc": "2.0",
  "id": 11,
  "method": "prompts/get",
  "params": {
    "name": "gift_congrats_style"
  }
}
{
  "jsonrpc": "2.0",
  "id": 11,
  "result": {
    "prompt": {
      "name": "gift_congrats_style",
      "messages": [
        {
          "role": "system",
          "content": [
            {
              "type": "text",
              "text": "You are a friendly assistant that writes short, warm birthday congratulations..."
            }
          ]
        }
      ]
    }
  }
}

6. Як це повʼязано з Apps SDK і нашим віджетом

Зараз MCP‑JSON, можливо, усе ще виглядає дещо громіздко. Давайте повʼяжемо це з тим, що ви вже робили через Apps SDK.

Нагадаємо, у фронтенді віджета у вас може бути код:

// усередині React-компонента в пісочниці ChatGPT
async function fetchGifts() {
  const result = await window.openai.callTool("suggest_gifts", {
    occasion: "birthday",
    budget: 50,
    recipient: "friend who loves sci-fi"
  });

  console.log(result);
}

На рівні Apps SDK це зручна функція, яка:

  1. знає URL MCP‑сервера (з конфігурації застосунку);
  2. уміє за іменем suggest_gifts знайти опис інструмента;
  3. пакує ваш виклик у MCP‑request tools/call;
  4. надсилає його обраним транспортом (HTTP/SSE);
  5. чекає MCP‑reply, розпаковує result і віддає його вам як result у JavaScript.

Якщо намалювати це у вигляді схеми, вийде приблизно так:

sequenceDiagram
    participant Widget
    participant AppsSDK as Apps SDK
    participant MCP as MCP‑сервер

    Widget->>AppsSDK: window.openai.callTool("suggest_gifts", {...})
    AppsSDK->>MCP: JSON { id:7, method:"tools/call", params:{...} }
    MCP-->>AppsSDK: JSON { id:7, result:{ content, structuredContent } }
    AppsSDK-->>Widget: result (ToolOutput)
    Widget->>Widget: setState(toolOutput)

Розуміння MCP‑формату дає вам дві дуже корисні навички.

По‑перше, ви можете впевнено дивитися на сирі MCP‑журнали (наприклад, у MCP Inspector, про який буде окрема лекція) й бачити: який саме tools/call пішов, які в ньому були аргументи, і що повернулося — result чи error.

По‑друге, під час проєктування інструментів і ресурсів ви можете думати не лише в термінах TypeScript‑типів, а й у термінах MCP‑схем: як це виглядатиме в JSON і наскільки це зручно для інших клієнтів (наприклад, агентів, які теж можуть підʼєднатися до вашого MCP‑сервера).

7. Міні‑практика: читаємо й налагоджуємо MCP‑JSON

Щоб MCP‑формат став «своїм», краще один раз власноруч розібрати по частинах кілька повідомлень. Візьмемо приклад цілісного діалогу tools/listtools/call → результат.

Клієнт хоче список інструментів

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list",
  "params": {}
}

Що ми бачимо:

  • це request (є id);
  • метод tools/list, отже, йдеться про discovery інструментів;
  • параметри порожні, без пагінації.

Сервер відповідає:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "tools": [
      {
        "name": "suggest_gifts",
        "title": "Gift ideas generator",
        "description": "Suggests gift ideas",
        "inputSchema": { "type": "object", "properties": { "occasion": { "type": "string" } } }
      }
    ]
  }
}

Одразу видно, що це відповідь на той самий запит (той самий id: 1). Протокол відпрацював успішно: result є, error немає. Тепер клієнт знає, що існує tool suggest_gifts.

Клієнт викликає інструмент

Далі клієнт робить tools/call:

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "suggest_gifts",
    "arguments": {
      "occasion": "anniversary"
    }
  }
}

Якщо сервер очікує ще й budget, але модель не вказала його, сервер може:

  • або повернути протокольну помилку (наприклад, error із кодом «invalid params»);
  • або прийняти рішення за замовчуванням (наприклад, використати середній бюджет) і повернути нормальний result.

У термінах, які ми вводили вище, перший варіант — протокольна помилка (error на верхньому рівні). Другий — уже область бізнес‑логіки: ви все одно повертаєте коректний result і вирішуєте, чи вважати таку ситуацію бізнес‑помилкою (isError: true), чи нормальною поведінкою.

Відповідь у разі помилки аргументів могла б виглядати так:

{
  "jsonrpc": "2.0",
  "id": 2,
  "error": {
    "code": -32602,
    "message": "Missing required property 'budget' in arguments"
  }
}

Знову ж таки, відрізняємо це від бізнес‑помилки: протокол порушено (аргументи не відповідають схемі), тому тут доречний error.

Зламаний приклад: шукаємо баг

Ось JSON, який інколи трапляється в новачків:

{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "tool": "suggest_gifts",
    "args": {
      "occasion": "birthday",
      "budget": 100
    }
  }
}

На перший погляд усе виглядає правдоподібно. Але якщо ви звіритеся зі специфікацією MCP, то помітите, що поля tool і args не збігаються з очікуваними name і arguments.

Клієнт/сервер MCP‑SDK, найімовірніше, ніколи не згенерує такий JSON. Але якщо ви, не знаючи специфікації, інтегруєтеся вручну, подібний баг цілком реальний. Саме тому в курсі ми й розбираємо протокол «у чистому вигляді», а не лише SDK‑обгортки.

8. Типові помилки під час роботи з повідомленнями MCP

Помилка №1: змішування протокольних і бізнес‑помилок.
Часто розробники зі звички все, що «пішло не так», загортають у верхньорівневий error — і відсутність ресурсу, і неправильні аргументи, і збій бази даних. У контексті MCP корисно розділяти: якщо JSON‑структуру та схему виклику порушено (не той метод, не ті поля, некоректні типи), це привід повернути error. Якщо ж інструмент просто не зміг виконати доменну операцію (немає подарунків для такого бюджету, користувача не знайдено), краще повернути коректний result із isError: true і зрозумілим повідомленням у content. Тоді і ChatGPT‑модель, і засоби налагодження зможуть коректно відрізняти «зламався канал звʼязку» від «сервер свідомо відмовив».

Помилка №2: ігнорування поля id і кореляції запитів.
Інколи в журналах MCP‑сервера можна побачити ручний вивід без id або з повторюваними значеннями id для різних активних запитів. В однопотоковому hello world це ще може якось працювати, але щойно у вас зʼявляються паралельні виклики або повторні спроби, стає складно зрозуміти, яка відповідь до якого запиту належить. JSON‑RPC спеціально вимагає унікальний id на час життя запиту, і MCP покладається на це правило. Якщо ви використовуєте офіційні SDK, про id можна не думати. Але щойно ви пишете транспорт або логування самі, не забувайте зберігати й виводити id — це перша річ, за якою ви налагоджуватимете дивні баги.

Помилка №3: нестабільні структури result для одного й того самого методу.
Є спокуса «трошки» міняти формат відповіді залежно від ситуації: іноді повертати масив подарунків, іноді — обʼєкт з одним рядком, іноді взагалі лише text без structuredContent. Модель, можливо, і переживе такі фокуси, але ваші віджети та будь‑які інші клієнти MCP — навряд. Специфікація MCP для кожного методу описує передбачувану структуру result; намагайтеся її дотримуватися. Якщо потрібен інший формат, краще оголосіть окремий tool або версію, а не змінюйте схему «на льоту».

Помилка №4: зайві або відсутні поля в params.
Типова проблема кастомних реалізацій — додати в params те, чого MCP не очікує, або забути обовʼязкове поле. Наприклад, надіслати toolName замість name у tools/call, або resourceId замість uri у resources/read. MCP‑SDK зазвичай такі речі валідовує і викидає зрозумілий виняток. Але якщо ви працюєте ближче до протоколу, можна довго шукати, чому «сервер мене не розуміє». Хороший прийом — поруч із обробником тримати приклад коректного JSON‑запиту зі специфікації або з журналів робочого клієнта й порівнювати з тим, що ви надсилаєте.

Помилка №5: спроба використовувати notifications як «другий канал відповідей».
Інколи розробники, побачивши notifications, починають надсилати результати операцій через сповіщення замість звичайних replies: «ну ми ж уже в MCP і в нас є SSE, давайте пушити все нотифікаціями». Проблема в тому, що JSON‑RPC‑сповіщення за означенням не привʼязані до конкретного id і не сприймаються клієнтом як відповідь на запит. У результаті складніше налагоджувати й неможливо зрозуміти, до якого виклику інструментів належить конкретне повідомлення. Сповіщення чудово підходять для подій (змінився список tools/resources/prompts, зʼявився новий прогрес, прийшов лог), але не для звичайних відповідей на tools/call та подібні виклики.

Помилка №6: не дивитися в MCP‑журнали й інспектори.
Найлюдяніша помилка — намагатися налагоджувати інтеграцію лише через ChatGPT UI: «натиснув кнопку, щось не прийшло, розберуся колись». Поки ви не бачите сирі MCP‑повідомлення (requests, replies, notifications), складно зрозуміти, на якому рівні проблема: модель не викликала tool, Apps SDK не дійшов до MCP‑сервера, сервер повернув не той JSON чи все зламалося вже під час відображення віджета. MCP Inspector / Jam і структуроване логування MCP‑повідомлень — ваш найкращий друг. Після того як ви один раз побачите живий tools/call і tools/list у журналах, формат повідомлень MCP остаточно перестане бути «магією» і стане звичною інженерною рутиною.

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