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
Align OAuth client with spec on PKCE verification and scope selection
Two changes to how the OAuth client uses discovered authorization-server
metadata, both required by the MCP authorization specification.
Verify PKCE support before the authorization-code grant. The spec's
Authorization Code Protection section requires clients to verify PKCE
support from the authorization server's metadata and to refuse to
proceed when code_challenge_methods_supported is absent. The new
validate_pkce_support() also refuses a method list that omits S256,
since that is the only method this client sends. The check sits at the
top of _perform_authorization_code_grant, so it covers both the initial
401 flow and the 403 insufficient_scope step-up, while grants that never
issue an authorization code (client credentials, private key JWT) are
unaffected. When no metadata document was discovered at all the flow
proceeds as before: absence of a document is not evidence of
non-support.
Stop reading scopes_supported from authorization-server metadata when
selecting a scope. The spec's scope-selection chain is the
WWW-Authenticate scope parameter, then the protected-resource metadata's
scopes_supported, otherwise omit the scope parameter. The SDK inserted
an extra fallback to the authorization server's scopes_supported, which
over-requests (an authorization server may serve many resource servers,
so its list is a superset of any one resource's) and causes
access_denied failures against servers that reject unknown scopes. With
the fallback removed, clients that relied on it should pass an explicit
scope on their OAuthClientMetadata.
Closes#1307
Copy file name to clipboardExpand all lines: docs/advanced/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
@@ -78,7 +78,7 @@ The first time `Client` sends a request, the server answers `401`. The provider
78
78
79
79
1.**Discovery.** It reads the `WWW-Authenticate` header, fetches the server's Protected Resource Metadata from `/.well-known/oauth-protected-resource`, learns which authorization server protects this resource, and fetches *that* server's metadata.
80
80
2.**Registration.** Nothing in storage? It registers you dynamically with your `OAuthClientMetadata` and stores the result.
81
-
3.**Authorization.** It generates the PKCE pair and a `state`, builds the authorization URL, awaits your `redirect_handler`, then awaits your `callback_handler` for the code.
81
+
3.**Authorization.** It checks that the discovered authorization-server metadata advertises `S256` PKCE support (and stops with `OAuthFlowError` if it does not), generates the PKCE pair and a `state`, builds the authorization URL, awaits your `redirect_handler`, then awaits your `callback_handler` for the code.
82
82
4.**Exchange.** It trades the code for an `OAuthToken`, stores it, and replays your original request with `Authorization: Bearer ...`.
83
83
84
84
After that it is quiet. Tokens come out of storage, an expired access token is refreshed with the refresh token, and only when none of that works does it run the flow again.
Forward the `iss` query parameter from the redirect so the validation can run: omitting it makes the flow fail with `OAuthFlowError` against servers that advertise `authorization_response_iss_parameter_supported`, and silently skips the check for servers that send `iss` without advertising it.
123
123
124
+
### OAuth client refuses to authorize when AS metadata does not advertise S256 PKCE
125
+
126
+
The OAuth client now verifies PKCE support before starting the authorization-code grant, as the MCP authorization specification's Authorization Code Protection section requires. When an authorization-server metadata document was discovered and its `code_challenge_methods_supported` is absent (which the spec defines as "the authorization server does not support PKCE") or does not list `S256` (the only method the SDK sends), the flow raises `OAuthFlowError` instead of redirecting to the authorization endpoint. The symptom is `OAuthFlowError: Authorization server metadata does not include code_challenge_methods_supported; PKCE support cannot be verified`. Previously the SDK never inspected the field and proceeded with an S256 challenge regardless.
127
+
128
+
When no authorization-server metadata document could be discovered at all, the flow proceeds as before — absence of a document is not evidence of non-support. Grants that never issue an authorization code (`ClientCredentialsOAuthProvider`, `PrivateKeyJWTOAuthProvider`) are unaffected. There is no SDK-side opt-out: an authorization server that supports S256 but omits the field from its published metadata needs that metadata fixed (RFC 8414 §2 defines `code_challenge_methods_supported`).
129
+
130
+
### OAuth client no longer reads `scopes_supported` from AS metadata to choose a scope
131
+
132
+
The specification's scope-selection chain is two steps: the `scope` parameter from the `WWW-Authenticate` challenge, then `scopes_supported` from the Protected Resource Metadata document, *otherwise the `scope` parameter is omitted*. The SDK inserted an extra fallback between those two steps — the **authorization-server** metadata's `scopes_supported` — which over-requests (an authorization server may serve many resource servers, so its list is a superset of any one resource's) and caused real `access_denied` failures ([#1307](https://github.com/modelcontextprotocol/python-sdk/issues/1307)). That fallback is removed: when neither the challenge nor the PRM names scopes, the client now omits the `scope` parameter and lets the authorization server apply its defaults.
133
+
134
+
This also affects the SEP-2207 `offline_access` augmentation, which only fires once a base scope was selected: if the authorization server's `scopes_supported` was your only scope source, the client now sends no `scope` at all (not even `offline_access`) and the authorization server's defaults decide whether a refresh token is issued. In either case, if you relied on the removed fallback, pass an explicit `scope` on the `OAuthClientMetadata` you give to `OAuthClientProvider`.
135
+
124
136
### `get_session_id` callback removed from `streamable_http_client`
125
137
126
138
The `get_session_id` callback (third element of the returned tuple) has been removed from `streamable_http_client`. The function now returns a 2-tuple `(read_stream, write_stream)` instead of a 3-tuple.
0 commit comments