JavaRush/Курси/ChatGPT Apps/Тестування входу й доступу через MCP Jam: None, Bearer, O...

Тестування входу й доступу через MCP Jam: None, Bearer, OAuth with credentials, Default OAuth

Відкрита

1. MCP Jam як лабораторія для авторизації

MCP Jam — це не «ще один дивний інструмент», а ваш лабораторний стенд, який може виконувати роль MCP‑клієнта. По суті, це емулятор поведінки ChatGPT під час роботи з MCP‑сервером: він уміє читати /.well-known/oauth-protected-resource, запускати OAuth‑процес, додавати токени до запитів і показувати, що саме пішло не так.

Є важливий практичний момент: якщо ви успішно пройшли процес Default OAuth у MCP Jam, ви приблизно на 80 % готові до інтеграції з реальним застосунком ChatGPT. Усе, що робить ChatGPT під час привʼязування облікового запису, Jam уже вміє — просто з прозорішими логами та зручними кнопками.

У попередній лекції ми налаштували базову авторизацію для нашого навчального MCP‑сервера GiftGenius: обрали спосіб перевірки токена (JWT або introspection), реалізували /.well-known/oauth-protected-resource і міделвар, який захищає інструменти. Тепер подивімося, як усе це поводиться в MCP Jam у різних режимах авторизації.

Наша мета в цій лекції — навчитися:

  • усвідомлено перемикати режими авторизації в Jam (None, Bearer, OAuth with credentials, Default OAuth);
  • розуміти, що саме Jam надсилає MCP‑серверу в кожному режимі;
  • діагностувати, у якій частині системи стався збій: MCP Server, Auth Server чи метадані;
  • перевірити, що захищені інструменти працюють лише з токеном, а відкриті — і без нього.

2. Наш навчальний MCP‑сервер: що ми тестуємо

Щоб не говорити абстрактно, коротко нагадаємо контекст. Продовжимо історію з нашим навчальним застосунком GiftGenius — це застосунок ChatGPT, який допомагає підбирати подарунки та показує користувачеві його замовлення й списки бажань.

На боці MCP‑сервера в нас уже є:

  • відкритий інструмент, наприклад search_gifts — його можна викликати анонімно;
  • захищений інструмент, наприклад list_user_orders — він має працювати лише для автентифікованого користувача й вимагати scope mcp:tools.

Сервер уміє:

  • публікувати /.well-known/oauth-protected-resource;
  • перевіряти токен (JWT або через introspection — у попередній лекції ви обрали один підхід);
  • витягувати з токена sub (ідентифікатор користувача), scope, aud і передавати їх обробникам інструментів.

Типовий міделвар перевірки токена в Node.js/TypeScript може виглядати так:

// middleware/auth.ts
export function requireScope(requiredScope: string) {
  return async (req: any, res: any, next: () => void) => {
    const header = req.headers["authorization"];
    if (!header?.startsWith("Bearer ")) {
      res
        .status(401)
        .set(
          "WWW-Authenticate",
          `Bearer realm="mcp", resource_metadata="${process.env.BASE_URL}/.well-known/oauth-protected-resource", scope="${requiredScope}"`
        )
        .json({ error: "unauthorized" });
      return;
    }

    // Тут ви вже перевіряєте токен (підпис, exp, aud, scope...)
    // і зберігаєте результат у req.user
    next();
  };
}

Цей міделвар використовуватиметься перед захищеними інструментами MCP. Якщо токена немає, ми повертаємо 401 і коректний WWW-Authenticate із resource_metadata, як того вимагає специфікація MCP Authorization. Докладний розбір перевірки токена та реалізації допоміжних функцій ви вже робили в попередній лекції, тож тут сприймайте це як даність.

3. Режими авторизації в MCP Jam: огляд

У MCP Jam є кілька режимів авторизації для підключення до MCP‑сервера. Вони відповідають типовим OAuth‑патернам: від повної відсутності токена — до повноцінного Authorization Code + PKCE.

Коротко перелічимо:

  1. None (No Auth) — Jam узагалі не додає заголовок Authorization. Це анонімний доступ. Режим підходить для відкритих MCP‑серверів і для перевірки того, що закриті ресурси коректно відмовляють із 401 та WWW-Authenticate.
  2. Bearer Token — Jam додає Authorization: Bearer <токен>, який ви вручну вставляєте в інтерфейсі. Підходить для швидких перевірок: токен уже десь отримано (curl, інтерфейс IdP), а ви хочете подивитися, як поводиться MCP‑ресурс.
  3. OAuth with credentials (Client Credentials) — Jam сам отримує токен через client_credentials на Auth Server, використовуючи задані Client ID і Secret. Це режим «конфіденційного клієнта»: зазвичай про взаємодію «сервер‑сервер», без участі користувача.
  4. Default OAuth (Authorization Code + PKCE) — головний режим для клієнтів на кшталт ChatGPT (public client без секрету). Jam читає resource_metadata, знаходить Auth Server, відкриває браузер із /authorize, проходить PKCE‑процес і отримує користувацький токен.

Для наочності зведімо це в таблицю.

Режим у Jam Що надсилає Jam Хто отримує токен Типовий сценарій
None Немає Authorization Ніхто Анонімні інструменти, перевірка 401
Bearer Token Bearer <ручний> Ви (curl, інтерфейс IdP) Тестування логіки Resource Server
OAuth with cred. Bearer <client token> Jam за client_credentials Сервісні/адмін‑інструменти
Default OAuth Bearer <user token> Jam через Authorization Code + PKCE Користувацький вхід як у ChatGPT

Тепер пройдемося по кожному режиму й подивимося, як проганяти через нього наш GiftGenius MCP‑сервер.

4. Режим None: перевіряємо, що сервер правильно відмовляє

Почнемо з найпростішого режиму: жодної авторизації.

У MCP Jam ви обираєте ваш сервер (наприклад, http://localhost:4000/mcp) і в налаштуваннях з’єднання виставляєте режим авторизації None.

Що при цьому відбувається:

  • Jam встановлює MCP‑з’єднання;
  • під час виклику інструменту він не додає заголовок Authorization;
  • ви можете викликати будь‑які відкриті інструменти (наприклад, search_gifts);
  • під час виклику захищеного інструменту (наприклад, list_user_orders) ваш сервер має відповісти 401 Unauthorized.

Важливо, щоб на цей 401 сервер додав правильний WWW-Authenticate. Ось приклад відповіді з додатковими полями realm і scope, близький до рекомендованого OpenAI та специфікації MCP Authorization:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="mcp",
  resource_metadata="https://giftgenius.example.com/.well-known/oauth-protected-resource",
  scope="mcp:tools"
Content-Type: application/json

{"error": "unauthorized"}

Побачивши таку відповідь, Jam розуміє: ресурс захищено; ось звідки брати метадані (resource_metadata) і які scopes очікуються. У режимі None він просто покаже вам помилку, а в режимі Default OAuth автоматично перейде за вказаним resource_metadata і запустить OAuth‑процес.

З погляду налагодження в режимі None ви перевіряєте:

  • що відкриті інструменти працюють узагалі без токена;
  • що захищені інструменти ніколи не виконуються анонімно;
  • що заголовок WWW-Authenticate відповідає специфікації (містить Bearer і resource_metadata).

На слух це здається тривіальною перевіркою, але чимало проблем починаються саме з того, що 401 повертається без WWW-Authenticate або з некоректним параметром (наприклад, із застарілим resource_metadata_uri замість актуального resource_metadata).

5. Режим Bearer Token: швидкий тест логіки Resource Server

Наступний крок — режим, у якому у вас уже є дійсний токен (отриманий поза Jam), і ви хочете перевірити саме логіку Resource Server: чи правильно він приймає або відхиляє токен, коректно працює зі scope й audience та привʼязує sub до користувача вашого сервісу.

У MCP Jam перемикаєте режим на Bearer Token і вставляєте в поле токена, скажімо:

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...

Тепер Jam додаватиме до кожного MCP‑запиту заголовок:

Authorization: Bearer eyJhbGciOi...

Ваш MCP‑сервер приймає запит, проходить через міделвар requireScope("mcp:tools"), декодує JWT і перевіряє claims. Типовий код перевірки можна (спрощено) написати так:

// auth/verifyToken.ts
import jwt from "jsonwebtoken";

export function verifyToken(header: string) {
  const token = header.replace("Bearer ", "");
  const payload = jwt.verify(token, process.env.JWT_PUBLIC_KEY!);
  // Тут можна перевірити aud, scope тощо.
  return payload as { sub: string; scope?: string };
}

І використати його в міделварі:

// усередині requireScope
const payload = verifyToken(header);
if (!payload.scope?.includes(requiredScope)) {
  res.status(403).json({ error: "insufficient_scope" });
  return;
}
(req as any).user = { id: payload.sub };
next();

У режимі Bearer ви можете експериментувати:

  • вставити токен без потрібного scope й переконатися, що сервер відповідає 403/401;
  • вставити токен із неправильною aud і подивитися, що сервер його відхиляє;
  • вставити прострочений токен, щоб перевірити помилку invalid_token.

Це режим локального «стрес‑тестування» логіки Resource Server без входу через інтерфейс і без PKCE. Усе, що ви перевіряєте тут, потім один в один застосовується до токенів, які ChatGPT або Jam отримуватимуть у режимі Default OAuth.

6. Режим OAuth with credentials (Client Credentials): токен «від імені застосунку»

Тепер — рідкісніший, але корисний для розуміння режим: OAuth with credentials, тобто grant client_credentials. У Jam ви вказуєте:

  • Client ID
  • Client Secret
  • потрібні scopes (наприклад, mcp:tools)

Jam виконує запит до token_endpoint вашого Auth Server приблизно такого вигляду:

POST /oauth2/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&
client_id=<ID>&
client_secret=<SECRET>&
scope=mcp:tools

Auth Server видає токен, у якому sub зазвичай означає самого клієнта (наприклад, sub = "mcp-jam-test-client"), а не конкретного користувача. Далі Jam починає використовувати цей токен як звичайний Bearer.

Для чого це може бути корисно у світі MCP:

  • сервісні/адміністративні інструменти, не привʼязані до конкретного користувача (наприклад, вивантаження логів, health‑check, техпідтримка);
  • перевірка, що MCP‑сервер уміє розрізняти користувацькі токени й токени «клієнта», якщо ваша бізнес‑логіка це враховує.

У контексті застосунків ChatGPT цей режим зазвичай не використовують, тому що ChatGPT як публічний клієнт не зберігає секрети (а public client, за визначенням, не має мати client_secret). Проте в Jam він добре показує різницю між:

  • «Я просто підсунув готовий токен» (режим Bearer);
  • «Jam сам отримав токен за клієнтськими реквізитами» (OAuth with credentials).

На навчальному сервері можна, наприклад, зробити спеціальний MCP‑tool admin_list_all_orders, який доступний лише за токеном із grant_type=client_credentials і відповідною роллю. Це не обовʼязкова частина сьогоднішньої лекції, але корисний експеримент.

7. Режим Default OAuth: повний Authorization Code + PKCE, як у ChatGPT

Тепер — головна «зірка програми»: Default OAuth. Саме цей режим найближчий до того, що робить ChatGPT під час привʼязування облікового запису вашого застосунку. Клієнт читає resource_metadata, переходить на Auth Server, відкриває користувачеві сторінку входу, отримує authorization code і обмінює його на access token за схемою Authorization Code + PKCE S256.

Розберімо послідовність кроків. Для наочності — діаграма.

sequenceDiagram
    participant Jam as MCP Jam (Клієнт)
    participant RS as MCP Server (Ресурс)
    participant PRM as /.well-known/oauth-protected-resource
    participant AS as Auth Server (Keycloak/Auth0)
    
    Jam->>RS: Виклик захищеного інструменту (без токена)
    RS-->>Jam: 401 + WWW-Authenticate (resource_metadata=PRM)
    Jam->>PRM: GET /.well-known/oauth-protected-resource
    PRM-->>Jam: JSON із resource, authorization_servers, scopes_supported...
    Jam->>AS: GET /authorize?client_id=...&code_challenge=...&scope=...
    Note right of AS: Користувач входить і надає згоду
    AS-->>Jam: redirect з authorization_code
    Jam->>AS: POST /token (code + code_verifier)
    AS-->>Jam: { access_token, scope, expires_in, ... }
    Jam->>RS: Виклик інструменту з Authorization: Bearer <access_token>
    RS-->>Jam: Успішний результат інструменту

Що вам важливо перевірити в цьому режимі:

  1. Коректну відповідь 401/WWW-Authenticate від MCP‑сервера. Якщо сервер не віддає resource_metadata або віддає хибний URL, Jam не зможе прочитати PRM і запустити OAuth‑процес.
  2. Валідний документ /.well-known/oauth-protected-resource. У ньому мають бути правильні resource, authorization_servers, scopes_supported тощо, щоб Jam зміг зрозуміти, куди йти по токени і які scopes запитувати.
  3. Правильне налаштування Auth Server.
    • Увімкнено Authorization Code Flow із PKCE S256.
    • Client ID відповідає тому, що очікується в PRM (або реєструється через DCR — Dynamic Client Registration).
    • Redirect URI в Auth Server точно збігається з тим, що використовує Jam.
  4. PKCE S256. Jam формує code_challenge і очікує, що Auth Server підтримує метод S256. Якщо PKCE вимкнено або підтримується лише plain, процес розвалиться.
  5. Scopes і audience. Auth Server має видавати токен із потрібною aud і запитаними scopes (mcp:tools тощо), а MCP‑сервер — перевіряти їх.

У результаті успішного Default OAuth ви отримаєте:

  • у Jam — зʼєднання з MCP‑сервером, у якого захищений інструмент list_user_orders повертає коректні дані саме для того користувача, під яким ви увійшли в Auth Server;
  • у логах Auth Server — успішний authorize + token‑обмін;
  • у логах MCP‑сервера — успішну валідацію токена та витяг sub.

Для налагодження часто допомагає додати простий логер в обробник інструменту, щоб упевнитися, що ви справді бачите userId із токена:

// усередині обробника MCP-інструменту list_user_orders
export async function listUserOrders(args: any, context: any) {
  const user = context.user as { id: string };
  console.log("[MCP] listUserOrders for user", user.id);
  // Далі ви повертаєте замовлення цього користувача
}

8. Де й що ламається: діагностика за режимами

Тепер обговорімо, як за симптомами в MCP Jam зрозуміти, де саме проблема: у MCP‑сервері, в Auth Server чи в метаданих. Цей розділ — свого роду чек‑лист діагностики за режимами.

Якщо в режимі None:

Ви викликаєте захищений інструмент, сервер повертає:

  • 200 OK і виконує дію навіть без токена — отже, перед цим інструментом немає перевірки токена. Потрібно додати міделвар або перевірку scopes.
  • 401, але без WWW-Authenticate або з некоректним resource_metadata — Jam не дізнається, куди йти по метадані, і не зможе запустити Default OAuth. Виправляйте заголовок за прикладом вище.

Якщо в режимі Bearer Token:

  • Jam стабільно отримує 401/403 навіть із токеном, у валідності якого ви впевнені під час прямого виклику (через curl або Postman). Найімовірніше, щось не так у логіці Resource Server: неправильна перевірка aud/scope або не той публічний ключ для підпису JWT.
  • Якщо Bearer‑токен працює в Jam, але потім не працює в Default OAuth, то проблема не в MCP‑сервері, а в Auth Server або PRM: токен, отриманий у Default OAuth, відрізняється за scope/aud від того, який ви тестували вручну.

Якщо в режимі OAuth with credentials:

  • Якщо Jam не може отримати токен (помилка на кроці /token), шукайте причину в налаштуваннях клієнта на Auth Server (неправильний secret, не дозволений client_credentials або заборонений scope).
  • Якщо токен є, але MCP‑сервер його відхиляє, можливо, ваш сервер очікує користувацький sub (email/ID користувача), а в токені є лише ідентифікатор клієнта. Або aud/scope не збігаються з очікуваними.

Якщо в режимі Default OAuth:

Це сценарій із найбільшою кількістю підводних каменів. Типові проблеми:

  • Неправильні redirect URI. Auth Server скаржиться на invalid_redirect_uri або просто не видає код. Переконайтеся, що URI Jam внесено в налаштування клієнта провайдера ідентичності (IdP) без зайвих слешів і помилок.
  • Відсутній або непідтримуваний PKCE. Якщо Auth Server вимагає PKCE, а Jam (або його стара версія) не надсилає code_challenge, або навпаки — Jam надсилає S256, а IdP не підтримує цей метод, ви побачите invalid_request.
  • Невідповідні scopes. У PRM ви заявили mcp:tools, а клієнтові в IdP дозволено лише openid, або навпаки — Jam просить більше scope, ніж IdP готовий видати.
  • Не той audience (aud). Токен видається з aud, відмінною від тієї, яку очікує MCP‑сервер (наприклад, URL іншого ресурсу). Сервер цілком слушно його відхилить.

Дуже важливо навчитися дивитися логи в трьох місцях:

  • MCP Jam — помилки під час розбору PRM і під час HTTP‑запитів до Auth Server;
  • Auth Server — логи /authorize і /token підкажуть, що саме йому не підходить;
  • MCP‑сервер — причини відхилення токена (invalid_token, insufficient_scope, wrong_audience).

9. Як це повʼязано з реальним застосунком ChatGPT

Чому ми витрачаємо стільки часу на Jam, а не йдемо одразу в Developer Mode ChatGPT? Тому що Jam — це саме лабораторний стенд: він дає вам керування режимами авторизації й показує всю «внутрішню кухню» процесу.

Коли ви запускаєте Default OAuth у Jam і доводите його до успіху, ви фактично підтверджуєте:

  • /.well-known/oauth-protected-resource у MCP‑сервера коректний;
  • Auth Server (Keycloak/Auth0/…) налаштований правильно;
  • ролі, scopes, audience і claims відповідають очікуванням;
  • MCP‑сервер уміє перевіряти токен і привʼязувати його до користувача.

ChatGPT, підключений до того самого MCP‑сервера, робитиме те саме: прочитає PRM, піде на Auth Server, отримає токен і почне викликати інструменти із заголовком Authorization: Bearer.

Різниця в тому, що в ChatGPT ви бачите лише фінальний результат («обліковий запис успішно привʼязано» або «щось пішло не так»), а в Jam — бачите весь протокол і можете крок за кроком зрозуміти, де саме проблема.

10. Міні‑практика: послідовне тестування нашого GiftGenius MCP‑сервера

Зберімо все в простий послідовний сценарій, який ви можете повторити у своєму проєкті.

Спочатку запускаєте ваш MCP‑сервер (наприклад, pnpm dev:mcp) і переконуєтеся, що:

  • він слухає на http://localhost:4000/mcp (або на вашому URL);
  • кінцева точка /.well-known/oauth-protected-resource віддає коректний JSON;
  • Auth Server (Keycloak) працює й має налаштований public‑client для Jam/ChatGPT.

Далі:

  1. Режим None.
    Підключаєте Jam до MCP‑сервера без авторизації. Перевіряєте, що:
    • search_gifts відпрацьовує;
    • list_user_orders повертає 401 з правильним WWW-Authenticate.
  2. Режим Bearer Token.
    Отримуєте access token через Keycloak (через інтерфейс або curl). Підставляєте його в Jam, викликаєте list_user_orders і переконуєтеся, що:
    • за дійсного токена інструмент відпрацьовує й повертає замовлення конкретного користувача;
    • за токена без mcp:tools або з іншою aud — сервер повертає помилку.
  3. Режим OAuth with credentials.
    Якщо у вас є конфіденційний клієнт, вказуєте client_id і client_secret у Jam, задаєте потрібний scope, викликаєте технічний інструмент (наприклад, admin_list_all_orders) і перевіряєте, що він працює тільки з таким сервісним токеном.
  4. Режим Default OAuth.
    Вмикаєте Default OAuth і викликаєте list_user_orders. Jam сам:
    • отримає 401 + WWW-Authenticate,
    • прочитає PRM,
    • відкриє браузер, де ви ввійдете до Keycloak,
    • отримає токен через Authorization Code + PKCE,
    • викличе MCP‑інструмент із токеном — і ви побачите свої замовлення у відповіді.

Якщо всі чотири режими відпрацювали так, як очікується, ви не просто «щось там налаштували в Keycloak», а справді розумієте, як перевіряти й налагоджувати весь auth‑процес.

11. Типові помилки під час роботи з MCP Jam і тестування авторизації

На практиці ці проблеми часто проявляються як повторювані патерни. Нижче — кілька типових сценаріїв «як робити не треба», щоб ви могли впізнавати їх за симптомами.

Помилка №1: очікувати, що захищений інструмент працюватиме в режимі None.
Іноді розробник вмикає Jam у режимі None, викликає list_user_orders і дивується 401, а потім «про всяк випадок» прибирає перевірку токена із сервера. У результаті MCP‑інструмент починає працювати анонімно, що для персональних даних і commerce‑сценаріїв категорично неприйнятно. Режим None потрібен саме для того, щоб перевірити: сервер коректно відмовляє без токена й повертає WWW-Authenticate із resource_metadata.

Помилка №2: заголовок WWW-Authenticate пропущено або сформовано неправильно.
Дуже поширений випадок: сервер повертає 401 без WWW-Authenticate або із застарілим параметром resource_metadata_uri. Jam (як і ChatGPT) у такому разі не розуміє, куди йти по Protected Resource Metadata, і Default OAuth просто не стартує. Мінімально достатній варіант — WWW-Authenticate: Bearer resource_metadata="https://.../.well-known/oauth-protected-resource". Поля realm і scope залишаються опційними; головне — не пропустити саме resource_metadata.

Помилка №3: тестувати лише Bearer‑режим і ігнорувати Default OAuth.
Розробник вручну отримує токен, вставляє його в Jam, бачить, що все працює, і вважає завдання виконаним. А коли приходить час підключати реальний ChatGPT, виявляється, що /.well-known некоректний, PKCE не підтримується, redirect URI не збігається — і привʼязування облікового запису падає. Тест Bearer‑режиму потрібен, але цього недостатньо. Default OAuth треба проганяти обовʼязково, інакше ви не перевірите половину ключових налаштувань Auth Server і PRM.

Помилка №4: намагатися використовувати client_credentials там, де потрібен користувацький токен.
Іноді у відчаї розробник вмикає в Jam режим OAuth with credentials і починає отримувати токени за client_credentials, а потім використовувати їх для користувацьких інструментів, на кшталт list_user_orders. У результаті sub у токені — це client_id, а не реальний користувач, і бізнес‑логіка починає поводитися дивно (наприклад, показувати «спільні» дані або падати під час спроби знайти користувача з таким ID). Для ChatGPT‑сценаріїв із реальними користувачами потрібен Authorization Code + PKCE (Default OAuth), а client_credentials підходить лише для сервісних завдань.

Помилка №5: неузгодженість scopes і audience між PRM, Auth Server і MCP‑сервером.
У /.well-known/oauth-protected-resource ви оголосили, що ресурс — https://giftgenius.example.com, а підтримувані scopes — ["mcp:tools"]. В Auth Server клієнтові видали токен без aud, а MCP‑сервер під час перевірки токена очікує строго aud = "https://giftgenius.example.com" і наявність mcp:tools. У результаті токен, отриманий через Default OAuth, MCP‑сервер відхиляє, і ви витрачаєте пів дня на пошук «магії». Завжди перевіряйте, що PRM, конфіг клієнта в IdP і перевірка в міделварі MCP‑сервера узгоджені між собою щодо audience і scope.

Помилка №6: використання застарілої версії MCP Jam.
Специфікація MCP Authorization активно розвивається: зʼявляються нові поля (resource_metadata), поліпшується PKCE‑процес, додаються допоміжні інструменти для налагодження. Якщо у вас стара версія Jam, вона може не розуміти нові поля або працювати із застарілими назвами параметрів. Це призводить до дивних багів: ви все налаштували за актуальними вимогами, а Jam просто не знає, що з цим робити. Перш ніж опускати руки, переконайтеся, що Jam оновлено до актуальної версії.

1
Задача
ChatGPT Apps,  10 рівень4 лекція
Недоступна
Режим Bearer Token — dev-JWT + валідація scope/aud/exp
Режим Bearer Token — dev-JWT + валідація scope/aud/exp
1
Задача
ChatGPT Apps,  10 рівень4 лекція
Недоступна
Default OAuth + OAuth with credentials — два інструменти, два scope, два режими Jam
Default OAuth + OAuth with credentials — два інструменти, два scope, два режими Jam
Коментарі
  • популярні
  • нові
  • старі
Щоб залишити коментар, потрібно ввійти в систему
Для цієї сторінки немає коментарів.