Zuplo
On this page

Machine-to-machine (M2M) MCP authentication with the client credentials grant

With no user to send to a consent screen, the agent authenticates as itself: it asks your identity provider for a token and 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. It sends the access token it gets back as Authorization: Bearer ACCESS_TOKEN on every MCP request. An official MCP extension covers 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 don't support machine-to-machine grants. So the extension is for agents you write. Fetching the token yourself and setting 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
Specification
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 identity provider already issues client-credentials tokens

The exchange

  • Your agent cron job, CI step
  • Identity provider token endpoint
  • MCP server verifies via JWKS
  1. 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. The specification's interactive flow has no equivalent step, which is why it fails in CI.

    Your agent to Identity provider

    POST /oauth2/token (carries the credential) grant_type=client_credentials
  2. Identity provider to Your agent

    access_token (carries the credential) aud = https://mcp.example.com/mcp
  3. Your agent to MCP server

    POST /mcp (carries the credential) Authorization: Bearer ACCESS_TOKEN
  4. 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 provider's JWKS
  5. Inside MCP server

    Check aud and scope, then read the caller sub = my-service
  6. MCP server to Your agent

    200 OK tool result
The agent authenticates as itself at the identity provider's token endpoint using a client secret or a signed JWT assertion, receives a short-lived access token, and presents it as a bearer token on the MCP request. The MCP server verifies the signature against the identity provider's JWKS, checks the audience and scope, and reads the calling service from the subject claim. No browser and no user appear anywhere in the flow.

Connect your agent

Pick your language and SDK.

Language
SDK
TypeScriptagent.ts
import {
  Client,
  ClientCredentialsProvider,
  StreamableHTTPClientTransport,
} from "@modelcontextprotocol/client";

const provider = new ClientCredentialsProvider({
  clientId: "my-service",
  clientSecret: process.env.MCP_CLIENT_SECRET!,
});

const client = new Client(
  { name: "my-service", version: "1.0.0" },
  { capabilities: {} },
);

// `authProvider` is what triggers the token request — there is no
// header to set. Swap in PrivateKeyJwtProvider for RFC 7523.
const transport = new StreamableHTTPClientTransport(
  new URL("https://mcp.example.com/mcp"),
  { authProvider: provider },
);

await client.connect(transport);
const { tools } = await client.listTools();

When to use something else

Use this when

  • A scheduled job, a CI step, or a background worker makes the call, with 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 don't support machine-to-machine grants.
  • The agent calls 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 — wording 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.

Zuplo

A client-credentials token proves which service is calling, and nothing about which tools that service can call, how often, or what reaches the API behind your server.

  1. Step 1.

    Point callers at the gateway

    Your identity provider stays where it is: the gateway becomes the OAuth server your callers talk to and delegates the login back to that same IdP. The token on the wire is one the gateway issued, which is what makes the rest of this list possible.

    "url": "https://api.example.com/mcp"
  2. Step 2.

    Add the policies

    A list on the route, and the options that go with it. No SDK, no middleware, nothing imported into your server.

    "inbound": [
      "mcp-oauth-inbound",
      "require-user-claims-inbound",
      "rate-limit-inbound",
      "mcp-token-exchange-inbound"
    ]
  3. Step 3.

    Deploy

    Your MCP server keeps its URL, its authentication, and its code, and never restarts.

    zuplo deploy

The token proves a service is calling. Is anything checking which?

require-user-claims-inbound

Only if you say so, on the route: match the caller's claims and the call is refused with a 403 before your server runs. Scope is not the axis — the gateway mints exactly one, mcp:tools, on every token.

Sales wants these tools in Claude. Claude will not do client credentials.

mcp-oauth-inbound

The sign-in method is a policy on the route, not code in your server, so interactive OAuth against the identity provider you already run is a configuration change. Exactly one MCP OAuth policy per project.

The scheduler retried all night against one token. Who stops it?

rate-limit-inbound

The route does. Cap calls per authenticated subject and the looping service account exhausts its own budget instead of everyone's — the limit follows the identity, not the egress IP.

Can we forward this token to the API behind the MCP server?

mcp-token-exchange-inbound

No — the specification forbids that with a MUST NOT, and the gateway enforces it for you: inbound auth is stripped, and an independent upstream credential goes out in its place.

All of these attach to one MCP route's policies.inbound — the same policy engine on the way in and on the way out. Your MCP server keeps the code and the authentication it has today.

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, in the draft directory of the ext-auth repository, and the extension support matrix on modelcontextprotocol.io 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 specification's flow is interactive: it opens a browser, a person signs in, and that person approves a consent screen. A scheduled job has no browser and nobody to click, so the flow hangs or fails at the redirect. Client credentials is what closes that gap.

Do I need the extension to do M2M today?

No. 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, 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.

A client secret or private_key_jwt — which one?

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 long-lived and authenticates as your application until somebody rotates it, so prefer assertions where you can.

Is a client-credentials token the same thing as a service account?

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 — 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 doesn't support machine-to-machine OAuth grants such as client credentials, service accounts, or JWT bearer assertions. Anthropic's connector documentation says the same: 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 specification release?

Nothing about this mechanism: client credentials was an extension before the release and still is, still Draft. Around it, Dynamic Client Registration is deprecated in favor 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.