> ## Documentation Index
> Fetch the complete documentation index at: https://docs.albus.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Authenticate Albus to your MCP server

> Choose Albus identity JWTs, OAuth client credentials, static bearer tokens, or custom headers for your hosted MCP server.

## Authentication mechanisms

Albus supports four mechanisms for authenticating requests from the Albus
platform to your hosted MCP servers:

| Mechanism                                                       | Use when                                                                                                                                           |
| --------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Albus identity JWT (default)](#1-albus-identity-jwt-default)   | Your server can verify tokens issued by Albus for your organization. No shared secret is required.                                                 |
| [OAuth 2.0 client credentials](#2-oauth-2-0-client-credentials) | Your server already trusts an identity provider, and Albus should obtain an access token from the provider at launch.                              |
| [Static bearer token](#3-static-bearer-token)                   | Your server accepts a fixed token in `Authorization: Bearer <token>`.                                                                              |
| [Custom headers](#4-custom-headers)                             | Your server requires an API key, another authorization scheme, a deployment bypass header, or an access token minted by the caller before the run. |

The default example below provides the complete agent configuration and run
request. Each alternative shows a diff against the default MCP server entry;
apply only the diff for the mechanism your server requires.

## 1. Albus identity JWT (default)

Use the default mechanism to authenticate Albus without provisioning a shared
secret or registering Albus as a client with your identity provider.

A server entry with no `auth` field defaults to
`"auth": {"type": "albus_identity_jwt"}`: every request to the server
includes the header `Authorization: Bearer <token>`, where `<token>` is an
identity token minted by Albus for your organization and the declared server
URL.

Authenticate the CLI or SDK with `ALBUS_API_KEY` as described in
[Authentication](/getting-started/authentication). Replace the example MCP
URL with your server's Streamable HTTP endpoint.

<CodeGroup>
  ```json agent.json theme={null}
  {
    "model": { "name": "gemini-3.6-flash" },
    "mcp_servers": [
      {
        "name": "acme",
        "url": "https://mcp.acme.com/api/mcp"
      }
    ]
  }
  ```

  ```bash CLI theme={null}
  albus sessions run support-1234 -p "List your tools." \
    --agent-name support-triage --agent-file agent.json
  ```

  ```python Python theme={null}
  import os

  from albus_sdk import Albus

  albus = Albus(api_key=os.environ["ALBUS_API_KEY"])

  response = albus.sessions.run_session(
      id="support-1234",
      agent_name="support-triage",
      user_prompt="List your tools.",
      agent={
          "model": {"name": "gemini-3.6-flash"},
          "mcp_servers": [
              {
                  "name": "acme",
                  "url": "https://mcp.acme.com/api/mcp",
              }
          ],
      },
      wait_timeout_seconds=120,
  )
  ```

  ```typescript TypeScript theme={null}
  import { Albus } from "@albus-ts/sdk";

  const albus = new Albus();

  const response = await albus.sessions.runSession({
    id: "support-1234",
    waitTimeoutSeconds: 120,
    body: {
      agentName: "support-triage",
      userPrompt: "List your tools.",
      agent: {
        model: { name: "gemini-3.6-flash" },
        mcpServers: [
          {
            name: "acme",
            url: "https://mcp.acme.com/api/mcp",
          },
        ],
      },
    },
  });
  ```
</CodeGroup>

Configure your server or gateway to verify the signature, expiry, issuer,
and audience of every identity token:

| Setting  | Value                                                                                                                         |
| -------- | ----------------------------------------------------------------------------------------------------------------------------- |
| Issuer   | `https://oidc.albus.sh/organizations/<org-id>`, shown under **Settings → Organization** and returned as `Organization.issuer` |
| JWKS URL | `<issuer>/.well-known/jwks.json`                                                                                              |
| Audience | The exact `url` in the MCP server entry, including any trailing slash or query string                                         |

Discovery is available at `<issuer>/.well-known/openid-configuration`.

<AccordionGroup>
  <Accordion title="Identity token claims">
    The identity token is an ES256 JWT with exactly the following six claims:

    ```json theme={null}
    {
      "iss": "https://oidc.albus.sh/organizations/42",
      "aud": "https://mcp.acme.com/api/mcp",
      "sub": "0d4f2f9a-6c1e-4b7e-9a3b-8f1c2d3e4f50",
      "session": "7b1a9c2e-3d4f-4a5b-8c6d-9e0f1a2b3c4d",
      "iat": 1758500000,
      "exp": 1758504500
    }
    ```

    | Claim            | Meaning                                                                                                                                                                                                                                                                                                                                                            |
    | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
    | `iss`            | Your organization's issuer, `https://oidc.albus.sh/organizations/<org-id>`. Every organization has a distinct issuer, so a token issued for another Albus organization never verifies against your verifier — the issuer is the tenant check. The console shows the issuer under **Settings → Organization**; the API returns the issuer as `Organization.issuer`. |
    | `aud`            | The exact `url` string declared for the server. A token minted for one server is rejected by the verifier of every other server.                                                                                                                                                                                                                                   |
    | `sub`, `session` | The invocation id and the session id of the run making the call, for audit logs and per-session limits.                                                                                                                                                                                                                                                            |
    | `iat`, `exp`     | Issue and expiry time. The identity token is minted once per invocation and is valid for about 75 minutes.                                                                                                                                                                                                                                                         |

    The issuer serves the standard discovery documents, public and cached for one
    hour:

    * `<issuer>/.well-known/openid-configuration`
    * `<issuer>/.well-known/jwks.json`
  </Accordion>

  <Accordion title="Verify with Next.js on Vercel: mcp-handler and jose">
    `withMcpAuth` from `mcp-handler` calls your verifier function with the bearer
    token and answers `401` when the verifier function returns `undefined`.
    Verify the token with `jose`:

    ```bash theme={null}
    npm install jose
    ```

    ```typescript app/api/mcp/route.ts theme={null}
    import type { AuthInfo } from "@modelcontextprotocol/server";
    import { createRemoteJWKSet, jwtVerify } from "jose";
    import { createMcpHandler, withMcpAuth } from "mcp-handler";

    const ALBUS_ISSUER = "https://oidc.albus.sh/organizations/42"; // Settings → Organization
    const THIS_SERVER = "https://mcp.acme.com/api/mcp"; // the url in your agent config
    const ALBUS_JWKS = createRemoteJWKSet(
      new URL(`${ALBUS_ISSUER}/.well-known/jwks.json`),
    );

    const verifyToken = async (
      _req: Request,
      bearerToken?: string,
    ): Promise<AuthInfo | undefined> => {
      if (!bearerToken) return undefined;
      try {
        const { payload } = await jwtVerify(bearerToken, ALBUS_JWKS, {
          issuer: ALBUS_ISSUER,
          audience: THIS_SERVER,
          algorithms: ["ES256"],
        });
        return {
          token: bearerToken,
          clientId: ALBUS_ISSUER,
          scopes: [],
          extra: { session: payload.session, invocation: payload.sub },
        };
      } catch {
        return undefined;
      }
    };

    const handler = createMcpHandler((server) => {
      // server.registerTool(...)
    });

    const authHandler = withMcpAuth(handler, verifyToken, { required: true });

    export { authHandler as GET, authHandler as POST };
    ```

    `jwtVerify` checks the signature against the JWKS and validates the `exp`,
    `iss`, and `aud` claims. Tool handlers read the session id and invocation id
    from `ctx.http?.authInfo.extra`.
  </Accordion>

  <Accordion title="Verify with Go: coreos/go-oidc">
    `oidc.NewProvider` reads the discovery document from the issuer; the
    `ClientID` field of `oidc.Config` is the expected audience.

    ```go theme={null}
    import "github.com/coreos/go-oidc/v3/oidc"

    provider, err := oidc.NewProvider(ctx, "https://oidc.albus.sh/organizations/42")
    if err != nil {
        return err
    }

    verifier := provider.Verifier(&oidc.Config{
        ClientID: "https://mcp.acme.com/api/mcp", // the url in your agent config
    })

    token, err := verifier.Verify(ctx, bearerToken) // signature, exp, iss, aud
    ```
  </Accordion>

  <Accordion title="Multiple organizations and key rotation">
    If your server is used by more than one Albus organization, keep an allowlist
    of the issuers you trust and configure a verifier for each issuer. Select the
    verifier by the token's `iss` claim only when the `iss` value is in the
    allowlist. Never fetch a JWKS from an issuer you did not configure: whoever
    controls the `iss` value would then control the signing key.

    Signing keys rotate by `kid`. Configure the verifier to select the key by
    `kid` and refetch the JWKS when a token contains an unknown `kid`. New keys
    are published before use; old keys remain published until tokens signed with
    the old keys have expired. Do not pin a key or cache the JWKS beyond the
    response's cache headers.
  </Accordion>
</AccordionGroup>

## 2. OAuth 2.0 client credentials

If your server already requires tokens from Auth0, Okta, WorkOS, or another
OAuth 2.0 provider, use `oauth2_client_credentials` to keep the server's
existing verification. Albus calls `token_url` with the client credentials
when the run starts and sends the returned access token as
`Authorization: Bearer <access-token>`.

Store the client secret as an Albus secret first (`client_id` may be a secret
reference or a literal):

```bash theme={null}
printf '%s' "$CLIENT_SECRET" | albus secrets create idp/acme-mcp/client-secret
```

<CodeGroup>
  ```diff CLI (agent.json) theme={null}
   {
     "name": "acme",
  -  "url": "https://mcp.acme.com/api/mcp"
  +  "url": "https://mcp.acme.com/api/mcp",
  +  "auth": {
  +    "type": "oauth2_client_credentials",
  +    "token_url": "https://acme.us.auth0.com/oauth/token",
  +    "client_id": "albus-agents",
  +    "client_secret": "albus.sh/secrets/idp/acme-mcp/client-secret",
  +    "audience": "https://mcp.acme.com/api/mcp"
  +  }
   }
  ```

  ```diff Python theme={null}
   {
       "name": "acme",
       "url": "https://mcp.acme.com/api/mcp",
  +    "auth": {
  +        "type": "oauth2_client_credentials",
  +        "token_url": "https://acme.us.auth0.com/oauth/token",
  +        "client_id": "albus-agents",
  +        "client_secret": "albus.sh/secrets/idp/acme-mcp/client-secret",
  +        "audience": "https://mcp.acme.com/api/mcp",
  +    },
   }
  ```

  ```diff TypeScript theme={null}
   {
     name: "acme",
     url: "https://mcp.acme.com/api/mcp",
  +  auth: {
  +    type: "oauth2_client_credentials",
  +    tokenUrl: "https://acme.us.auth0.com/oauth/token",
  +    clientId: "albus-agents",
  +    clientSecret: "albus.sh/secrets/idp/acme-mcp/client-secret",
  +    audience: "https://mcp.acme.com/api/mcp",
  +  },
   }
  ```
</CodeGroup>

| Field                | Meaning                                                    |
| -------------------- | ---------------------------------------------------------- |
| `token_url`          | The provider's token endpoint. Must be a public HTTPS URL. |
| `client_id`          | A secret reference or a literal.                           |
| `client_secret`      | A secret reference; a raw value is rejected.               |
| `audience`, `scopes` | Optional; passed to the provider as given.                 |

Albus obtains the access token once, at launch, and does not refresh the access
token during the run. The access token must be valid for at least 10 minutes or
the launch fails. If the access token expires during a long run, every `401`
from the server appears in the audit log as `mcp_auth_rejected` and as a tool
error visible to the model.

## 3. Static bearer token

Use `bearer` when your server accepts a fixed access token and does not need
an identity provider exchange. Albus resolves the secret at launch and sends
`Authorization: Bearer <secret>` on every MCP request.

Store the token without the `Bearer` prefix:

```bash theme={null}
printf '%s' "$MCP_TOKEN" | albus secrets create acme-mcp-token
```

<CodeGroup>
  ```diff CLI (agent.json) theme={null}
   {
     "name": "acme",
  -  "url": "https://mcp.acme.com/api/mcp"
  +  "url": "https://mcp.acme.com/api/mcp",
  +  "auth": { "type": "bearer", "token": "albus.sh/secrets/acme-mcp-token" }
   }
  ```

  ```diff Python theme={null}
   {
       "name": "acme",
       "url": "https://mcp.acme.com/api/mcp",
  +    "auth": {"type": "bearer", "token": "albus.sh/secrets/acme-mcp-token"},
   }
  ```

  ```diff TypeScript theme={null}
   {
     name: "acme",
     url: "https://mcp.acme.com/api/mcp",
  +  auth: { type: "bearer", token: "albus.sh/secrets/acme-mcp-token" },
   }
  ```
</CodeGroup>

`auth.token` must be a secret reference; a raw token is rejected.

## 4. Custom headers

Use `headers` when your server requires an API key in a custom header,
another authorization scheme, or a token the caller obtains before the run.
Header values can be secret references or literals.

For an `X-API-Key` header, store the key as a secret:

```bash theme={null}
printf '%s' "$MCP_API_KEY" | albus secrets create acme-api-key
```

<CodeGroup>
  ```diff CLI (agent.json) theme={null}
   {
     "name": "acme",
  -  "url": "https://mcp.acme.com/api/mcp"
  +  "url": "https://mcp.acme.com/api/mcp",
  +  "headers": { "X-API-Key": "albus.sh/secrets/acme-api-key" }
   }
  ```

  ```diff Python theme={null}
   {
       "name": "acme",
       "url": "https://mcp.acme.com/api/mcp",
  +    "headers": {"X-API-Key": "albus.sh/secrets/acme-api-key"},
   }
  ```

  ```diff TypeScript theme={null}
   {
     name: "acme",
     url: "https://mcp.acme.com/api/mcp",
  +  headers: { "X-API-Key": "albus.sh/secrets/acme-api-key" },
   }
  ```
</CodeGroup>

Headers other than `Authorization` combine with any `auth` mode. An
`Authorization` entry in `headers` (in any casing) disables the default
identity token and is sent verbatim, so the value must be the complete header
value, scheme included (`Bearer <token>` or `token <token>`). A request with
both an explicit `auth` block and a `headers.Authorization` entry is rejected
with `400`.

### Caller-minted tokens

If your application already holds credentials for the server's identity
provider, mint a short-lived access token before calling `run_session` and
pass the access token as a literal `headers.Authorization` value. Your server
keeps trusting the same provider, and the provider credentials stay with your
application.

With the minted access token in `MCP_ACCESS_TOKEN`, apply this change to the
default example before submitting the run. The CLI command uses `jq` to add
the header to the complete agent configuration.

<CodeGroup>
  ```diff CLI theme={null}
  +jq --arg token "$MCP_ACCESS_TOKEN" \
  +  '.mcp_servers[0].headers.Authorization = ("Bearer " + $token)' \
  +  agent.json > agent-run.json
   albus sessions run support-1234 -p "List your tools." \
  -  --agent-name support-triage --agent-file agent.json
  +  --agent-name support-triage --agent-file agent-run.json
  ```

  ```diff Python theme={null}
   {
       "name": "acme",
       "url": "https://mcp.acme.com/api/mcp",
  +    "headers": {"Authorization": f"Bearer {os.environ['MCP_ACCESS_TOKEN']}"},
   }
  ```

  ```diff TypeScript theme={null}
   {
     name: "acme",
     url: "https://mcp.acme.com/api/mcp",
  +  headers: { Authorization: `Bearer ${process.env.MCP_ACCESS_TOKEN}` },
   }
  ```
</CodeGroup>

Albus sends the header value unchanged and does not refresh the access token.
Mint a token whose lifetime exceeds the run. A literal header value is not
stored: the agent revision records the header name with the value
`"<literal>"`, so a token minted per run does not create a new revision, and a
retry with the same idempotency key and a freshly minted token re-attaches to
the original run.

### Vercel Deployment Protection

Store the deployment bypass secret with Albus and use the header
`x-vercel-protection-bypass` with a value such as
`albus.sh/secrets/acme-vercel-bypass`. The bypass header lets the request
reach your handler; the handler still verifies the identity token to
authorize the request. Leave `auth` at its default to use both checks.

For `401` responses recorded as `mcp_auth_rejected`, check the issuer and
audience, the complete `Authorization` value, and the token's expiry. See
[Troubleshooting](/guides/troubleshooting#secrets-and-mcp).
