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.
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:
Copy
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();
Copy
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])