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
Copy file name to clipboardExpand all lines: docs/client/oauth-clients.md
+4-3Lines changed: 4 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -76,7 +76,7 @@ Look at `main()`. The provider goes on the **httpx2 client**, the httpx2 client
76
76
77
77
The first time `Client` sends a request, the server answers `401`. The provider takes over:
78
78
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.
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. (An older server that publishes no resource metadata is asked for authorization server metadata at its own origin instead.) Either way the metadata must name, as its `issuer`, the server it was fetched for; anything else is refused.
80
80
2.**Registration.** Nothing in storage? It registers you dynamically with your `OAuthClientMetadata` and stores the result.
81
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.
82
82
4.**Exchange.** It trades the code for an `OAuthToken`, stores it, and replays your original request with `Authorization: Bearer ...`.
@@ -105,13 +105,14 @@ A nightly job, a CI step, another service. There is no browser and nobody to cli
105
105
106
106
`ClientCredentialsOAuthProvider` is the same `httpx2.Auth`, minus the human:
107
107
108
-
```python title="client.py" hl_lines="4 27-33"
108
+
```python title="client.py" hl_lines="4 27-34"
109
109
--8<--"docs_src/oauth_clients/tutorial002.py"
110
110
```
111
111
112
112
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
+
*`issuer` names the authorization server that issued those credentials; use the `issuer` value its `/.well-known/oauth-authorization-server` document returns. Discovery still runs as above, but token requests are only ever built from metadata for *that* issuer; if the MCP server points anywhere else, the flow stops with an `OAuthFlowError` instead. Leave it out and the provider uses whichever authorization server discovery finds.
115
116
*`scope` is a space-separated string, the OAuth wire format.
116
117
* Everything downstream is identical: the same `TokenStorage`, the same `httpx2.AsyncClient(auth=...)`, the same `streamable_http_client`.
117
118
@@ -124,7 +125,7 @@ By default the secret travels as HTTP Basic auth on the token request (`client_s
124
125
One more provider lives in `mcp.client.auth.extensions.client_credentials`:
125
126
**`PrivateKeyJWTOAuthProvider`**, for clients that authenticate with a JWT instead of a
126
127
shared secret (`private_key_jwt`, the key-pair and workload-identity flavour). It follows
127
-
the same pattern: construct one, put it on `auth=`. The same module ships
128
+
the same pattern: construct one (it takes the same optional `issuer`), put it on `auth=`. The same module ships
128
129
`SignedJWTParameters` and `static_assertion_provider`, two helpers that build its assertion.
129
130
130
131
There is one more no-human situation: the client belongs to an enterprise whose identity provider, not the user, decides which MCP servers it may reach. That is a different grant with its own trust model and its own page, **[Identity assertion](identity-assertion.md)**.
There is no client-side override. Fix the deployment instead: make the authorization server's
2519
2521
`issuer` string-equal the URL in the protected resource metadata's `authorization_servers`
2520
-
list. See [OAuth metadata URLs no longer gain a trailing slash](#oauth-metadata-urls-no-longer-gain-a-trailing-slash)
2522
+
list (or the MCP server's origin, without protected resource metadata). See [OAuth metadata URLs no longer gain a trailing slash](#oauth-metadata-urls-no-longer-gain-a-trailing-slash)
2521
2523
for how v2 preserves the exact string form of these URLs.
2522
2524
2523
2525
### OAuth client requests `offline_access` and adds `prompt=consent` when the authorization server supports it ([SEP-2207](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2207))
0 commit comments