Machine-to-Machine (M2M) MCP Authentication with the Client Credentials Grant
When there is no user to send to a consent screen, the agent authenticates as itself: it asks your identity provider for a token, then presents that token to the MCP server.
- Method
- Client credentials (M2M)
- Platform
- Any platform
- MCP Specification
- 2026-07-28
How it works
The agent posts grant_type=client_credentials — or a JWT assertion signed with its own private key — to your identity provider's token endpoint, gets an access token back, and sends it as Authorization: Bearer <token> on every MCP request. There is an official MCP extension for this, io.modelcontextprotocol/oauth-client-credentials, and the TypeScript and Python SDKs ship providers that do the token fetch and refresh for you. It is still Draft, and the extension support matrix lists no client that implements it: ChatGPT and Claude both document that they do not support machine-to-machine grants. So the extension is for agents you write — and the degenerate version, fetch the token yourself and set the header, works in every SDK that takes headers.
- Best for
- Cron jobs, CI steps and background workers, where no user is present to consent
- MCP spec
- An official MCP extension, still Draft — shipped in the TypeScript and Python SDKs
- Works with
- Your own agents — the extension support matrix lists no client that implements it
- Effort
- An afternoon, if your IdP already issues client-credentials tokens
The exchange
Each step in the exchange, in order.
- Your agent cron job, CI step
- Identity provider token endpoint
- MCP server verifies via JWKS
-
No browser and no user. The credential is the client's own — a secret, or a JWT signed with its private key, which the extension recommends instead. This is the step the spec's interactive flow has no equivalent for, and the reason that flow fails in CI.
Your agent to Identity provider
POST /oauth2/token grant_type=client_credentials -
Identity provider to Your agent
access_token aud = https://mcp.example.com/mcp -
Your agent to MCP server
POST /mcp Authorization: Bearer <access-token> -
Ordinary JWT validation: signature, audience, expiry, scope. A server that already accepts OAuth tokens from users needs no new code path for these, only a decision about which subjects are services.
Inside MCP server
Verify the signature against the IdP's JWKS -
Inside MCP server
Check aud and scope, then read the caller sub = my-service -
MCP server to Your agent
200 OK tool result
Connect your agent
Pick your language and SDK.
When to use something else
Use this when
- A scheduled job, a CI step or a background worker makes the call, and there is nobody to send to a consent screen.
- Your identity provider already issues client-credentials tokens, so the MCP server validates them with the JWKS work it is already doing.
- You want the credential to expire on its own: the extension notes these tokens are typically shorter-lived than user-delegated ones, where an API key lasts until somebody rotates it.
Use something else when
- The server has to be reachable from Claude or ChatGPT: both document that they do not support machine-to-machine grants.
- The call is made on behalf of a named person and the API behind the server enforces per-user permissions.
- The agent and the MCP server run in the same cloud, where workload identity issues the token and there is no secret at all.
The core specification makes authorization OPTIONAL and describes only the interactive authorization-code flow. Where it acknowledges a client acting on its own behalf, it is to excuse it: in the step-up rules, client_credentials clients MAY "abort the request immediately" rather than re-authorize. That sentence is identical in 2025-11-25 and in 2026-07-28, the current protocol version. Machine-to-machine auth sits outside both, in the io.modelcontextprotocol/oauth-client-credentials extension, still in the draft directory of the ext-auth repository.
What lands on you next
A client-credentials token proves which service is calling. It says nothing about which tools that service may call, how often, or what reaches the API behind your server.
-
The token carries
scope: mcp:tools. Is anything checking that? -
jwt-scopes-inboundOnly if something reads it. This policy requires the token's space-delimited
scopeclaim to contain every scope you list, and refuses the call at the route before your server runs. -
Sales wants these tools in Claude. Claude will not do client credentials.
-
mcp-oauth-inboundThe inbound method is a policy on the route rather than code in your server, so an interactive sign-in against the identity provider you already run is configuration. Exactly one such policy per project.
-
The scheduler retried all night against one token. Who stops it?
-
rate-limit-inboundWith
rateLimitBy: "user"every call is counted against the authenticated subject rather than a shared egress IP, so one looping service account cannot spend everyone's budget. -
Can we forward this token to the API behind the MCP server?
-
mcp-token-exchange-inboundThe spec forbids passthrough with a MUST NOT. Inbound auth headers are stripped and an independent upstream credential is used, so the token you present is never the token that leaves.
All of these attach to one MCP route's policies.inbound — the same policy engine on the way in and on the way out.
Common questions
Does MCP support machine-to-machine authentication?
Yes, through an official extension rather than the core specification: io.modelcontextprotocol/oauth-client-credentials. It is still Draft — it lives in the draft directory of the ext-auth repository — and the extension support matrix on modelcontextprotocol.io currently lists no client that implements it. The official TypeScript and Python SDKs do.
Why does my MCP server connect from my desktop but fail in CI?
Because the spec's flow is interactive. It opens a browser, a human signs in, and a human approves the consent screen. In a scheduled job there is no browser and nobody to click, so the flow hangs or fails at the redirect. That is the gap client credentials exists to close.
Do I need the extension to do M2M today?
No, and mostly you should not wait for it. Request a token from your identity provider's token endpoint yourself and set Authorization: Bearer on the MCP client. Every agent SDK that accepts headers supports that today, and the server sees an ordinary bearer token either way.
What is the difference between the client credentials flow and the authorization code flow?
Who the token represents. Authorization code produces a token for a user who signed in and consented, which needs a browser. Client credentials produces a token for the calling application itself, from a single request to the token endpoint. The two are sometimes called three-legged and two-legged OAuth.
Should I use a client secret or private_key_jwt?
The extension recommends JWT bearer assertions, defined in RFC 7523: the client signs a short-lived assertion with its private key and the authorization server validates it against the registered public key. A client secret is a long-lived credential that authenticates as your application until it is rotated, so the extension's guidance is to prefer assertions where you can.
Is a client-credentials token the same thing as a service account?
In practice, usually yes — service account, machine identity, workload credential and M2M client are the same idea under different vendor names. What differs is where the key material lives. A cloud workload identity has no stored secret at all: the platform mints a token from the workload's ambient identity, so there is nothing to rotate or leak.
Can ChatGPT or Claude connect to a server that requires this?
No. OpenAI's plugin authentication documentation states that ChatGPT does not support machine-to-machine OAuth grants such as client credentials, service accounts, or JWT bearer assertions. Anthropic's connector documentation is as direct: a pure machine-to-machine client_credentials grant is not supported, and every connection requires user consent.
What changed for M2M in the 2026-07-28 spec release?
Nothing about this mechanism: client credentials was an extension before the release and is an extension after it, still Draft. What did change around it is that Dynamic Client Registration is now deprecated in favour of Client ID Metadata Documents, and the initialize handshake and protocol-level sessions are gone — so a headless client that used DCR to self-register has one more reason to stop.
One policy engine for APIs, AI, and MCP
Put your MCP servers behind a gateway that speaks every identity provider, filters tools per role, and logs every call.