Интеграция OAuth 2.1 для MCP сама по себе нетривиальна. Клиент должен найти authorization server, зарегистрироваться или предъявить известный серверу client_id, подготовить PKCE, открыть браузер, провести пользователя через login и consent, а затем получить управление обратно через redirect.
Тестировать этот flow ещё сложнее. Большую часть протокола можно пройти через HttpClient: отправить запросы, разобрать HTML, проверить cookies, заголовки и параметры redirect. Но такой тест не отвечает на один важный вопрос: разрешит ли настоящий браузер переход с consent page на callback локального MCP-клиента?
У нас этот переход защищён Content Security Policy. Login-форма может отправлять данные только на собственный origin. Consent-форма дополнительно получает точный origin проверенного callback. Ошибка в form-action может остаться незаметной для HTTP-теста: строка в заголовке выглядит правильно, сервер возвращает 302, но браузер блокирует отправку формы.
Поэтому мы добавили отдельный Playwright-тест для этой границы.
Локальный клиент внутри теста
Desktop-приложения и CLI обычно принимают OAuth callback через временный HTTP listener на loopback-интерфейсе. Тест делает то же самое.
Сначала он запускает TcpListener на 127.0.0.1 и передаёт порт 0. Операционная система выбирает свободный порт, поэтому параллельные прогоны не спорят за заранее заданный адрес. Listener начинает ждать запрос ещё до регистрации OAuth-клиента.
Полученный адрес вида http://127.0.0.1:49152/callback тест передаёт в Dynamic Client Registration. Клиент регистрируется как native public client без секрета. Затем тест генерирует случайные code_verifier и state, вычисляет PKCE challenge методом S256 и собирает authorization request со scope mcp:tools и нужным resource.
После этого управление получает Playwright. Браузер открывает настоящий HTTPS endpoint авторизации CodeAlive, вводит email и пароль, проходит login и нажимает кнопку consent. Если тестовые credentials не заданы, сценарий сначала создаёт пользователя через обычную форму регистрации и подтверждает его email.
Callback должен получить браузерный запрос
Главная проверка происходит не по заголовку Location. Тест ждёт, пока запрос действительно придёт в TcpListener:
Listener разбирает реальный GET /callback?..., отвечает короткой страницей Authentication complete и возвращает URI тесту. Мы проверяем три вещи: authorization code не пуст, state не изменился, а iss точно совпадает с нашим authorization server. Таймаут превращает заблокированный CSP redirect в понятный сбой теста, а не в зависший CI job.
Этот browser test намеренно не обменивает code на access token. Полный DCR → authorization code → PKCE token exchange → refresh rotation → revocation уже проходит через HTTP integration suite. Там же мы отдельно проверяем точный состав CSP: только 'self' на login и 'self' плюс callback origin на consent.
Playwright-тест добавляет одну проверку, которую HTTP-клиент выполнить не может: браузер принял нашу политику и доставил authorization response настоящему loopback listener. Именно на этой границе нам и нужен настоящий браузер.
Спецификация: Authorization в Model Context Protocol.