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.
Коротко перелічимо:
- None (No Auth) — Jam узагалі не додає заголовок Authorization. Це анонімний доступ. Режим підходить для відкритих MCP‑серверів і для перевірки того, що закриті ресурси коректно відмовляють із 401 та WWW-Authenticate.
- Bearer Token — Jam додає Authorization: Bearer <токен>, який ви вручну вставляєте в інтерфейсі. Підходить для швидких перевірок: токен уже десь отримано (curl, інтерфейс IdP), а ви хочете подивитися, як поводиться MCP‑ресурс.
- OAuth with credentials (Client Credentials) — Jam сам отримує токен через client_credentials на Auth Server, використовуючи задані Client ID і Secret. Це режим «конфіденційного клієнта»: зазвичай про взаємодію «сервер‑сервер», без участі користувача.
- 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: Успішний результат інструменту
Що вам важливо перевірити в цьому режимі:
- Коректну відповідь 401/WWW-Authenticate від MCP‑сервера. Якщо сервер не віддає resource_metadata або віддає хибний URL, Jam не зможе прочитати PRM і запустити OAuth‑процес.
- Валідний документ /.well-known/oauth-protected-resource. У ньому мають бути правильні resource, authorization_servers, scopes_supported тощо, щоб Jam зміг зрозуміти, куди йти по токени і які scopes запитувати.
- Правильне налаштування Auth Server.
- Увімкнено Authorization Code Flow із PKCE S256.
- Client ID відповідає тому, що очікується в PRM (або реєструється через DCR — Dynamic Client Registration).
- Redirect URI в Auth Server точно збігається з тим, що використовує Jam.
- PKCE S256. Jam формує code_challenge і очікує, що Auth Server підтримує метод S256. Якщо PKCE вимкнено або підтримується лише plain, процес розвалиться.
- 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.
Далі:
- Режим None.
Підключаєте Jam до MCP‑сервера без авторизації. Перевіряєте, що:- search_gifts відпрацьовує;
- list_user_orders повертає 401 з правильним WWW-Authenticate.
- Режим Bearer Token.
Отримуєте access token через Keycloak (через інтерфейс або curl). Підставляєте його в Jam, викликаєте list_user_orders і переконуєтеся, що:- за дійсного токена інструмент відпрацьовує й повертає замовлення конкретного користувача;
- за токена без mcp:tools або з іншою aud — сервер повертає помилку.
- Режим OAuth with credentials.
Якщо у вас є конфіденційний клієнт, вказуєте client_id і client_secret у Jam, задаєте потрібний scope, викликаєте технічний інструмент (наприклад, admin_list_all_orders) і перевіряєте, що він працює тільки з таким сервісним токеном. - Режим 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 оновлено до актуальної версії.