Skip to content

fix(mcp): re-register the OAuth client when its redirect URI no longer matches - #2620

Merged
sailist merged 1 commit into
MoonshotAI:mainfrom
xpzouying:fix/mcp-oauth-stale-redirect-registration
Aug 5, 2026
Merged

fix(mcp): re-register the OAuth client when its redirect URI no longer matches#2620
sailist merged 1 commit into
MoonshotAI:mainfrom
xpzouying:fix/mcp-oauth-stale-redirect-registration

Conversation

@xpzouying

Copy link
Copy Markdown
Contributor

Related Issue

Resolve #2606

Problem

See the linked issue and its root-cause comment: startCallbackServer() binds a random free port per flow (oauth/callback-server.ts, listen(0)), while dynamic client registration records the first flow's redirect URI. Every subsequent interactive authorization therefore presents a callback URI the registration does not cover, and the authorization endpoint rejects it with "Invalid redirect URI" — deterministically, on the second interactive flow and every one after. The rejection renders only in the user's browser; this client just waits for a callback that never comes. The only recovery was manually deleting credentials/mcp/<key>-client.json.

Verified live in both directions: a second interactive authorization against a stored registration failed with exactly this error; after clearing the registration the same flow succeeded first try.

What changed

  • McpOAuthClientProvider (v1 mcp/oauth/provider.ts, v2 mcpCore/oauth/provider.ts) gains invalidateStaleRegistration(redirectUri): when a stored registration exists and its redirect_uris do not cover the given URI, the client registration is dropped (tokens and discovery state untouched) and true is returned.
  • beginAuthorization (v1/v2 oauth/service.ts) calls it right after binding the callback listener, before invoking auth() — so auth() finds no client information and re-registers with the current callback URI. Flows whose registration still matches are untouched.

Verification

  • New unit tests (v1 test/mcp/oauth-store.test.ts, v2 test/mcpCore/oauth/store.test.ts): mismatched registration dropped with true; matching registration kept; no-op without a stored registration. Suites pass (16 v1 / 12 v2).
  • tsc --noEmit and oxlint clean for both packages.
  • Changeset included (@moonshot-ai/kimi-code: patch) — the failure is user-visible (re-authorization goes from always-failing to working).

@changeset-bot

changeset-bot Bot commented Aug 4, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 8cef67f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@moonshot-ai/kimi-code Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Aug 4, 2026

Copy link
Copy Markdown
pnpm dlx https://pkg.pr.new/@moonshot-ai/kimi-code@8cef67f
npx https://pkg.pr.new/@moonshot-ai/kimi-code@8cef67f

commit: 8cef67f

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 98d5b81926

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


provider.setRedirectUrl(new URL(callbackServer.redirectUri));
await provider.ready;
await provider.invalidateStaleRegistration(callbackServer.redirectUri);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep the existing client registration through refresh

Dropping the stored DCR record before auth() runs removes the old clientInformation that the SDK uses for the refresh-token branch. On servers that bind refresh tokens to the original client_id, refreshAuthorization() will now fail with invalid_grant/invalid_client; the SDK re-throws those OAuth errors instead of falling through to the browser flow, so a user who still had a usable refresh token can lose reauth entirely. The same pattern is applied in the v1 service.

Useful? React with 👍 / 👎.

…r matches

The callback listener binds a random port per flow, while DCR
registration records the redirect URI of the flow that created it —
so every interactive authorization after the first was rejected with
"Invalid redirect URI", an error rendered only in the user's browser
while the client waited for a callback that never came. Detect the
mismatch before invoking auth() and drop the stale registration so the
flow re-registers with the current callback URI (v1 + v2).

Resolve MoonshotAI#2606
@xpzouying
xpzouying force-pushed the fix/mcp-oauth-stale-redirect-registration branch from 98d5b81 to 8cef67f Compare August 4, 2026 18:11
@sailist
sailist merged commit 2ee6e43 into MoonshotAI:main Aug 5, 2026
14 checks passed
@github-actions github-actions Bot mentioned this pull request Aug 5, 2026
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 OAuth: stale DCR client registration is reused, authorization fails with "Invalid redirect URI" until credentials are deleted by hand

2 participants