Перейти к основному содержимому
CodeAlive 3.0: от context engine к агенту для исследования кода
CodeAlive

Ко всем статьям

Как мы автоматизировали тестирование MCP OAuth 2.1

Интеграция 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:

csharp
using TcpListener listener = new(IPAddress.Loopback, 0);listener.Start(1);
int port = ((IPEndPoint)listener.LocalEndpoint).Port;string callbackUrl = $"http://127.0.0.1:{port}/callback";Task<Uri> receivedCallback = ReceiveCallbackAsync(listener, cancellationToken);
// Playwright completes login and consent in the browser.Uri callback = await receivedCallback.WaitAsync(TimeSpan.FromSeconds(30));Dictionary<string, string> parameters = ParseQuery(callback.Query);
Assert.That(parameters["code"], Is.Not.Empty);Assert.That(parameters["state"], Is.EqualTo(state));Assert.That(parameters["iss"], Is.EqualTo(oauthIssuer));

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.

Model Context Protocol (MCP)

Дайте агентам всю кодовую базу

Проиндексируйте первый репозиторий за минуты — или попробуйте демо без регистрации.