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
Let pre-provisioned OAuth clients name their authorization server
ClientCredentialsOAuthProvider and PrivateKeyJWTOAuthProvider take an
optional issuer keyword: the issuer identifier of the authorization
server the fixed client_id (and secret) were issued by. When set, token
requests, the client_credentials exchange and any refresh, are only built
from discovered authorization server metadata whose issuer matches it; if
discovery yields metadata for another server, or none at all, the flow
stops with OAuthFlowError before the secret is attached or an assertion
is minted. Omitting it keeps the current behaviour.
This is the same "the authorization server is configuration" model that
IdentityAssertionOAuthProvider already uses, made available to the two
older machine-to-machine providers without changing their defaults.
Copy file name to clipboardExpand all lines: docs/client/oauth-clients.md
+3-2Lines changed: 3 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -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, spelled exactly as that server's metadata states it (for an authorization server built with this SDK that is the URL with a trailing slash). 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)**.
0 commit comments