Documentation Index
Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt Use this file to discover all available pages before exploring further.
OAuth Client Credentials
Machine-to-machine authentication for MCP using the OAuth 2.0 client credentials flow
The OAuth Client Credentials extension (io.modelcontextprotocol/oauth-client-credentials) adds support for the OAuth 2.0 client credentials flow to MCP. This enables automated systems to connect to MCP servers without interactive user authorization.
What it is
The standard MCP authorization flow requires a user to interactively approve access — a browser opens, the user logs in, and grants permission. That works well for humans, but breaks down when there’s no user present.
The OAuth Client Credentials extension solves this by letting a client authenticate using application-level credentials (a client ID and secret, or a signed JWT assertion) rather than delegated user credentials. The client proves its identity directly to the authorization server, which issues an access token without requiring a browser redirect or user interaction.
When to use it
Use OAuth Client Credentials when:
- Background services need to call MCP tools on a schedule or in response to events, without a user present
- CI/CD pipelines invoke MCP servers as part of automated build, test, or deployment workflows
- Server-to-server integrations connect two backend systems where there’s no end user involved
- Daemon processes or long-running workers need persistent access to MCP resources
If your integration has a human user who should explicitly authorize access, use the standard MCP authorization flow instead.
How it works
The extension supports two credential formats:
JWT Bearer Assertions (recommended)
Defined in RFC 7523, JWT Bearer Assertions let the client sign a token with its private key and present it as proof of identity. The authorization server validates the signature using the client’s registered public key.
sequenceDiagram
participant Client
participant AS as Authorization Server
participant MCP as MCP Server
Client->>AS: POST /token<br/>grant_type=urn:ietf:params:<br/>oauth:grant-type:jwt-bearer<br/>assertion=<signed JWT>
AS-->>Client: access_token
Client->>MCP: MCP request (Bearer token)
The JWT assertion typically includes:
iss: Client ID (the issuer)sub: Client ID (subject being authenticated)aud: Authorization server token endpoint URLexp: Expiration timeiat: Issued-at time
Client Secrets
For simpler deployments, the extension also supports the standard client credentials flow using a client_id and client_secret. The client sends its credentials directly to the authorization server’s token endpoint and receives an access token in return.
sequenceDiagram
participant Client
participant AS as Authorization Server
participant MCP as MCP Server
Client->>AS: POST /token<br/>grant_type=client_credentials<br/>client_id + client_secret
AS-->>Client: access_token
Client->>MCP: MCP request (Bearer token)
<Warning> 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:
- Store secrets in a secrets manager, never in source code or environment files checked into version control.
- Rotate secrets on a regular schedule and immediately after any suspected compromise.
- Scope credentials to the minimum permissions required.
- Prefer JWT assertions when possible — they are short-lived and do not require transmitting the signing key.
Implementation guide
For MCP clients
To use the OAuth Client Credentials extension, your client must:
<Steps>
```jsonc theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"method": "...",
"params": {
// Other fields...
"_meta": {
// Other fields...
"io.modelcontextprotocol/clientCapabilities": {
"extensions": {
"io.modelcontextprotocol/oauth-client-credentials": {},
},
},
},
},
}
```
```
Authorization: Bearer <access_token>
``` For MCP servers
To accept client credentials tokens, your server must:
<Steps>
```jsonc theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"result": {
// Other fields...
"capabilities": {
"extensions": {
"io.modelcontextprotocol/oauth-client-credentials": {},
},
},
},
}
``` SDK examples
The official MCP SDKs provide built-in support for client credentials authentication. Both handle token acquisition and refresh automatically.
<Steps>
bash theme={null} npm install @modelcontextprotocol/client
<Tab title="Python">
```bash theme={null}
pip install mcp
```
</Tab>
</Tabs>
#### Using a client secret
\<Tabs\>
<Tab title="TypeScript">
```typescript theme={null}
import {
Client,
ClientCredentialsProvider,
StreamableHTTPClientTransport,
} from "@modelcontextprotocol/client";
const provider = new ClientCredentialsProvider({
clientId: "my-service",
clientSecret: "s3cr3t",
});
const client = new Client(
{ name: "my-service", version: "1.0.0" },
{ capabilities: {} },
);
const transport = new StreamableHTTPClientTransport(
new URL("https://mcp.example.com/mcp"),
{ authProvider: provider },
);
await client.connect(transport);
// Use the client
const tools = await client.listTools();
console.log(
"Available tools:",
tools.tools.map((t) => t.name),
);
await transport.close();
```
</Tab>
<Tab title="Python">
```python theme={null}
import asyncio
import httpx2
from mcp import Client
from mcp.client.auth.extensions.client_credentials import (
ClientCredentialsOAuthProvider,
)
from mcp.client.streamable_http import streamable_http_client
from mcp.shared.auth import OAuthClientInformationFull, OAuthToken
class InMemoryTokenStorage:
def __init__(self) -> None:
self.tokens: OAuthToken | None = None
self.client_info: OAuthClientInformationFull | None = None
async def get_tokens(self) -> OAuthToken | None:
return self.tokens
async def set_tokens(self, tokens: OAuthToken) -> None:
self.tokens = tokens
async def get_client_info(self) -> OAuthClientInformationFull | None:
return self.client_info
async def set_client_info(self, client_info: OAuthClientInformationFull) -> None:
self.client_info = client_info
provider = ClientCredentialsOAuthProvider(
server_url="https://mcp.example.com/mcp",
storage=InMemoryTokenStorage(),
client_id="my-service",
client_secret="s3cr3t",
scopes="read write",
)
async def main() -> None:
async with httpx2.AsyncClient(auth=provider) as http_client:
transport = streamable_http_client(
"https://mcp.example.com/mcp",
http_client=http_client,
)
async with Client(transport) as client:
# Use the client
tools = await client.list_tools()
print("Available tools:", [t.name for t in tools.tools])
if __name__ == "__main__":
asyncio.run(main())
```
</Tab>
</Tabs>
#### Using a JWT private key
\<Tabs\>
<Tab title="TypeScript">
```typescript theme={null}
import {
Client,
PrivateKeyJwtProvider,
StreamableHTTPClientTransport,
} from "@modelcontextprotocol/client";
const provider = new PrivateKeyJwtProvider({
clientId: "my-service",
privateKey: process.env.CLIENT_PRIVATE_KEY_PEM,
algorithm: "RS256",
});
const client = new Client(
{ name: "my-service", version: "1.0.0" },
{ capabilities: {} },
);
const transport = new StreamableHTTPClientTransport(
new URL("https://mcp.example.com/mcp"),
{ authProvider: provider },
);
await client.connect(transport);
// Use the client
const tools = await client.listTools();
console.log(
"Available tools:",
tools.tools.map((t) => t.name),
);
await transport.close();
```
</Tab>
<Tab title="Python">
```python theme={null}
import asyncio
from pathlib import Path
import httpx2
from mcp import Client
from mcp.client.auth.extensions.client_credentials import (
PrivateKeyJWTOAuthProvider,
SignedJWTParameters,
)
from mcp.client.streamable_http import streamable_http_client
from mcp.shared.auth import OAuthClientInformationFull, OAuthToken
class InMemoryTokenStorage:
def __init__(self) -> None:
self.tokens: OAuthToken | None = None
self.client_info: OAuthClientInformationFull | None = None
async def get_tokens(self) -> OAuthToken | None:
return self.tokens
async def set_tokens(self, tokens: OAuthToken) -> None:
self.tokens = tokens
async def get_client_info(self) -> OAuthClientInformationFull | None:
return self.client_info
async def set_client_info(self, client_info: OAuthClientInformationFull) -> None:
self.client_info = client_info
# Create a signed JWT assertion provider from key parameters
jwt_params = SignedJWTParameters(
issuer="my-service",
subject="my-service",
signing_key=Path("private_key.pem").read_text(),
signing_algorithm="RS256",
lifetime_seconds=300,
)
provider = PrivateKeyJWTOAuthProvider(
server_url="https://mcp.example.com/mcp",
storage=InMemoryTokenStorage(),
client_id="my-service",
assertion_provider=jwt_params.create_assertion_provider(),
scopes="read write",
)
async def main() -> None:
async with httpx2.AsyncClient(auth=provider) as http_client:
transport = streamable_http_client(
"https://mcp.example.com/mcp",
http_client=http_client,
)
async with Client(transport) as client:
# Use the client
tools = await client.list_tools()
print("Available tools:", [t.name for t in tools.tools])
if __name__ == "__main__":
asyncio.run(main())
```
</Tab>
</Tabs> Client support
<Note> Support for this extension varies by client. Extensions are opt-in and never active by default.
Check the client matrix for current implementation status across MCP clients.
Related resources
Last updated Oct 08, 2026