Follow Discord
Sweep 22 Oct 2026 · 04:32Z Build v2.1.308 527 read Stable v2.1.308 Latest v2.1.308 Next v2.1.308 Feeds RSS JSON llms.txt llms-full.txt Unofficial
One change · mcp

OAuth Client Credentials changedextensions/auth/oauth-client-credentials

Upstream edited this page at 28 Jul 2026 15:42 UTC, give or take a minute or two: the time comes from Anthropic’s own sitemap rather than from a commit. This site recorded the change at 20 Oct 2026 20:07 UTC.

Upstream edited
Recorded here
Lines+309added
Lines−323removed
From line 4 where the diff opens
First seen 14 Aug 2026 this site's first read of the page
Recorded edits2to this page, all time

#### Using a client secret #### Using a JWT private key

The whole hunk

from line 4, old and new numbered
/
lines

The two sides of this change are more than 400 edits apart, too far apart to line up, so this is the differ's own diff of it and the words inside a line are not marked.

from line 4
44 
55The OAuth Client Credentials extension (`io.modelcontextprotocol/oauth-client-credentials`) adds support for the [OAuth 2.0 client credentials flow](https://datatracker.ietf.org/doc/html/rfc6749#section-4.4) to MCP. This enables automated systems to connect to MCP servers without interactive user authorization.
66 
7<Card title="Specification" icon="file-lines" href="https://github.com/modelcontextprotocol/ext-auth/blob/main/specification/draft/oauth-client-credentials.mdx">
8 Full technical specification for the OAuth Client Credentials extension.
9</Card>
7> **[Specification](https://github.com/modelcontextprotocol/ext-auth/blob/main/specification/draft/oauth-client-credentials.mdx)**
8>
9> Full technical specification for the OAuth Client Credentials extension.
1010 
1111## What it is
1212 
from line 67
6767 Client->>MCP: MCP request (Bearer token)
6868```
6969 
70<Warning>
71 Client secrets are **long-lived credentials** that grant access without user interaction. If a secret is leaked, an attacker can silently authenticate as your application until the secret is rotated. To reduce risk:
72 
73 * Store secrets in a secrets manager, never in source code or environment files checked into version control.
74 * Rotate secrets on a regular schedule and immediately after any suspected compromise.
75 * Scope credentials to the minimum permissions required.
76 * Prefer JWT assertions when possible — they are short-lived and do not require transmitting the signing key.
77</Warning>
70> [!WARNING]
71>
72> Client secrets are **long-lived credentials** that grant access without user interaction. If a secret is leaked, an attacker can silently authenticate as your application until the secret is rotated. To reduce risk:
73>
74> * Store secrets in a secrets manager, never in source code or environment files checked into version control.
75> * Rotate secrets on a regular schedule and immediately after any suspected compromise.
76> * Scope credentials to the minimum permissions required.
77> * Prefer JWT assertions when possible — they are short-lived and do not require transmitting the signing key.
7878 
7979## Implementation guide
8080 
from line 82
8282 
8383To use the OAuth Client Credentials extension, your client must:
8484 
85<Steps>
86 <Step title="Declare support">
87 Include the extension in its per-request capabilities:
88 
89 ```jsonc theme={null}
90 {
91 "jsonrpc": "2.0",
92 "id": 1,
93 "method": "...",
94 "params": {
95 // Other fields...
96 "_meta": {
97 // Other fields...
98 "io.modelcontextprotocol/clientCapabilities": {
99 "extensions": {
100 "io.modelcontextprotocol/oauth-client-credentials": {},
101 },
102 },
85**1. Declare support**
86 
87Include the extension in its per-request capabilities:
88 
89```jsonc theme={null}
90{
91 "jsonrpc": "2.0",
92 "id": 1,
93 "method": "...",
94 "params": {
95 // Other fields...
96 "_meta": {
97 // Other fields...
98 "io.modelcontextprotocol/clientCapabilities": {
99 "extensions": {
100 "io.modelcontextprotocol/oauth-client-credentials": {},
103101 },
104102 },
105 }
106 ```
107 </Step>
108 
109 <Step title="Obtain an access token">
110 Request a token from the authorization server using the client credentials grant before connecting to the MCP server.
111 </Step>
112 
113 <Step title="Include the token">
114 Pass the token in the `Authorization` header of HTTP requests to the MCP server:
115 
116 ```
117 Authorization: Bearer <access_token>
118 ```
119 </Step>
120 
121 <Step title="Handle token refresh">
122 Client credentials tokens typically have shorter lifetimes than user-delegated tokens. Implement token refresh logic to obtain a new token before expiry.
123 </Step>
124</Steps>
103 },
104 },
105}
106```
107 
108**2. Obtain an access token**
109 
110Request a token from the authorization server using the client credentials grant before connecting to the MCP server.
111 
112**3. Include the token**
113 
114Pass the token in the `Authorization` header of HTTP requests to the MCP server:
115 
116```
117Authorization: Bearer <access_token>
118```
119 
120**4. Handle token refresh**
121 
122Client credentials tokens typically have shorter lifetimes than user-delegated tokens. Implement token refresh logic to obtain a new token before expiry.
125123 
126124### For MCP servers
127125 
128126To accept client credentials tokens, your server must:
129127 
130<Steps>
131 <Step title="Validate the token">
132 On each request, verify the JWT signature and claims against your authorization server's public keys (usually via a JWKS endpoint).
133 </Step>
134 
135 <Step title="Check scopes">
136 Ensure the token includes the required scopes for the requested operation.
137 </Step>
138 
139 <Step title="Advertise support">
140 Optionally (but recommended for discoverability), include the extension in the `server/discover` response:
141 
142 ```jsonc theme={null}
143 {
144 "jsonrpc": "2.0",
145 "id": 1,
146 "result": {
147 // Other fields...
148 "capabilities": {
149 "extensions": {
150 "io.modelcontextprotocol/oauth-client-credentials": {},
151 },
152 },
128**1. Validate the token**
129 
130On each request, verify the JWT signature and claims against your authorization server's public keys (usually via a JWKS endpoint).
131 
132**2. Check scopes**
133 
134Ensure the token includes the required scopes for the requested operation.
135 
136**3. Advertise support**
137 
138Optionally (but recommended for discoverability), include the extension in the `server/discover` response:
139 
140```jsonc theme={null}
141{
142 "jsonrpc": "2.0",
143 "id": 1,
144 "result": {
145 // Other fields...
146 "capabilities": {
147 "extensions": {
148 "io.modelcontextprotocol/oauth-client-credentials": {},
153149 },
154 }
155 ```
156 </Step>
157</Steps>
150 },
151 },
152}
153```
158154 
159155## SDK examples
160156 
161157The official MCP SDKs provide built-in support for client credentials authentication. Both handle token acquisition and refresh automatically.
162158 
163<Steps>
164 <Step title="Install the SDK">
165 <Tabs>
166 <Tab title="TypeScript">
167 ```bash theme={null}
168 npm install @modelcontextprotocol/client
169 ```
170 </Tab>
171 
172 <Tab title="Python">
173 ```bash theme={null}
174 pip install mcp
175 ```
176 </Tab>
177 </Tabs>
178 </Step>
179 
180 <Step title="Create a provider and connect">
181 Choose the credential format that matches your setup:
182 
183 #### Using a client secret
184 
185 <Tabs>
186 <Tab title="TypeScript">
187 ```typescript theme={null}
188 import {
189 Client,
190 ClientCredentialsProvider,
191 StreamableHTTPClientTransport,
192 } from "@modelcontextprotocol/client";
193 
194 const provider = new ClientCredentialsProvider({
195 clientId: "my-service",
196 clientSecret: "s3cr3t",
197 });
198 
199 const client = new Client(
200 { name: "my-service", version: "1.0.0" },
201 { capabilities: {} },
202 );
203 
204 const transport = new StreamableHTTPClientTransport(
205 new URL("https://mcp.example.com/mcp"),
206 { authProvider: provider },
207 );
208 
209 await client.connect(transport);
210 
211 // Use the client
212 const tools = await client.listTools();
213 console.log(
214 "Available tools:",
215 tools.tools.map((t) => t.name),
216 );
217 
218 await transport.close();
219 ```
220 </Tab>
221 
222 <Tab title="Python">
223 ```python theme={null}
224 import asyncio
225 
226 import httpx2
227 
228 from mcp import Client
229 from mcp.client.auth.extensions.client_credentials import (
230 ClientCredentialsOAuthProvider,
159**1. Install the SDK**
160 
161**TypeScript**
162 
163```bash theme={null}
164npm install @modelcontextprotocol/client
165```
166 
167**Python**
168 
169```bash theme={null}
170pip install mcp
171```
172 
173**2. Create a provider and connect**
174 
175Choose the credential format that matches your setup:
176 
177#### Using a client secret
178 
179**TypeScript**
180 
181```typescript theme={null}
182import {
183 Client,
184 ClientCredentialsProvider,
185 StreamableHTTPClientTransport,
186} from "@modelcontextprotocol/client";
187 
188const provider = new ClientCredentialsProvider({
189 clientId: "my-service",
190 clientSecret: "s3cr3t",
191});
192 
193const client = new Client(
194 { name: "my-service", version: "1.0.0" },
195 { capabilities: {} },
196);
197 
198const transport = new StreamableHTTPClientTransport(
199 new URL("https://mcp.example.com/mcp"),
200 { authProvider: provider },
201);
202 
203await client.connect(transport);
204 
205// Use the client
206const tools = await client.listTools();
207console.log(
208 "Available tools:",
209 tools.tools.map((t) => t.name),
210);
211 
212await transport.close();
213```
214 
215**Python**
216 
217```python theme={null}
218import asyncio
219 
220import httpx2
221 
222from mcp import Client
223from mcp.client.auth.extensions.client_credentials import (
224 ClientCredentialsOAuthProvider,
225)
226from mcp.client.streamable_http import streamable_http_client
227from mcp.shared.auth import OAuthClientInformationFull, OAuthToken
228 
229 
230class InMemoryTokenStorage:
231 def __init__(self) -> None:
232 self.tokens: OAuthToken | None = None
233 self.client_info: OAuthClientInformationFull | None = None
234 
235 async def get_tokens(self) -> OAuthToken | None:
236 return self.tokens
237 
238 async def set_tokens(self, tokens: OAuthToken) -> None:
239 self.tokens = tokens
240 
241 async def get_client_info(self) -> OAuthClientInformationFull | None:
242 return self.client_info
243 
244 async def set_client_info(self, client_info: OAuthClientInformationFull) -> None:
245 self.client_info = client_info
246 
247 
248provider = ClientCredentialsOAuthProvider(
249 server_url="https://mcp.example.com/mcp",
250 storage=InMemoryTokenStorage(),
251 client_id="my-service",
252 client_secret="s3cr3t",
253 scopes="read write",
254)
255 
256 
257async def main() -> None:
258 async with httpx2.AsyncClient(auth=provider) as http_client:
259 transport = streamable_http_client(
260 "https://mcp.example.com/mcp",
261 http_client=http_client,
231262 )
232 from mcp.client.streamable_http import streamable_http_client
233 from mcp.shared.auth import OAuthClientInformationFull, OAuthToken
234 
235 
236 class InMemoryTokenStorage:
237 def __init__(self) -> None:
238 self.tokens: OAuthToken | None = None
239 self.client_info: OAuthClientInformationFull | None = None
240 
241 async def get_tokens(self) -> OAuthToken | None:
242 return self.tokens
243 
244 async def set_tokens(self, tokens: OAuthToken) -> None:
245 self.tokens = tokens
246 
247 async def get_client_info(self) -> OAuthClientInformationFull | None:
248 return self.client_info
249 
250 async def set_client_info(self, client_info: OAuthClientInformationFull) -> None:
251 self.client_info = client_info
252 
253 
254 provider = ClientCredentialsOAuthProvider(
255 server_url="https://mcp.example.com/mcp",
256 storage=InMemoryTokenStorage(),
257 client_id="my-service",
258 client_secret="s3cr3t",
259 scopes="read write",
263 async with Client(transport) as client:
264 # Use the client
265 tools = await client.list_tools()
266 print("Available tools:", [t.name for t in tools.tools])
267 
268 
269if __name__ == "__main__":
270 asyncio.run(main())
271```
272 
273#### Using a JWT private key
274 
275**TypeScript**
276 
277```typescript theme={null}
278import {
279 Client,
280 PrivateKeyJwtProvider,
281 StreamableHTTPClientTransport,
282} from "@modelcontextprotocol/client";
283 
284const provider = new PrivateKeyJwtProvider({
285 clientId: "my-service",
286 privateKey: process.env.CLIENT_PRIVATE_KEY_PEM,
287 algorithm: "RS256",
288});
289 
290const client = new Client(
291 { name: "my-service", version: "1.0.0" },
292 { capabilities: {} },
293);
294 
295const transport = new StreamableHTTPClientTransport(
296 new URL("https://mcp.example.com/mcp"),
297 { authProvider: provider },
298);
299 
300await client.connect(transport);
301 
302// Use the client
303const tools = await client.listTools();
304console.log(
305 "Available tools:",
306 tools.tools.map((t) => t.name),
307);
308 
309await transport.close();
310```
311 
312**Python**
313 
314```python theme={null}
315import asyncio
316from pathlib import Path
317 
318import httpx2
319 
320from mcp import Client
321from mcp.client.auth.extensions.client_credentials import (
322 PrivateKeyJWTOAuthProvider,
323 SignedJWTParameters,
324)
325from mcp.client.streamable_http import streamable_http_client
326from mcp.shared.auth import OAuthClientInformationFull, OAuthToken
327 
328 
329class InMemoryTokenStorage:
330 def __init__(self) -> None:
331 self.tokens: OAuthToken | None = None
332 self.client_info: OAuthClientInformationFull | None = None
333 
334 async def get_tokens(self) -> OAuthToken | None:
335 return self.tokens
336 
337 async def set_tokens(self, tokens: OAuthToken) -> None:
338 self.tokens = tokens
339 
340 async def get_client_info(self) -> OAuthClientInformationFull | None:
341 return self.client_info
342 
343 async def set_client_info(self, client_info: OAuthClientInformationFull) -> None:
344 self.client_info = client_info
345 
346 
347# Create a signed JWT assertion provider from key parameters
348jwt_params = SignedJWTParameters(
349 issuer="my-service",
350 subject="my-service",
351 signing_key=Path("private_key.pem").read_text(),
352 signing_algorithm="RS256",
353 lifetime_seconds=300,
354)
355 
356provider = PrivateKeyJWTOAuthProvider(
357 server_url="https://mcp.example.com/mcp",
358 storage=InMemoryTokenStorage(),
359 client_id="my-service",
360 assertion_provider=jwt_params.create_assertion_provider(),
361 scopes="read write",
362)
363 
364 
365async def main() -> None:
366 async with httpx2.AsyncClient(auth=provider) as http_client:
367 transport = streamable_http_client(
368 "https://mcp.example.com/mcp",
369 http_client=http_client,
260370 )
261 
262 
263 async def main() -> None:
264 async with httpx2.AsyncClient(auth=provider) as http_client:
265 transport = streamable_http_client(
266 "https://mcp.example.com/mcp",
267 http_client=http_client,
268 )
269 async with Client(transport) as client:
270 # Use the client
271 tools = await client.list_tools()
272 print("Available tools:", [t.name for t in tools.tools])
273 
274 
275 if __name__ == "__main__":
276 asyncio.run(main())
277 ```
278 </Tab>
279 </Tabs>
280 
281 #### Using a JWT private key
282 
283 <Tabs>
284 <Tab title="TypeScript">
285 ```typescript theme={null}
286 import {
287 Client,
288 PrivateKeyJwtProvider,
289 StreamableHTTPClientTransport,
290 } from "@modelcontextprotocol/client";
291 
292 const provider = new PrivateKeyJwtProvider({
293 clientId: "my-service",
294 privateKey: process.env.CLIENT_PRIVATE_KEY_PEM,
295 algorithm: "RS256",
296 });
297 
298 const client = new Client(
299 { name: "my-service", version: "1.0.0" },
300 { capabilities: {} },
301 );
302 
303 const transport = new StreamableHTTPClientTransport(
304 new URL("https://mcp.example.com/mcp"),
305 { authProvider: provider },
306 );
307 
308 await client.connect(transport);
309 
310 // Use the client
311 const tools = await client.listTools();
312 console.log(
313 "Available tools:",
314 tools.tools.map((t) => t.name),
315 );
316 
317 await transport.close();
318 ```
319 </Tab>
320 
321 <Tab title="Python">
322 ```python theme={null}
323 import asyncio
324 from pathlib import Path
325 
326 import httpx2
327 
328 from mcp import Client
329 from mcp.client.auth.extensions.client_credentials import (
330 PrivateKeyJWTOAuthProvider,
331 SignedJWTParameters,
332 )
333 from mcp.client.streamable_http import streamable_http_client
334 from mcp.shared.auth import OAuthClientInformationFull, OAuthToken
335 
336 
337 class InMemoryTokenStorage:
338 def __init__(self) -> None:
339 self.tokens: OAuthToken | None = None
340 self.client_info: OAuthClientInformationFull | None = None
341 
342 async def get_tokens(self) -> OAuthToken | None:
343 return self.tokens
344 
345 async def set_tokens(self, tokens: OAuthToken) -> None:
346 self.tokens = tokens
347 
348 async def get_client_info(self) -> OAuthClientInformationFull | None:
349 return self.client_info
350 
351 async def set_client_info(self, client_info: OAuthClientInformationFull) -> None:
352 self.client_info = client_info
353 
354 
355 # Create a signed JWT assertion provider from key parameters
356 jwt_params = SignedJWTParameters(
357 issuer="my-service",
358 subject="my-service",
359 signing_key=Path("private_key.pem").read_text(),
360 signing_algorithm="RS256",
361 lifetime_seconds=300,
362 )
363 
364 provider = PrivateKeyJWTOAuthProvider(
365 server_url="https://mcp.example.com/mcp",
366 storage=InMemoryTokenStorage(),
367 client_id="my-service",
368 assertion_provider=jwt_params.create_assertion_provider(),
369 scopes="read write",
370 )
371 
372 
373 async def main() -> None:
374 async with httpx2.AsyncClient(auth=provider) as http_client:
375 transport = streamable_http_client(
376 "https://mcp.example.com/mcp",
377 http_client=http_client,
378 )
379 async with Client(transport) as client:
380 # Use the client
381 tools = await client.list_tools()
382 print("Available tools:", [t.name for t in tools.tools])
383 
384 
385 if __name__ == "__main__":
386 asyncio.run(main())
387 ```
388 </Tab>
389 </Tabs>
390 </Step>
391</Steps>
371 async with Client(transport) as client:
372 # Use the client
373 tools = await client.list_tools()
374 print("Available tools:", [t.name for t in tools.tools])
375 
376 
377if __name__ == "__main__":
378 asyncio.run(main())
379```
392380 
393381## Client support
394382 
395<Note>
396 Support for this extension varies by client. Extensions are opt-in and never active by default.
397</Note>
383> [!NOTE]
384>
385> Support for this extension varies by client. Extensions are opt-in and never active by default.
398386 
399387Check the [client matrix](/extensions/client-matrix) for current implementation status across MCP clients.
400388 
401389## Related resources
402390 
403<CardGroup cols={2}>
404 <Card title="ext-auth repository" icon="github" href="https://github.com/modelcontextprotocol/ext-auth">
405 Source code and reference implementations
406 </Card>
407 
408 <Card title="Full specification" icon="file-lines" href="https://github.com/modelcontextprotocol/ext-auth/blob/main/specification/draft/oauth-client-credentials.mdx">
409 Technical specification with normative requirements
410 </Card>
411 
412 <Card title="RFC 6749 — Client Credentials Grant" icon="link" href="https://datatracker.ietf.org/doc/html/rfc6749#section-4.4">
413 The underlying OAuth 2.0 specification
414 </Card>
415 
416 <Card title="RFC 7523 — JWT Bearer Assertions" icon="link" href="https://datatracker.ietf.org/doc/html/rfc7523">
417 JWT assertion format specification
418 </Card>
419</CardGroup>
391> **[ext-auth repository](https://github.com/modelcontextprotocol/ext-auth)**
392>
393> Source code and reference implementations
394 
395> **[Full specification](https://github.com/modelcontextprotocol/ext-auth/blob/main/specification/draft/oauth-client-credentials.mdx)**
396>
397> Technical specification with normative requirements
398 
399> **[RFC 6749 — Client Credentials Grant](https://datatracker.ietf.org/doc/html/rfc6749#section-4.4)**
400>
401> The underlying OAuth 2.0 specification
402 
403> **[RFC 7523 — JWT Bearer Assertions](https://datatracker.ietf.org/doc/html/rfc7523)**
404>
405> JWT assertion format specification
420406 
Feedback