Thanks to visit codestin.com
Credit goes to github.com

Skip to content

SEP-2352: Clarify authorization server binding and migration#2352

Merged
mcp-commander[bot] merged 13 commits into
mainfrom
den/as
Mar 28, 2026
Merged

SEP-2352: Clarify authorization server binding and migration#2352
mcp-commander[bot] merged 13 commits into
mainfrom
den/as

Conversation

@dend

@dend dend commented Mar 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Test plan

  • npx prettier --check passes
  • npm run check:docs:js-comments passes
  • Review rendered docs for correct formatting

🤖 Generated with Claude Code

@dend dend requested a review from a team as a code owner March 4, 2026 22:42
Add guidance for clients on binding persisted client credentials to
specific authorization servers and handling AS changes. Clarifies
multi-AS behavior when multiple servers are listed in
authorization_servers.

Closes #1349

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@dend dend changed the title docs: clarify authorization server binding and migration SEP-2352: Clarify authorization server binding and migration Mar 4, 2026
@dend dend self-assigned this Mar 4, 2026
@dend dend added auth security SEP draft SEP proposal with a sponsor. labels Mar 4, 2026
@dend dend moved this to In Review in SEP Review Pipeline Mar 4, 2026

@pcarleton pcarleton left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is a good change. Both typescript and python will need to adjustments.

Should be straightforward to conformance test as well, and key it on DCR support.

Comment thread docs/specification/draft/basic/authorization.mdx Outdated
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Comment thread docs/specification/draft/basic/authorization.mdx Outdated
Comment thread docs/specification/draft/basic/authorization.mdx Outdated
Comment thread docs/specification/draft/basic/authorization.mdx Outdated
Comment thread docs/specification/draft/basic/authorization.mdx Outdated
@guglielmo-san

Copy link
Copy Markdown

I am implementing this SEP in the Go SDK and would like to clarify a couple of cases regarding client credential management:

  1. The SEP states that clients that persist client credentials obtained via DCR must associate them with the specific AS keyed by the issuer identifier. Go SDK does not persist credentials but performs DCR dynamically on-demand (e.g., every time authorization is needed or after a token expires). Is persistence of DCR credentials considered a MUST or a SHOULD for compliance with this SEP?
  2. The SEP states clients MUST maintain separate registration state per authorization server. RFC 9728 Section 7.6 defers server selection to the client. Is a client that always selects only the first AS from authorization_servers (and never attempts others) considered compliant? Or is there an expectation that clients should attempt alternative servers on authorization failure?

@localden localden added final SEP finalized. and removed accepted SEP accepted by core maintainers, but still requires final wording and reference implementation. labels Jun 4, 2026
koic added a commit to modelcontextprotocol/ruby-sdk that referenced this pull request Jul 11, 2026
… SEP-2352

## Motivation and Context

SEP-2352 (modelcontextprotocol/modelcontextprotocol#2352, merged for the 2026-07-28 spec release) clarifies
authorization server binding and migration: clients MUST key persisted client credentials by the issuing
authorization server's issuer identifier, MUST NOT reuse credentials across different authorization servers,
and MUST re-register when the server's authorization server changes. CIMD `client_id`s (HTTPS URLs) are
explicitly portable across authorization servers, and a mismatch SHOULD surface an error rather than silently use
mismatched credentials.

The Ruby flow previously reused any stored `client_information` with a `client_id` unconditionally,
so a server that migrated to a new authorization server would have the old server's credentials
(and its dead tokens) replayed forever. This change follows the detect-change, invalidate,
and re-register approach that both reference SDKs converged on (typescript-sdk#2348/#2358, merged 2026-06-24;
python-sdk#2933/#2936/#2946, which uses the same proactive compare-before-reuse design as this change)
while keeping the 4-method storage contract intact by stamping the binding into the persisted hash:

- Successful Dynamic Client Registration now persists the credentials with an `"issuer"` member set to
  the validated AS metadata `issuer` (which `ensure_issuer_matches!` has already pinned to the discovery URL).
- `Flow#ensure_client_registered` reuses stored credentials only when the stored issuer matches the current one.
  Credentials without an issuer binding (data persisted by older SDK versions, or user-supplied pre-registered credentials)
  are bound to the current issuer on first use. A portable CIMD `client_id` is reused and re-bound.
  Any other issuer mismatch discards the stale registration and its tokens (tokens minted by the old AS are dead at the new one)
  and re-registers via CIMD/DCR.
- `Flow#refresh!` raises `AuthorizationError` instead of replaying another authorization server's credentials at the token endpoint;
  `MCP::Client::HTTP#attempt_refresh` already rescues that error and falls back to the full flow, which performs
  the discard-and-re-register.
- The `client_credentials` grant (machine-to-machine) reads static pre-registered credentials without going through
  `ensure_client_registered`, so it would have silently sent another authorization server's credentials after a migration.
  `run_client_credentials!` now surfaces an `AuthorizationError` when the stored `issuer` mismatches the current authorization server,
  implementing the spec's "SHOULD surface an error" (the TypeScript SDK raises `AuthorizationServerMismatchError` here).
  Re-registration is not an option for pre-registered m2m credentials, so the operator must update the stored credentials;
  credentials without a recorded issuer keep working unchanged.

Two adjacent hardenings from the reference implementations were checked and need no change here:
the legacy-fallback issuer stamp is self-consistent because this SDK's fallback registers at the same origin it
uses as the issuer (the cross-origin never-reached-AS binding python-sdk#2946 closed cannot arise),
and preserving a non-rotated `refresh_token` across refreshes already exists via `preserve_refresh_token`.
The TypeScript SDK's redirected-to-issuer callback gate is not adopted: the RFC 9207 `iss` validation (SEP-2468) covers that mix-up leg.

Resolves #385.

## How Has This Been Tested?

New tests in `test/mcp/client/oauth/flow_test.rb`:

- stored credentials with a matching issuer skip DCR
- an issuer mismatch re-registers and persists the new `client_id` with the new issuer
- the stale registration's tokens are cleared before re-registration (observed by aborting the flow at the DCR step)
- legacy credentials without an issuer (string- and symbol-keyed) are reused and bound to the current issuer on first use
- a CIMD `client_id` with a mismatched issuer is reused without DCR and re-bound
- fresh DCR persists the issuer binding
- `refresh!` succeeds with a matching stored issuer and raises (without contacting the token endpoint) with a mismatched one
- the `client_credentials` grant succeeds with a matching stored issuer and raises (without contacting the token endpoint) with
  a mismatched one

All existing flow, HTTP OAuth, and provider tests pass unchanged.

## Breaking Changes

None for the documented storage contract: `client_information` hashes gain an additional `"issuer"` member that storages persist opaquely,
and credentials stored without one keep working via first-use binding. Behavior changes only in the previously broken case where
the authorization server changed, which now re-registers (or, for static machine-to-machine credentials, surfaces an error) instead of
replaying the old server's credentials.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: Accepted

Development

Successfully merging this pull request may close these issues.

The authorization spec is unclear when multiple Authorization servers are involved

10 participants