Skip to content

fix(mcp): fix OAuth auto-connect failing on first connection#15547

Open
domdomegg wants to merge 1 commit intoanomalyco:devfrom
domdomegg:fix/oauth-auto-connect-state
Open

fix(mcp): fix OAuth auto-connect failing on first connection#15547
domdomegg wants to merge 1 commit intoanomalyco:devfrom
domdomegg:fix/oauth-auto-connect-state

Conversation

@domdomegg
Copy link

@domdomegg domdomegg commented Mar 1, 2026

Issue for this PR

Closes #15546

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

When OpenCode auto-connects to a remote MCP server requiring OAuth (e.g. on startup with a pre-configured server), the SDK transport gets a 401 and internally triggers the OAuth auth() flow. This flow calls provider.state() to get an OAuth state parameter — but state() was implemented as a reader that throws if no state was previously saved. Since saveState() is only called in the explicit startAuth() path (not the automatic connect path), state() always throws on first connect.

The thrown error is a plain Error, not UnauthorizedError, so OpenCode's create() catch block doesn't recognize it as auth-related. The server ends up with status: "failed" instead of status: "needs_auth".

Two fixes:

  1. McpOAuthProvider.state() — generate and persist a new cryptographic state when none exists, instead of throwing. The SDK's OAuthClientProvider interface defines state?() as a generator that returns a state value, not a reader.

  2. create() error handling — also treat errors containing "OAuth" from an auth-provider-enabled transport as auth-related, so they result in needs_auth status rather than failed. This is a defense-in-depth fix for any other plain Error the SDK might throw during the auth flow.

How did you verify your code works?

Added 3 new tests in test/mcp/oauth-auto-connect.test.ts:

  • first connect to OAuth server shows needs_auth instead of failed — verifies the full auto-connect path sets needs_auth status
  • state() generates a new state when none is saved — verifies state generation and persistence
  • state() returns existing state when one is saved — verifies pre-saved state is returned as-is

All MCP tests pass: bun test test/mcp/ --timeout 30000 → 9 pass, 0 fail.

Screenshots / recordings

N/A

Checklist

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

… MCP servers

state() threw when no state was pre-saved, but the SDK calls it as a
generator during automatic auth. Also harden create() to treat
auth-related errors from the SDK transport as needs_auth.
@github-actions github-actions bot added needs:compliance This means the issue will auto-close after 2 hours. and removed needs:compliance This means the issue will auto-close after 2 hours. labels Mar 1, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Mar 1, 2026

Thanks for updating your PR! It now meets our contributing guidelines. 👍

@domdomegg
Copy link
Author

After the fix:

CleanShot 2026-03-01 at 08 52 13@2x CleanShot 2026-03-01 at 08 53 00@2x

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.

Remote MCP servers with OAuth fail with 'No OAuth state saved for MCP server'

1 participant