Skip to content

fix(test-client): Make OAuth callback address configurable#1166

Open
JoshKappler wants to merge 1 commit into
getsentry:mainfrom
JoshKappler:fix/configurable-oauth-callback-address
Open

fix(test-client): Make OAuth callback address configurable#1166
JoshKappler wants to merge 1 commit into
getsentry:mainfrom
JoshKappler:fix/configurable-oauth-callback-address

Conversation

@JoshKappler

Copy link
Copy Markdown

Fixes #1139.

Problem

The test client hardcodes the OAuth callback to http://localhost:8765/callback (constants.ts) and binds the callback server to 127.0.0.1 (auth/oauth.ts). Running inside a VM or container the browser lives on the host and cannot reach that address, and there is no env var or flag to override the port or the redirect URI, so the OAuth flow cannot complete. SENTRY_ACCESS_TOKEN only helps if you already have a token.

Changes

Adds three variables, resolved once per OAuthClient in a new auth/redirect.ts:

Variable Default Purpose
MCP_OAUTH_PORT 8765 Port the callback server listens on
MCP_OAUTH_HOST 127.0.0.1 Address the callback server binds to
MCP_OAUTH_REDIRECT_URI http://localhost:$MCP_OAUTH_PORT/callback Redirect URI sent to the OAuth server

Resolving once matters: the redirect URI has to be byte identical across client registration, the authorization request, and the token exchange. The bind address and the advertised address are separate because they differ behind port forwarding.

One thing that is not obvious from the issue: the registered redirect URI is now stored alongside the client ID. Dynamic Client Registration binds the URI to the client, and authorize.ts and callback.ts both validate the request against client.redirectUris. Since anyone hitting this bug has already run the CLI once on defaults, the cached client ID would be reused and the new redirect URI rejected, so the fix would not actually work. Clients are now re-registered when the URI changes. Clients registered before this change have no recorded URI and are assumed to use the previous default, so existing setups are untouched. I keyed on the stored URI rather than adding it to the config key because that key also holds the cached access token, and changing it would throw away valid tokens.

Overrides are validated: http or https only, no userinfo, ports 1-65535 (port 0 cannot work here since the browser needs the port before the server binds).

Non-loopback binds log a warning, since that exposes the callback server to the network.

Testing

pnpm run tsc and pnpm run lint pass. pnpm --filter @sentry/mcp-test-client run test is 70 passed, including 20 new tests covering the resolver, the re-registration behavior, and that one redirect URI reaches registration, authorization, and token exchange. I confirmed the re-registration test fails against the old caching behavior.

I also ran a throwaway integration check (not committed, to keep real port binding out of CI) that started the callback server with MCP_OAUTH_HOST=0.0.0.0 and an overridden redirect URI, fetched the callback over HTTP, and confirmed it resolved with the code and state.

@sentry/mcp-agent-cli-test has one failing test on my machine, but it fails the same way on a clean checkout of main (a Windows path issue), so it is unrelated to this change.

Per AGENTS.md, this was written with AI assistance and the commit carries the required Co-Authored-By attribution.

The CLI hardcoded the OAuth callback to http://localhost:8765/callback and
bound the callback server to 127.0.0.1. Inside a VM or container the browser
runs on the host and cannot reach that address, and there was no way to
override either the port or the redirect URI, so the OAuth flow could not
complete. SENTRY_ACCESS_TOKEN only helps if you already have a token.

Adds MCP_OAUTH_PORT, MCP_OAUTH_HOST, and MCP_OAUTH_REDIRECT_URI, resolved
once per client so the redirect URI stays byte identical across registration,
authorization, and token exchange. The bind address and the advertised
address are resolved separately because they differ behind port forwarding.

The registered redirect URI is now recorded with the client ID. Dynamic
Client Registration binds the URI to the client, so reusing a cached client
after changing the redirect URI fails validation server side. Clients are
re-registered when it changes; clients registered before this are assumed to
use the previous default and are left alone.

Overrides are validated: http or https only, no userinfo, and ports 1-65535
since the browser needs the port before the server binds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OAuth callback hardcoded to localhost breaks authentication in VMs

1 participant