Integrating OAuth 2.1 for MCP is not a small task. A client has to discover the authorization server, register or present a known client_id, prepare PKCE, open a browser, take the user through login and consent, and receive control again through a redirect.
Testing that flow is harder still. We can cover most of the protocol with HttpClient: send requests, parse HTML, inspect cookies and headers, and follow redirect parameters. That leaves one important question unanswered. Will a real browser allow the consent page to send the authorization response to a local MCP client?
Content Security Policy protects this transition in CodeAlive. The login form may submit only to its own origin. The consent form also allows the exact origin of the validated callback. A mistake in form-action can pass an HTTP test: the header looks right and the server returns a 302, while the browser refuses to submit the form.
We added a focused Playwright test for this boundary.
Running a local client inside the test
Desktop applications and CLI tools commonly receive OAuth callbacks through a temporary HTTP listener on the loopback interface. Our test behaves the same way.
It first binds a TcpListener to 127.0.0.1 with port 0. The operating system selects a free port, so parallel test runs do not compete for a fixed address. The listener starts waiting before the OAuth client is registered.
The test sends the resulting address, such as http://127.0.0.1:49152/callback, to Dynamic Client Registration. It registers a native public client without a secret. It then generates random code_verifier and state values, calculates the S256 PKCE challenge, and builds an authorization request with the mcp:tools scope and the expected resource.
Playwright takes over from there. The browser opens the real CodeAlive HTTPS authorization endpoint, enters the email and password, completes login, and approves consent. When test credentials are not configured, the scenario first creates a user through the regular signup form and confirms the email.
Waiting for the browser, not a Location header
The main assertion does not inspect a Location header. The test waits for a request to arrive at the TcpListener:
The listener parses the real GET /callback?..., returns a small Authentication complete page, and gives the URI back to the test. We verify that the authorization code is present, state is unchanged, and iss exactly matches our authorization server. The timeout turns a CSP-blocked redirect into a clear test failure instead of a stuck CI job.
The browser test deliberately stops before exchanging the code for an access token. Our HTTP integration suite already covers the complete DCR → authorization code → PKCE token exchange → refresh rotation → revocation flow. It also checks the exact CSP sources: only 'self' during login, then 'self' plus the callback origin during consent.
The Playwright test adds the one assertion an HTTP client cannot make: the browser accepted our policy and delivered the authorization response to a real loopback listener. That is the boundary where a real browser earns its place in the test suite.
Specification: Authorization in the Model Context Protocol.