Skip to content

fix(mcp): trigger OAuth for servers that only enforce auth at request time - #53468

Closed
jethac wants to merge 1 commit into
anomalyco:devfrom
jethac:fix/mcp-deferred-oauth
Closed

jethac wants to merge 1 commit into
anomalyco:devfrom
jethac:fix/mcp-deferred-oauth

Conversation

@jethac

@jethac jethac commented Oct 6, 2026

Copy link
Copy Markdown

Issue for this PR

Closes #26195. The config escape hatch follows the approach proposed in #26912.

Type of change

  • Bug fix

What does this PR do?

Google's official MCP connectors (gmailmcp/drivemcp/calendarmcp.googleapis.com) accept initialize — and ping and tools/list — without credentials, and only return 401 on tools/call. Since the OAuth flow only starts on a connect-time 401, it never runs: opencode mcp auth prints "Authentication successful!" with zero tokens stored, and every tool call fails as Unauthorized.

The fix has three parts:

  • Persist discovery state — the provider implements the SDK's discoveryState/saveDiscoveryState hooks:
    • OAuth endpoints learned from a 401 challenge (via its WWW-Authenticate header) are stored next to the other credentials in mcp-auth.json
    • also fixes token refresh for servers without standard RFC 9728 discovery, since refresh no longer has to re-derive the authorization server
    • fallback-only states (no metadata) are never persisted, so a failed discovery can't poison later refreshes
  • Start the flow explicitly in startAuth() when the server connects without a 401:
    • triggers only with no usable stored tokens and evidence OAuth is required: a configured authorizationServerUrl, or a stored challenge
    • calls the SDK's auth() directly and returns the authorization URL, so the normal browser flow and finishAuth run unchanged
    • deliberately does not treat "OAuth endpoints are discoverable" as evidence — some servers expose them but work without auth, and forcing a flow there would regress public servers
  • Handle request-time 401s on connected servers:
    • the server flips to needs_auth and the client is closed, with a toast pointing at opencode mcp auth <name>
    • instead of failing every tool call as Unauthorized with no way out

Behavior change:

  • if a server issued a 401 earlier, authenticate() now starts the OAuth flow even if the server currently accepts unauthenticated connects:
    • at connect time, a server that stopped requiring auth is indistinguishable from one that defers auth to tool calls, so the flow runs rather than risk another silent no-token connect
    • two existing tests pinned the old behavior via a server that turns anonymous mid-test; restructured to keep their intent with servers that never required auth

How did you verify your code works?

Four new tests in test/mcp/oauth-auto-connect.test.ts using a deferred-auth mock that mirrors Google's behavior (stateless, everything public except tools/call). Also checked against the real gmailmcp endpoint: initialize/ping/tools/list return 200 unauthenticated, tools/call returns 401 with valid RFC 9728 metadata in the challenge. bun run typecheck clean in opencode and core; full opencode test suite passes except a pre-existing flaky SSE timeout test that also fails on dev and passes 3/3 in isolation.

Screenshots / recordings

N/A — no UI change beyond an existing-style toast.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

… time

Some servers (e.g. Google's Gmail/Drive/Calendar MCP endpoints) accept
the initialize handshake without credentials and only return 401 on
tool calls. The OAuth flow therefore never starts, mcp auth prints
"Authentication successful!" with no tokens stored, and every tool
call fails as Unauthorized.

- Persist OAuth discovery state (RFC 9728 / RFC 8414) learned from 401
  challenges via the provider's discoveryState/saveDiscoveryState hooks
- startAuth() explicitly starts the authorization flow after a silent
  connect when the server has no usable tokens and there is evidence it
  requires OAuth (configured authorizationServerUrl or a stored
  challenge)
- Tool-call 401s flip the server to needs_auth with a toast instead of
  dead-ending as Unauthorized
- Add oauth.authorizationServerUrl config for servers that require
  OAuth but expose no discoverable metadata

Fixes anomalyco#26195
@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

The following comment was made by an LLM, it may be inaccurate:

@thdxr

thdxr commented Oct 10, 2026

Copy link
Copy Markdown
Member

This branch has been upgraded to V2. We're no longer taking PRs for V1. If you think this is still relevant, please port it over and open it for V2.

— from 𝕺𝖕𝖊𝖓𝕮𝖔𝖉𝖊

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.

opencode mcp auth fails to open browser for OAuth flow (Google Drive MCP)

2 participants