JavaRush /Курси /ChatGPT Apps /Архітектура авторизації MCP: MCP Client, MCP Server, MCP ...

Архітектура авторизації MCP: MCP Client, MCP Server, MCP Auth Server

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

1. Про що ця лекція і чого в ній немає

У цій лекції ми:

  • складаємо цілісну картину «трикутника довіри» між MCP Client, MCP Server і MCP Auth Server — разом із користувачем, який стоїть ніби «над» цим трикутником як власник ресурсів;
  • обговорюємо потік: хто кому надсилає токен, де користувач входить у систему і чому MCP‑сервер ніколи не бачить його пароля;
  • повʼязуємо це з нашим бекендом Next.js/MCP і майбутнім налаштуванням Keycloak/Auth0.

Чого ми не робимо сьогодні:

  • не «натискаємо галочки» в Keycloak і не налаштовуємо конкретний IdP;
  • не пишемо повноцінну перевірку JWT або інтроспекцію — це теми наступних лекцій (про Auth Server і про MCP Server як захищений ресурс).

Ваше завдання на сьогодні — щоб ви могли взяти аркуш, намалювати стрілки між ChatGPT, вашим сервером і Auth0/Keycloak та без запинки пояснити: де вхід, де токен, де дані.

2. Трикутник довіри: MCP Client, MCP Server, MCP Auth Server

Почнемо з дійових осіб. Технічний «трикутник довіри» утворюють MCP Client, MCP Server і MCP Auth Server. Користувач (User) — окрема роль: власник ресурсів, який стоїть ніби над цим трикутником і надає згоду на доступ. У специфікації MCP і Apps SDK цю архітектуру формалізовано доволі чітко.

User (Resource Owner)

Це людина по той бік екрана. Вона:

  • входить у ChatGPT;
  • пише запит: «покажи мої замовлення / мої списки подарунків»;
  • погоджується привʼязати обліковий запис вашого сервісу до ChatGPT.

Головне: саме вона володіє ресурсами (історією замовлень, профілями, списками подарунків) і саме вона надає згоду на доступ до них.

MCP Client

У нашому контексті це:

  • ChatGPT з Apps SDK;
  • іноді — MCP Jam Inspector (під час налагодження).

MCP Client уміє:

  • читати метадані вашого MCP‑сервера (через .well-known);
  • запускати OAuth‑потік у браузері користувача;
  • зберігати й додавати токени до викликів MCP‑інструментів.

Важливо памʼятати, що MCP Client — public client, тобто публічний клієнт. Він не зберігає ваш client_secret, тому спілкується з Auth Server-ом як публічний SPA‑застосунок: Authorization Code + PKCE.

MCP Server (Resource Server)

Це ваш бекенд, що реалізує MCP:

  • встановлює зʼєднання з ChatGPT;
  • оголошує інструменти (tools), ресурси, промпти;
  • на кожен виклик інструмента дивиться на заголовок Authorization: Bearer <token>;
  • перевіряє токен (підпис, exp, aud, scope) і, якщо все гаразд, виконує бізнес‑логіку.

Принциповий момент: MCP‑сервер не займається входом користувача. Він не бачить паролів, не показує форму входу, не надсилає лист «підтвердьте електронну пошту». Він довіряє лише криптографічно підписаним токенам від Auth Server-а.

MCP Auth Server (Authorization Server / IdP)

Це окремий сервіс автентифікації та авторизації: Keycloak, Auth0, Ory Hydra+Kratos, Okta, Cognito, Azure AD тощо.

Він відповідає за:

  • інтерфейс входу (електронна пошта/пароль, SSO, 2FA);
  • зберігання облікових записів користувачів;
  • видачу токенів (access token, refresh token);
  • публікацію OAuth/OIDC‑метаданих (/authorize, /token, jwks_uri, /registration тощо).

Для MCP він має підтримувати OAuth 2.1 для public clients (PKCE S256, dynamic client registration тощо).

Зведена табличка ролей

Хто Що робить Чого не робить
User Вводить логін і пароль, надає згоду на доступ до даних Не спілкується безпосередньо із MCP Server
MCP Client (ChatGPT/Jam) Ініціює OAuth, зберігає токен, викликає MCP tools Не перевіряє паролі, не перевіряє підпис токена
MCP Server Перевіряє токени, виконує бізнес‑логіку tools Не показує форму входу, не зберігає паролі
MCP Auth Server Автентифікує користувача, видає токени Не знає про ваші MCP‑інструменти та їхню бізнес‑логіку

Якщо у вас у голові все це змішувалося в один «великий сервер, який робить усе», — час розділити ролі.

3. Як виглядає flow: від «немає токена» до захищеного виклику інструментів

Тепер подивімося на потік повідомлень. У специфікації MCP цей процес називають «The Flow»: discovery → redirect → code → token → authorized calls.

Крок 0. Спроба викликати захищений інструмент без токена

Користувач пише: «Покажи мої збережені ідеї для подарунків».

ChatGPT як MCP Client вирішує: «для цього потрібно викликати інструмент getUserGiftLists на нашому MCP‑сервері». Він робить виклик без токена (бо користувач ще не входив у систему).

Ваш MCP‑сервер:

  • бачить відсутній або некоректний Authorization‑заголовок;
  • відповідає 401 Unauthorized і додає заголовок WWW-Authenticate: Bearer resource_metadata="https://api.giftgenius.com/.well-known/oauth-protected-resource" з посиланням на метадані захищеного ресурсу (resource metadata; про це — нижче).

Приблизно так (лише логіка, не повний HTTP):

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://api.giftgenius.com/.well-known/oauth-protected-resource"

ChatGPT бачить цей заголовок і розуміє: «ага, ресурс захищено OAuth. Потрібно виконати OAuth‑потік і привʼязати обліковий запис».

Discovery: .well-known/oauth-protected-resource

Далі MCP Client запитує у вашого сервера метадані:

GET /.well-known/oauth-protected-resource

Сервер відповідає JSON‑документом з ідентифікатором ресурсу та списком авторизаційних серверів, у яких потрібно отримувати токени.

Мінімальний приклад (детально налаштуємо пізніше; зараз важлива сама ідея):

{
  "resource": "https://api.giftgenius.com",
  "authorization_servers": [
    "https://auth.giftgenius.com"
  ],
  "scopes_supported": ["gifts.read", "gifts.write"]
}

Тут:

  • resource — канонічний ID вашого ресурсу; його потім мають використовувати як audience або resource під час видачі токена;
  • authorization_servers — список Auth Server-ів, у яких ChatGPT може запитати токен;
  • scopes_supported — які «права доступу» ваш MCP‑сервер узагалі розуміє.

Authorization Request: переспрямування на Auth Server

Отримавши метадані, MCP Client переходить на Auth Server. Він відкриває в браузері сторінку:

GET https://auth.giftgenius.com/authorize
    ?response_type=code
    &client_id=chatgpt-giftgenius
    &redirect_uri=... (зворотний URL MCP Clientʼа)
    &code_challenge=...
    &code_challenge_method=S256
    &scope=openid gifts.read
    &resource=https://api.giftgenius.com

Користувач:

  • бачить знайомий екран входу (наприклад, Keycloak або Auth0);
  • вводить логін і пароль, проходить 2FA;
  • підтверджує, що ChatGPT може читати його подарункові списки (scope gifts.read).

Code → Token: обмін коду на токен з PKCE

Після успішного входу Auth Server переспрямовує користувача назад до MCP Clientʼа з code. MCP Client:

  • робить POST на /token;
  • передає code і code_verifier (який відповідає code_challenge з кроку вище).

Auth Server перевіряє PKCE: хешує code_verifier і порівнює його з початковим code_challenge. Якщо все гаразд і клієнт справді той самий, хто починав потік, тоді він:

  • видає короткочасний access_token (зазвичай JWT);
  • у ньому вказує:
    • sub — ID користувача в Auth Server-і;
    • aud або resource — ваш MCP‑сервер;
    • scope — дозволені дії (gifts.read, openid тощо).

Authenticated Request: виклик MCP‑інструмента з токеном

Тепер MCP Client готовий знову викликати ваш інструмент — уже із заголовком:

Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...

MCP‑сервер:

  • перевіряє підпис токена (за JWK Auth Server-а) або через introspection;
  • перевіряє строк чинності (exp);
  • перевіряє aud / resource — що токен справді видано для https://api.giftgenius.com;
  • дивиться на scope і вирішує, чи можна викликати getUserGiftLists.

Після цього він уже спокійно звертається до вашої БД за конкретним userId і повертає персональні списки подарунків.

Зверніть увагу: до цього моменту ми говорили лише про мережевий потік — як токен отримується і доходить до MCP‑сервера. Далі важливо зрозуміти, як із sub та інших claims у токені виходить конкретний userId у вашій БД. Саме тут у дію вступає identity bridge.

4. Identity Bridge: як user ChatGPT перетворюється на userId у вашій БД

Найцікавіший фрагмент архітектури — це «міст ідентичності» (identity bridge). У специфікації MCP прямо підкреслюється: MCP‑сервер не знає про користувачів ChatGPT, він спирається на дані в токені від Auth Server-а.

Схема приблизно така:

flowchart TD
  User[User у ChatGPT] -->|Login/SSO| Auth[Auth Server]
  Auth -->|JWT: sub, email, tenant| MCP[MCP Server]
  MCP -->|userId/tenantId| DB[(Ваша БД)]

Покроково це виглядає так.

По‑перше, Auth Server на своєму боці знає своїх користувачів: у нього є сутності user, email, id, можливо, tenant, roles. Після успішного входу він додає цю інформацію в токен (у claims):

{
  "sub": "auth0|abc123",
  "email": "user@example.com",
  "given_name": "Alice",
  "https://giftgenius.com/tenant": "tenant-42",
  "scope": "openid gifts.read",
  "aud": "https://api.giftgenius.com"
}

По‑друге, MCP Server під час перевірки токена дістає ці claims і визначає, хто це «в його світі». Наприклад:

  • якщо sub уже є в таблиці User.authProviderId — беремо повʼязаний userId;
  • якщо ні — створюємо локальний запис (динамічне створення, on‑the‑fly provisioning) і повʼязуємо.

Типовий фрагмент TypeScript‑коду на стороні MCP‑сервера (спрощений, без перевірки підпису) може виглядати так:

type TokenClaims = {
  sub: string;
  email?: string;
  scope?: string;
};

async function mapClaimsToUserId(claims: TokenClaims): Promise<string> {
  const user = await db.user.findUnique({ where: { authSub: claims.sub } });
  if (user) return user.id;

  const created = await db.user.create({
    data: { authSub: claims.sub, email: claims.email ?? null }
  });
  return created.id;
}

По‑третє, уже знаючи userId, MCP‑сервер дістає все, що потрібно: списки подарунків, історію замовлень, налаштування, тариф.

Отже, Auth Server стає «мостом» між зовнішнім світом (ChatGPT, Google, SSO) і вашим внутрішнім світом (customer_id у БД замовлень).

5. Чому потрібно розділяти Auth Server і MCP Server

Може виникнути спокуса: «А давайте мій MCP‑сервер сам і екран входу покаже, і токен видасть». Формально це можливо (ви можете вбудувати в нього міні‑IdP), але з погляду архітектури це погана ідея. Причини тут досить практичні.

По‑перше, безпека й масштабованість. Auth Server — це «важкий механізм»: 2FA, social login, політики паролів, блокування облікового запису, відновлення доступу, аудит входів, можливо, сертифікації. Писати це заново в кожному мікросервісі (і в кожному MCP‑сервері) — прямий шлях до хаосу та проблем із відповідністю вимогам на кшталт PCI‑DSS. Значно простіше делегувати це Keycloak/Auth0 і просто перевіряти їхній токен.

По‑друге, різноманіття клієнтів. Сьогодні у вас лише ChatGPT. Завтра ви підʼєднаєте Claude Desktop, свій вебфронтенд на Next.js або мобільний застосунок. Усі вони можуть використовувати один і той самий Auth Server і ту саму схему OAuth 2.1, а ваш MCP‑сервер просто продовжить перевіряти токени. Вам не доведеться переписувати бізнес‑логіку під кожного нового клієнта.

По‑третє, чистота коду. MCP Server в ідеалі:

  • уміє публікувати /.well-known/oauth-protected-resource;
  • уміє перевіряти Bearer-токен і діставати з нього userId, scopes, tenant;
  • реалізує бізнес‑інструменти (orders, gifts, profiles).

Уся UI‑логіка входу — форми, верстка, соціальні логіни — живе в Auth Server-і й не захаращує бекенд.

6. Як це виглядає в нашому навчальному застосунку GiftGenius

Повернімося до застосунку, який ми розробляємо протягом курсу. Припустімо, у нас є:

  • ChatGPT App «GiftGenius» із віджетом (Apps SDK), який уміє підбирати подарунки;
  • MCP‑сервер на Node/Next.js, який надає інструменти:
    • searchGifts — анонімний, не потребує входу;
    • getSavedGiftLists — персональний, потребує автентифікації;
  • Auth Server (пізніше — Keycloak/Auth0), де кожен користувач має обліковий запис.

Сценарій для анонімного та автентифікованого користувача

Якщо користувач просто пише «підбери подарунок для брата, 30 років, любить настолки», наш застосунок може:

  • викликати анонімний інструмент searchGifts;
  • показати в інтерфейсі рекомендації.

У цьому разі:

  • токен не потрібен;
  • MCP‑сервер просто виконує запит (наприклад, до вашого каталогу або стороннього API).

Щойно користувач каже «збережи це в мої списки» або «покажи мої збережені ідеї», модель вирішує викликати захищений інструмент getSavedGiftLists. Сервер відповідає 401 + WWW-Authenticate з resource_metadata. ChatGPT запускає OAuth‑майстер «Link GiftGenius account», проводить користувача через вхід і отримує токен.

Далі, під час кожного захищеного виклику:

  • MCP Server уже бачить Authorization: Bearer ...;
  • дістає userId із токена;
  • фільтрує дані за цим userId.

Саме завдяки цьому ми можемо:

  • розділяти дані різних користувачів;
  • безпечніше показувати історію замовлень і список обраного;
  • будувати commerce‑функції (пізніше в курсі).

Архітектура бекенду: middleware + обробники інструментів

На практиці в коді Node/Next.js це часто виглядає як ланцюжок: «middleware автентифікації → бізнес‑обробник інструмента». У лекції про реалізацію tool‑обробників ми вже підкреслювали, що в них потрібно передавати контекст: user_id, токени, налаштування.

Фрагмент коду може бути таким:

// auth-context.ts
export type AuthContext = {
  userId: string | null;    // null для анонімних викликів
  scopes: string[];
};

Middleware, який вішається на всі MCP‑ендпойнти:

// mcp-auth-middleware.ts
export async function buildAuthContext(req: Request): Promise<AuthContext> {
  const header = req.headers.authorization || "";
  const token = header.replace(/^Bearer\s+/i, "");

  if (!token) return { userId: null, scopes: [] }; // анонімний користувач

  const claims = await verifyAndDecodeToken(token); // перевірка та декодування токена
  const userId = await mapClaimsToUserId(claims);
  const scopes = (claims.scope || "").split(" ");
  return { userId, scopes };
}

А сам обробник інструмента отримує цей контекст:

// tools/getSavedGiftLists.ts
export async function getSavedGiftLists(_args: {}, ctx: AuthContext) {
  if (!ctx.userId) throw new Error("User must be authenticated");

  return db.giftList.findMany({
    where: { ownerId: ctx.userId }
  });
}

Суть у тому, що tool‑обробник не знає нічого ні про OAuth, ні про PKCE. Він просто працює з «очевидним» userId. Уся OAuth‑магія від нього схована — у MCP‑клієнті та в Auth‑middleware.

7. Візуальні схеми: як разом працюють Client, Server і Auth

Ми вже покроково розібрали flow у розділі 3 — текстом. Іноді простіше один раз намалювати, ніж сім разів пояснювати, тож зараз покажемо ті самі взаємодії у вигляді двох діаграм.

Скелет взаємодії (The Triangle of Trust)

flowchart TD
  U[User] -->|1. Login / Consent| A[MCP Auth Server]
  U -->|2. Спілкується| C["MCP Client (ChatGPT)"]
  C -->|3. OAuth Flow| A
  C -->|4. Bearer Token| S[MCP Server]
  S -->|5. Data| C

Схема читається так.

Спочатку користувач входить через Auth Server, який, по суті, підтверджує його особу й видає токен. MCP Client керує цим процесом, а потім використовує токен, щоб звертатися до MCP‑сервера. MCP‑сервер не бачить логіна й пароля: він бачить лише токен і вирішує, що дозволено.

Потік від запиту до відповіді

sequenceDiagram
  participant User
  participant ChatGPT as MCP Client
  participant Auth as Auth Server
  participant MCP as MCP Server

  User->>ChatGPT: "Покажи мої списки подарунків"
  ChatGPT->>MCP: callTool(getSavedGiftLists) (без токена)
  MCP-->>ChatGPT: 401 + WWW-Authenticate (resource_metadata)
  ChatGPT->>Auth: /authorize + PKCE
  User->>Auth: Вводить логін/пароль, дає consent
  Auth-->>ChatGPT: redirect + code
  ChatGPT->>Auth: /token + code_verifier
  Auth-->>ChatGPT: access_token (JWT)
  ChatGPT->>MCP: callTool(getSavedGiftLists) + Authorization: Bearer ...
  MCP-->>ChatGPT: JSON з особистими списками
  ChatGPT-->>User: Відображений список у віджеті

Ця діаграма — те, що ви маєте вміти «розповісти із заплющеними очима» наприкінці модуля.

8. Трішки глибше: кілька ресурсів, кілька клієнтів, DCR

Перевага такої архітектури в тому, що вона добре масштабується.

По‑перше, у вас може бути кілька MCP‑серверів (наприклад, один — про подарунки, інший — про замовлення) і один Auth Server, який видає токени з різними aud/resource. Кожен ресурсний сервер зобовʼязаний перевіряти, що токен справді призначено саме для нього. Інакше виникне класична проблема «confused deputy», коли токен для одного сервісу приймається іншим.

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

  • ChatGPT App;
  • ваш власний фронтенд;
  • мобільний застосунок;
  • інтеграція партнера через MCP Gateway.

Усі вони будуть:

  • читати /.well-known/oauth-protected-resource;
  • дізнаватися, де розташований Auth Server;
  • проходити OAuth 2.1 flow;
  • отримувати токени й викликати MCP‑сервер.

По‑третє, сучасні Auth Server-и дедалі частіше підтримують Dynamic Client Registration (DCR) — можливість динамічно реєструвати клієнтів через API. MCP‑специфікація якраз і передбачає таку можливість: клієнт (ChatGPT/Jam) може автоматично реєструвати себе на Auth Server-і за його registration_endpoint.

У цьому модулі нам важливо розуміти, що:

  • MCP Client, MCP Server і Auth Server спілкуються через стандартизовані discovery‑документи й токени;
  • вам не потрібно «жорстко задавати» всіх клієнтів у коді бекенду;
  • ви можете розбудовувати екосистему, не ламаючи наявну модель авторизації.

9. Типові помилки в розумінні архітектури авторизації MCP

Помилка № 1: «MCP‑сервер має сам автентифікувати користувача».
Іноді розробники намагаються вбудувати форму входу прямо в MCP‑сервер, а потім передавати логін і пароль через інструменти. Це ламає саму ідею OAuth. MCP‑сервер не має бачити пароль за жодних обставин. Вхід і згода — зона відповідальності Auth Server-а. MCP‑сервер працює лише з токенами та їхніми claims.

Помилка № 2: Плутанина між MCP Client і MCP Server.
Буває, що ChatGPT сприймають як «частину мого бекенду» і намагаються, наприклад, зберігати в ньому якісь секрети або очікувати, що він сам перевірить права доступу. Насправді MCP Client лише ініціює OAuth і додає токени. Перевірка токена і прав — завдання MCP‑сервера, а не ChatGPT.

Помилка № 3: «API‑ключ у .env замість OAuth».
Класичний антипатерн: зробити один великий SERVICE_API_KEY, покласти його в .env MCP‑сервера й вважати, що проблему вирішено. У такому варіанті немає розмежування прав між користувачами, не можна безпечно показувати персональні дані або виконувати покупки. Усе робиться «від імені сервісу», а не від імені користувача. Це повністю суперечить цілям авторизації в ChatGPT Apps.

Помилка № 4: Ігнорування audience і resource.
Якщо MCP‑сервер приймає будь‑який валідний JWT із коректним підписом і не дивиться на aud/resource, тоді будь‑який токен, виданий для іншого сервісу тим самим Auth Server-ом, може бути використаний для виклику ваших інструментів. Це пряме порушення моделі безпеки OAuth. Сервер зобовʼязаний перевіряти, що токен видано саме для його resource.

Помилка № 5: Змішування auth‑логіки та бізнес‑логіки.
Іноді в tool‑обробники починають тягнути весь розбір токена, перевірку підпису, роботу з JWK тощо. У підсумку код стає крихким і важким у супроводі. Значно правильніше відокремити шар «перевірка токена, мапінг на userId» (middleware) від шару «власне логіка інструмента», який отримує вже зрозумілий AuthContext.

Помилка № 6: Очікування, що ChatGPT «сам усе зробить» без .well-known.
Без правильного ендпойнта /.well-known/oauth-protected-resource MCP‑клієнт просто не знає, де розташований ваш Auth Server і які scopes потрібні. Результат — чат мовчки «не вміє входити», а розробник довго дивиться в порожні журнали. Правильний шлях такий: MCP‑сервер чітко оголошує свої вимоги до авторизації через .well-known, клієнт читає їх і будує потік.

Помилка № 7: Забутий користувач у бізнес‑логіці.
Іноді, навіть правильно налаштувавши OAuth і мапінг токена на userId, розробники не використовують це в запитах до БД. Наприклад, забувають фільтрувати за ownerId = userId. Тоді будь‑який автентифікований користувач може побачити чужі дані. Наявність токена — це лише перший крок; другим кроком завжди є коректне використання userId і scope у бізнес‑коді.

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