You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Address review: seal the parse boundary and judge secret-less methods
An SDK-internal field must never be sourced from the wire, not sourced
then cleared: the registration response body is now parsed with issuer
dropped before validation, so an echoed member neither seeds the SEP-2352
binding nor fails the parse when malformed. The parse goes through
pydantic's JSON layer so malformed bytes still surface as
OAuthRegistrationError.
Both placeholder styles servers use for unset members - null, and "" -
now read as omitted keys across the whole record, closing the gap for
list fields and retiring the field-by-field empty-string coercions. An
empty client_id is accordingly no client_id, so the unrepresentable
empty-identifier record and its dead guards in prepare_token_auth go.
check_registration_usable now also rejects a secret-based method for
which the server issued no client_secret, before persistence and before
any interactive authorization, instead of letting the token request go
out unauthenticated after the browser round-trip.
The bundled registration endpoint refuses private_key_jwt with 400
invalid_client_metadata: it authenticates token requests with the secret
it mints and holds no client key to verify an assertion, so confirming
the method would register a client it could never authenticate - which
this SDK's own client would then immediately declare unusable, orphaning
the record.
Correct prose that overstated the private_key_jwt refresh path (the
tolerance keeps prepare_token_auth from raising so a rejected refresh can
fall back to a fresh, signed client-credentials exchange), the stale
Raises text in prepare_token_auth, and the client failure-model page,
which described OAuthRegistrationError as only a refusal.
Copy file name to clipboardExpand all lines: docs/client/oauth-clients.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -131,7 +131,7 @@ There is one more no-human situation: the client belongs to an enterprise whose
131
131
132
132
## When it fails
133
133
134
-
When the OAuth flow goes wrong, the provider raises an `OAuthFlowError` from `mcp.client.auth`. It has two subclasses. `OAuthRegistrationError` means the authorization server refused to register you. `OAuthTokenError` means the token endpoint said no. One `except OAuthFlowError:` covers discovery, registration, authorization, and exchange.
134
+
When the OAuth flow goes wrong, the provider raises an `OAuthFlowError` from `mcp.client.auth`. It has two subclasses. `OAuthRegistrationError` means registration did not yield a client you can use: the authorization server refused to register you, or it did register you but with credentials this flow cannot use (for instance an authentication method it does not implement). `OAuthTokenError` means the token endpoint said no. One `except OAuthFlowError:` covers discovery, registration, authorization, and exchange.
135
135
136
136
Not everything is a flow error. The network can still fail; those are ordinary `httpx2` exceptions and pass through untouched.
Copy file name to clipboardExpand all lines: docs/migration.md
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2196,9 +2196,9 @@ class OAuthClientInformationFull(OAuthClientMetadataBase): ... # server record
2196
2196
2197
2197
On `OAuthClientInformationFull`, `application_type` and `token_endpoint_auth_method` are now `str | None`, `grant_types` is `list[str]`, and `redirect_uris` is optional (`list[AnyUrl] | None`, no minimum length). `client_id` is now required (`str`): [RFC 7591 §3.2.1](https://datatracker.ietf.org/doc/html/rfc7591#section-3.2.1) makes it mandatory in the response, and a record of a registered client without one was never meaningful. Code that only reads these fields is unaffected. Code that relied on `isinstance(client_info, OAuthClientMetadata)`, or passed an `OAuthClientInformationFull` where an `OAuthClientMetadata` is expected, must reference the record type directly. `validate_scope()` and `validate_redirect_uri()` moved with the record: they are methods of `OAuthClientInformationFull` (the type authorization-server code holds) and are no longer available on `OAuthClientMetadata`.
2198
2198
2199
-
A registration response the server sends is no longer rejected on these fields: an echoed `""` for `token_endpoint_auth_method` or `application_type` reads as absent (as it already did for the optional URL fields), and a member serialized as an explicit `null` reads as an omitted key, so its default applies. Whether a substituted value is usable is judged where it matters, not at parse. When Dynamic Client Registration completes with a `token_endpoint_auth_method` the authorization-code flow cannot apply - anything other than `none`, `client_secret_post`, or `client_secret_basic`, including `private_key_jwt`, whose assertion that flow has no key to sign - the client raises `OAuthRegistrationError` naming the method, before the record is stored or authorization begins. Separately, a stored or pre-registered record carrying a method the SDK does not know at all raises `OAuthTokenError` when it reaches the token exchange (`private_key_jwt` on such a record is fine: `PrivateKeyJWTOAuthProvider` supplies the assertion, and its refresh path still works).
2199
+
A registration response the server sends is no longer rejected on these fields: a member serialized as a placeholder - an explicit `null`, or `""` - reads as an omitted key, so its default applies. Whether a substituted value is usable is judged where it matters, not at parse. When Dynamic Client Registration completes with credentials the authorization-code flow cannot use - a `token_endpoint_auth_method` other than `none`, `client_secret_post`, or `client_secret_basic` (including `private_key_jwt`, whose assertion that flow has no key to sign), or a secret-based method for which the server issued no `client_secret` - the client raises `OAuthRegistrationError` naming the problem, before the record is stored or authorization begins. Separately, a stored or pre-registered record carrying a method the SDK does not know at all raises `OAuthTokenError` when it reaches the token exchange; `private_key_jwt` on such a record does not raise there, so `PrivateKeyJWTOAuthProvider`, which signs its assertion only in the client-credentials exchange, still recovers from a rejected refresh by exchanging afresh.
2200
2200
2201
-
The SDK's own registration endpoint also now returns all registered metadata in its 201 response (RFC 7591 §3.2.1), including the client's `application_type`, which v1 dropped from the echo (silently reporting the default in place of a client's `"web"`).
2201
+
The SDK's own registration endpoint now returns all registered metadata in its 201 response (RFC 7591 §3.2.1) - including the client's `application_type`, which v1 dropped from the echo (silently reporting the default in place of a client's `"web"`), and `client_secret_expires_at` (`0` when the secret never expires) whenever a `client_secret` is issued. It also now answers a `private_key_jwt` registration with `400 invalid_client_metadata` rather than confirming a method it authenticates no requests with.
2202
2202
2203
2203
### Stricter client authentication at `/token` and `/revoke`
0 commit comments