Source Intelligence
Sweep 28 Aug 2026 · 00:00Z Build v2.1.250 478 read Stable v2.1.236 Latest v2.1.250 Next v2.1.250 Feeds RSS JSON llms.txt

DisclaimerUnofficial, and not affiliated with Anthropic. Nearly all of this is read straight out of what ships: npm bundles, captured prompts, published docs. Anthropic's own notes go in verbatim, marked as theirs. The rest is my reading, and every entry carries the strings behind it. If one looks wrong, vote it down and say why.

Page history

OAuth Client Credentials

extensions/auth/oauth-client-credentials

1 recorded change 419 lines First seen Last changed Upstream

History

extensions/auth/oauth-client-credentials First recorded · 419 lines, first recorded

# OAuth Client Credentials ## What it is ## When to use it ## How it works ### JWT Bearer Assertions (recommended) ### Client Secrets ## Implementation guide ### For MCP clients ### For MCP servers ## SDK examples ## Client support ## Related resources

The first capture of this source. The page was already there, and this is what it said.

# 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](https://datatracker.ietf.org/doc/html/rfc6749#section-4.4) to MCP. This enables automated systems to connect to MCP servers without interactive user authorization.

<Card title="Specification" icon="file-lines" href="https://github.com/modelcontextprotocol/ext-auth/blob/main/specification/draft/oauth-client-credentials.mdx">
  Full technical specification for the OAuth Client Credentials extension.
</Card>

## 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](https://datatracker.ietf.org/doc/html/rfc7523), 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.

```mermaid theme={null}
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 URL
* `exp`: Expiration time
* `iat`: 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.

```mermaid theme={null}
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.
</Warning>

## Implementation guide

### For MCP clients

To use the OAuth Client Credentials extension, your client must:

<Steps>
  <Step title="Declare support">
    Include the extension in its per-request capabilities:

    ```jsonc theme={null}
    {
      "jsonrpc": "2.0",
      "id": 1,
      "method": "...",
      "params": {
        // Other fields...
        "_meta": {
          // Other fields...
          "io.modelcontextprotocol/clientCapabilities": {
            "extensions": {
              "io.modelcontextprotocol/oauth-client-credentials": {},
            },
          },
        },
      },
    }
    ```
  </Step>

  <Step title="Obtain an access token">
    Request a token from the authorization server using the client credentials grant before connecting to the MCP server.
  </Step>

  <Step title="Include the token">
    Pass the token in the `Authorization` header of HTTP requests to the MCP server:

    ```
    Authorization: Bearer <access_token>
    ```
  </Step>

  <Step title="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.
  </Step>
</Steps>

### For MCP servers

To accept client credentials tokens, your server must:

<Steps>
  <Step title="Validate the token">
    On each request, verify the JWT signature and claims against your authorization server's public keys (usually via a JWKS endpoint).
  </Step>

  <Step title="Check scopes">
    Ensure the token includes the required scopes for the requested operation.
  </Step>

  <Step title="Advertise support">
    Optionally (but recommended for discoverability), include the extension in the `server/discover` response:

    ```jsonc theme={null}
    {
      "jsonrpc": "2.0",
      "id": 1,
      "result": {
        // Other fields...
        "capabilities": {
          "extensions": {
            "io.modelcontextprotocol/oauth-client-credentials": {},
          },
        },
      },
    }
    ```
  </Step>
</Steps>

## SDK examples

The official MCP SDKs provide built-in support for client credentials authentication. Both handle token acquisition and refresh automatically.

<Steps>
  <Step title="Install the SDK">
    <Tabs>
      <Tab title="TypeScript">
        ```bash theme={null}
        npm install @modelcontextprotocol/client
        ```
      </Tab>

      <Tab title="Python">
        ```bash theme={null}
        pip install mcp
        ```
      </Tab>
    </Tabs>
  </Step>

  <Step title="Create a provider and connect">
    Choose the credential format that matches your setup:

    #### 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: {} },

Cut at 300 lines.