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.
Specification
Full technical specification for the OAuth Client Credentials extension.
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.
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.The JWT assertion typically includes:
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.
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.
Request a token from the authorization server using the client credentials grant before connecting to the MCP server.
3
Include the token
Pass the token in the Authorization header of HTTP requests to the MCP server:
Authorization: Bearer <access_token>
4
Handle token refresh
Client credentials tokens typically have shorter lifetimes than user-delegated tokens. Implement token refresh logic to obtain a new token before expiry.
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 clientconst tools = await client.listTools();console.log( "Available tools:", tools.tools.map((t) => t.name),);await transport.close();
from mcp.client.auth.extensions.client_credentials import ( ClientCredentialsOAuthProvider,)from mcp.client.streamable_http import streamablehttp_clientfrom mcp import ClientSessionprovider = ClientCredentialsOAuthProvider( server_url="https://mcp.example.com/mcp", client_id="my-service", client_secret="s3cr3t", scopes="read write",)async with streamablehttp_client( "https://mcp.example.com/mcp", auth_provider=provider,) as (read_stream, write_stream, _): async with ClientSession(read_stream, write_stream) as session: await session.initialize() # Use the client tools = await session.list_tools() print("Available tools:", [t.name for t in tools.tools])