Skip to content

mcp: authorize at most once when Connect falls back to initialize - #1467

Closed
guglielmo-san wants to merge 1 commit into
mainfrom
guglielmoc/dont_fall_back_initialize
Closed

guglielmo-san wants to merge 1 commit into
mainfrom
guglielmoc/dont_fall_back_initialize

Conversation

@guglielmo-san

Copy link
Copy Markdown
Contributor

With an OAuthHandler, Client.Connect sends server/discover first and falls back to initialize on any non-modern error. When server/discover stayed unauthorized, the fallback initialize got the same 401 and ran OAuthHandler.Authorize a second time. With auth.AuthorizationCodeHandler, that is a second full authorization flow and consent prompt for a single Connect.

This follows the approach discussed on #1423:

  • Authorize returns an error during server/discover (e.g. the user declines consent): Connect returns that error instead of falling back, since the fallback would only run the authorization flow again. The transport wraps Authorize errors with an unexported sentinel so Connect can tell them apart from a legacy server.
  • Authorize succeeds but server/discover still fails, including with 401/403: Connect still falls back to initialize, which carries the credentials just acquired, since a server may authorize per method. Connect shares a flag across its handshake requests through the context, and the transport doesn't call Authorize again once it has succeeded during the handshake.

Unlike #1425, a server that rejects server/discover with 403 but allows initialize still connects.

Tests: TestStreamableClientOAuth_AuthorizeOncePerConnect runs a real server behind bearer authorization and checks Authorize runs exactly once in four cases: Authorize fails (no initialize sent, the error wraps the handler's error), the granted token is rejected (initialize tried with it, no second Authorize), server/discover forbidden but initialize allowed (connects), and a legacy server (connects). The first two fail on main. go test -race ./... and staticcheck pass. The client conformance suite (--suite all, latest release) gives the same failing scenarios as main; the auth/resource-mismatch and auth/2025-03-26-oauth-metadata-backcompat scenarios no longer repeat the authorization flow after server/discover.

AI assistance was used to prepare this change.

Fixes #1423

With an OAuthHandler, Client.Connect sends server/discover first and
falls back to initialize on any non-modern error. When server/discover
stayed unauthorized, the fallback initialize got the same 401 and ran
OAuthHandler.Authorize a second time: a second consent prompt for one
Connect.

Following the approach agreed on the issue:

- If Authorize returns an error during server/discover, Connect returns
  that error instead of falling back, since the fallback would only run
  the authorization flow again.
- If Authorize succeeds but server/discover still fails, including with
  401 or 403, Connect still falls back to initialize, which carries the
  credentials just acquired (a server may authorize per method). The
  transport doesn't call Authorize again during the handshake.

Fixes #1423
@guglielmo-san
guglielmo-san deleted the guglielmoc/dont_fall_back_initialize branch October 28, 2026 13:07
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.

mcp: Client.Connect falls back to initialize after a 401 on server/discover, running the authorization flow twice

1 participant