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
The rebase onto main picked up files added since the swap (stories
examples, identity-assertion client/docs, client probe, docs_src
tutorials) that still imported httpx. Apply the same httpx -> httpx2
rename to them, update the migration guide's mcp-types and
identity-assertion sections to name httpx2, and document the
certifi -> truststore TLS verification change.
Copy file name to clipboardExpand all lines: docs/client/identity-assertion.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
@@ -19,7 +19,7 @@ Everything below is the second request: the client that sends it and the authori
19
19
20
20
## The client
21
21
22
-
**`IdentityAssertionOAuthProvider`** lives in `mcp.client.auth.extensions.identity_assertion`. Like every provider in **[OAuth clients](oauth-clients.md)** it is an `httpx.Auth`: construct one, put it on `auth=`, hand the `httpx.AsyncClient` to the transport.
22
+
**`IdentityAssertionOAuthProvider`** lives in `mcp.client.auth.extensions.identity_assertion`. Like every provider in **[OAuth clients](oauth-clients.md)** it is an `httpx2.Auth`: construct one, put it on `auth=`, hand the `httpx2.AsyncClient` to the transport.
@@ -139,7 +139,7 @@ And notice what the returned `OAuthToken` does not carry: a refresh token. The I
139
139
140
140
*[SEP-990](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/990) lets the enterprise identity provider, not the end user, decide which MCP servers a client may reach. The IdP signs that decision into an **ID-JAG**.
141
141
* Obtaining the ID-JAG is an [RFC 8693](https://datatracker.ietf.org/doc/html/rfc8693) token exchange against *your IdP*, and the SDK does not make it. Presenting it to the MCP authorization server is the [RFC 7523](https://datatracker.ietf.org/doc/html/rfc7523)`jwt-bearer` grant, and the SDK does both sides of that.
142
-
*`IdentityAssertionOAuthProvider` is another `httpx.Auth`: a pre-registered confidential client, a pinned `issuer`, and one `assertion_provider(audience, resource)` callback. No browser, no registration, no refresh token.
142
+
*`IdentityAssertionOAuthProvider` is another `httpx2.Auth`: a pre-registered confidential client, a pinned `issuer`, and one `assertion_provider(audience, resource)` callback. No browser, no registration, no refresh token.
143
143
* The authorization server is never discovered from the resource server. Configure `issuer` to exactly the string its metadata document serves; the comparison is character for character.
144
144
* Server side, `identity_assertion_enabled=True` plus `exchange_identity_assertion`. The SDK authenticates the client and gates the grant; validating the ID-JAG is entirely yours, and the issued token is bound to the ID-JAG's `resource`, not the request's.
Copy file name to clipboardExpand all lines: docs/client/oauth-clients.md
+7-7Lines changed: 7 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,7 +2,7 @@
2
2
3
3
Some MCP servers are protected. Send them a request without a token and they answer `401 Unauthorized`.
4
4
5
-
**`OAuthClientProvider`** is how you get the token. It is not an MCP object at all. It is an `httpx.Auth`, the standard httpx hook for "do something to every request". You attach it to an `httpx.AsyncClient`, hand that client to the Streamable HTTP transport, and stop thinking about it.
5
+
**`OAuthClientProvider`** is how you get the token. It is not an MCP object at all. It is an `httpx2.Auth`, the standard httpx2 hook for "do something to every request". You attach it to an `httpx2.AsyncClient`, hand that client to the Streamable HTTP transport, and stop thinking about it.
6
6
7
7
This page is the client side. Making your own server demand a token is **[Authorization](../run/authorization.md)**.
8
8
@@ -68,9 +68,9 @@ A real client runs a small local HTTP server on the redirect URI instead of call
68
68
69
69
### Into the `Client`
70
70
71
-
Look at `main()`. The provider goes on the **httpx client**, the httpx client goes into `streamable_http_client(url, http_client=...)`, and that transport goes into `Client`.
71
+
Look at `main()`. The provider goes on the **httpx2 client**, the httpx2 client goes into `streamable_http_client(url, http_client=...)`, and that transport goes into `Client`.
72
72
73
-
`streamable_http_client` has no `auth=` keyword. Anything HTTP-level (auth, headers, timeouts, proxies) belongs on the `httpx.AsyncClient` you bring. That layering is **[Client transports](transports.md)**.
73
+
`streamable_http_client` has no `auth=` keyword. Anything HTTP-level (auth, headers, timeouts, proxies) belongs on the `httpx2.AsyncClient` you bring. That layering is **[Client transports](transports.md)**.
74
74
75
75
## What the provider does for you
76
76
@@ -103,7 +103,7 @@ The URL must be HTTPS with a non-root path; anything else is a `ValueError` at c
103
103
104
104
A nightly job, a CI step, another service. There is no browser and nobody to click "allow". That is the **client credentials** grant: you already hold a `client_id` and a `client_secret`, and the token endpoint is the whole flow.
105
105
106
-
`ClientCredentialsOAuthProvider` is the same `httpx.Auth`, minus the human:
106
+
`ClientCredentialsOAuthProvider` is the same `httpx2.Auth`, minus the human:
107
107
108
108
```python title="client.py" hl_lines="4 27-33"
109
109
--8<--"docs_src/oauth_clients/tutorial002.py"
@@ -113,7 +113,7 @@ What changed:
113
113
114
114
* No `OAuthClientMetadata`, no handlers. You pass `client_id` and `client_secret`; the provider builds a minimal `client_credentials` registration around them and skips dynamic registration entirely.
115
115
*`scopes` is a space-separated string, the OAuth wire format.
116
-
* Everything downstream is identical: the same `TokenStorage`, the same `httpx.AsyncClient(auth=...)`, the same `streamable_http_client`.
116
+
* Everything downstream is identical: the same `TokenStorage`, the same `httpx2.AsyncClient(auth=...)`, the same `streamable_http_client`.
117
117
118
118
By default the secret travels as HTTP Basic auth on the token request (`client_secret_basic`). Pass `token_endpoint_auth_method="client_secret_post"` to put it in the form body instead. Some authorization servers only accept one of the two.
119
119
@@ -133,11 +133,11 @@ There is one more no-human situation: the client belongs to an enterprise whose
133
133
134
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.
135
135
136
-
Not everything is a flow error. The network can still fail; those are ordinary `httpx` exceptions and pass through untouched.
136
+
Not everything is a flow error. The network can still fail; those are ordinary `httpx2` exceptions and pass through untouched.
137
137
138
138
## Recap
139
139
140
-
*`OAuthClientProvider` is an `httpx.Auth`. Put it on an `httpx.AsyncClient`, pass that to `streamable_http_client(url, http_client=...)`, and `Client` never knows OAuth happened.
140
+
*`OAuthClientProvider` is an `httpx2.Auth`. Put it on an `httpx2.AsyncClient`, pass that to `streamable_http_client(url, http_client=...)`, and `Client` never knows OAuth happened.
141
141
* You supply four things: the server URL, an `OAuthClientMetadata`, a `TokenStorage`, and the redirect/callback handler pair.
142
142
*`TokenStorage` is a `Protocol`: four async methods, no base class. Persist `client_info` as well as the tokens.
143
143
* Discovery, registration (dynamic, or via a **Client ID Metadata Document**), PKCE, the `state` and `iss` checks, and token refresh are the provider's job, not yours.
0 commit comments