JavaRush /Курсы /ChatGPT Apps /Тестирование логина и доступа через MCP Jam: None, Bearer...

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

ChatGPT Apps
10 уровень , 4 лекция
Открыта

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

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

Очень важный практический момент: если вы добились успешного Default OAuth‑флоу в MCP Jam, вы примерно на 80% готовы к интеграции с реальным ChatGPT App. Всё, что делает 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‑App, который помогает подбирать подарки и показывает пользователю его заказы и вишлисты.

На стороне MCP‑сервера у нас уже есть:

  • открытый инструмент, например search_gifts — его можно вызывать анонимно;
  • защищённый инструмент, например list_user_orders — он должен работать только для аутентифицированного пользователя и требовать scope mcp:tools.

Сервер умеет:

  • публиковать .well-known/oauth-protected-resource;
  • проверять токен (JWT или через introspection — вы выбрали один подход в предыдущей лекции);
  • извлекать из токена sub (user id), 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, Keycloak UI), а вы хотите проверить поведение 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, UI 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 spec:

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 без участия UI‑логина и PKCE. Всё, что вы проверяете здесь, потом один в один применяется к токенам, которые ChatGPT или Jam будут получать в режиме Default OAuth.

6. Режим OAuth with credentials (Client Credentials): токен «от имени приложения»

Теперь — более редкий, но полезный для понимания режим: OAuth with credentials, то есть client_credentials‑grant. В 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 Apps этот режим обычно не используется, потому что 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 при привязке аккаунта вашего App. Клиент читает resource_metadata, идёт к Auth Server, открывает пользователю страницу логина, получает authorization code и меняет его на access token по схеме Authorization Code + PKCE S256.

Разберём последовательность шагов. Для наглядности — диаграмма.

sequenceDiagram
    participant Jam as MCP Jam (Client)
    participant RS as MCP Server (Resource)
    participant PRM as /.well-known/oauth-protected-resource
    participant AS as Auth Server (Keycloak/Auth0)
    
    Jam->>RS: Вызов защищённого tool (без токена)
    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: Вызов tool с 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 App

Почему мы так много времени тратим на игру с 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);
  • endpoint /.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 (через UI или 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, она может не понимать свежие поля или работать с устаревшими именами параметров. Это приводит к сюрреалистическим багам: вы всё настроили по последнему RFC, а 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
1
Опрос
Аутентификация и доступ, 10 уровень, 4 лекция
Недоступен
Аутентификация и доступ
Аутентификация и доступ
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ