# IdP-Managed MCP Access

Your identity provider decides who can use Bumpkit's [MCP server](/docs/mcp) from MCP clients like Claude and Codex. Employees sign in to the MCP client with corporate SSO and start using Bumpkit tools. They never see a Bumpkit sign-in or consent screen, and Bumpkit checks their membership on every request.

This is the MCP enterprise-managed authorization extension (`io.modelcontextprotocol/enterprise-managed-authorization`). Your identity provider issues a short-lived Identity Assertion JWT Authorization Grant (ID-JAG) for Bumpkit, the MCP client trades it at Bumpkit's token endpoint for an access token, and Bumpkit maps the person to a member of your workspace.

> **Beta, early access.** Flag: `idp_managed_mcp`. It also needs [single sign-on](/docs/sso#single-sign-on) (flag `sso`) with an OIDC connection turned on. Until your workspace has access, the **IdP-managed MCP access** card doesn't appear on the Single sign-on page and `/v1/identity/sso/managed-mcp` answers `403 FEATURE_EARLY_ACCESS` with a link to request it. Request access at `https://dashboard.bumpkit.dev/early-access?feature=idp_managed_mcp`.

## When to use it

- **You want one place to grant and revoke access.** Assign Bumpkit to people or groups in your identity provider; remove them there and their MCP access ends.
- **You don't want each employee to approve a consent screen.** Admins approve Bumpkit once for the whole organization.
- **You want MCP access to follow corporate SSO policy.** Sign-in, MFA and device rules stay in your identity provider.

Keep using per-person sign-in (the default in [Connect Claude, Codex or Grok](/docs/mcp#connect-claude-codex-or-grok-in-one-minute)) if your identity provider can't issue ID-JAGs or your MCP client doesn't support enterprise-managed authorization yet. You can run both side by side until you [require](#set-it-up) IdP-managed access.

## How it works

1. An employee signs in to the MCP client with corporate SSO.
2. When the client needs Bumpkit, it asks your identity provider for an ID-JAG for Bumpkit. The identity provider checks that the person is assigned to Bumpkit.
3. The client sends the ID-JAG, with the client ID and secret you registered in Bumpkit, to `https://bumpkit.dev/oauth/token`.
4. Bumpkit checks the signature against your identity provider's published keys, checks the ID-JAG is for Bumpkit and hasn't been used before, finds the member, and returns an access token for `https://bumpkit.dev/mcp`. The token lasts up to 1 hour, and never more than 1 hour past the ID-JAG's own expiry. There's no refresh token: the client gets a new ID-JAG when it needs a new token.
5. Every MCP request re-checks that the member is still active in the workspace, that IdP-managed access is still on, and that the identity provider is still your SSO provider. The member's current role applies.

## Set it up

You need admin access to your identity provider and an owner or admin role in Bumpkit; the first single sign-on setup needs an owner. Only an owner can change the tenant or Athenz domain, turn this off, or stop requiring it.

### 1. Turn on single sign-on with OIDC

IdP-managed access trusts the same identity provider your workspace already uses for [OIDC single sign-on](/docs/sso#single-sign-on). The issuer in every ID-JAG must match the **OIDC issuer URL** in **Settings → Single sign-on** exactly. Set up and [verify your email domains](/docs/sso#single-sign-on) too: Bumpkit uses them to match people the first time.

### 2. Turn on IdP-managed MCP access

In **Settings → Single sign-on**, find the **IdP-managed MCP access** card, check **Accept ID-JAGs from this identity provider** and click **Save**. The card shows the values your identity provider needs:

| Value on the card | Example | Where it goes |
|---|---|---|
| Issuer | `https://acme.okta.com` | Your OIDC issuer; the `iss` of every ID-JAG |
| Audience | `https://bumpkit.dev` | The ID-JAG's `aud`: Bumpkit's authorization server |
| Resource | `https://bumpkit.dev/mcp` | The resource the ID-JAG is for |
| Token endpoint | `https://bumpkit.dev/oauth/token` | Where the MCP client trades the ID-JAG |
| Audience tenant (`aud_tenant`) | Your workspace ID | Optional; if the identity provider sends it, it must be this workspace ID or the Audience value |
| Scopes | `mcp`, `mcp:read` | See [Roles and scopes](#roles-and-scopes) |

Then register each MCP client your organization uses. Under **MCP clients**, type a name such as `Claude` and click **Register client**. Bumpkit shows the client ID (`bkmcp_ema_...`) and client secret (`bkmcp_emas_...`). If your identity provider always puts its own client ID in the ID-JAG, as ThunderID and Athenz do, enter that ID under **Client ID from your IdP** when you register: Bumpkit uses it as the client ID and still generates the secret. A client ID can be up to 255 letters, digits and `. _ : / @ + ~ -`, and each one can be registered once per workspace. Copy the secret now: it's shown once and Bumpkit stores only a hash. Put the client ID in your identity provider (step 3) and give the ID and secret to whoever configures the MCP client's enterprise-managed authorization settings. Remove a client to cut off every token issued to it.

From the CLI:

```bash
bumpkit identity sso managed-mcp view
bumpkit identity sso managed-mcp clients add Claude
bumpkit identity sso managed-mcp clients add ThunderID --client-id thunder-app
```

and `bumpkit identity sso managed-mcp set --enabled [--required] [--tenant TENANT] [--role-domain DOMAIN] --yes` to turn it on. **Identity provider tenant** (`--tenant`) is only for issuers that serve several tenants from one issuer and put a `tenant` claim in ID-JAGs. **Athenz domain** (`--role-domain`) is only for Athenz. See [CLI and MCP](#cli-and-mcp) for every command.

### 3. Add Bumpkit as a resource app in your identity provider

In your identity provider, set Bumpkit up as the resource (the app the MCP client gets access to), point it at the audience and resource above, enter the client ID from step 2 for the MCP client, define the scopes `mcp` and `mcp:read`, and assign the people or groups who may use Bumpkit. Follow the [guide for your identity provider](#identity-provider-guides).

Before you roll it out, check one ID-JAG. Paste it under **Test an ID-JAG** on the card and click **Check**, or pipe it to `bumpkit identity sso managed-mcp test`. Bumpkit says whether it would accept it, as which member and role and with which scope, or which error to fix. The test doesn't use the ID-JAG up, so the client can still trade it.

### 4. Optionally require it

Check **Require it for verified domains** and click **Save** (or add `--required`). For people whose email is on one of your verified domains, Bumpkit then turns off its own sign-in for MCP clients:

- New sign-ins and consent through Bumpkit's sign-in page are refused with `access_denied`.
- MCP connections they already approved stop working on their next request, and so do their refresh tokens.
- Their personal API keys can't connect to `https://bumpkit.dev/mcp`; the `401` says `access_denied`. API keys still work with the CLI and the REST API.

This applies to everyone whose email is on a domain your workspace verified, whichever workspace they use. Requiring it needs IdP-managed access turned on and at least one verified domain.

## What employees do

1. Sign in to the MCP client with corporate SSO, the way your organization set it up.
2. Ask the client to use a Bumpkit tool, like `status` or `release_check`.
3. That's it: there's no Bumpkit sign-in, workspace picker or Allow button. The client connects as them, in your workspace, with their role.

The first time, Bumpkit matches the person to a member by the identity provider's account ID if they've signed in to Bumpkit with SSO before, or else by their email. Email matching needs the ID-JAG to carry `email` with `email_verified: true`, on one of your verified domains. If there's no member yet and **Create accounts on first sign-in** is on for your SSO connection, Bumpkit adds them with the **Role for new members**. After that, the identity provider account and the member stay linked, and Bumpkit never moves the link to another member. A linked member doesn't need a verified domain or an email in the ID-JAG, so removing a domain doesn't cut them off; deactivate them or clear their link to do that. A deactivated member is refused before anything else is checked. Single sign-on and IdP-managed access share one link and the same rule, so signing in once with either links both. Changing the OIDC issuer, client ID or **Identity provider tenant** clears every link, and people link again by verified email the next time they sign in. To clear one person's link, see [single sign-on](/docs/sso#single-sign-on). Once you set a tenant, dashboard sign-in also needs a matching `tenant` claim in ID tokens. Bumpkit only matches by email on domains this workspace verified, and a domain can be verified by one workspace only.

### Offboarding

- **With [SCIM](/docs/sso#scim-provisioning):** when your identity provider deactivates someone, Bumpkit refuses their next MCP request.
- **Without SCIM:** once you unassign or deactivate someone in your identity provider, it stops issuing them ID-JAGs, so they can't get new tokens. A token they already have works until it expires, up to 1 hour, unless you also remove them on the **Team** page, which stops their next request.
- Turning off IdP-managed access, changing the OIDC issuer, or removing the client stops every token issued through it on the next request.
- Removing a member in Bumpkit stops their tokens on the next request and keeps them out, even if **Create accounts on first sign-in** is on and your identity provider still grants them access. To bring them back, invite them again or provision them through SCIM. They join with the invited role, and a canceled or expired invitation doesn't bring them back.

## Roles and scopes

A member's role comes from Bumpkit: from their [SCIM group mapping](/docs/sso#groups-to-roles), from the role you gave them on the Team page, or from the connection's **Role for new members** for people added on first use. The identity provider can only narrow access, never widen it.

| Scope in the ID-JAG | Access |
|---|---|
| `mcp` | The member's role |
| `mcp:read` | Read-only: members who can make changes, admins included, act as `viewer`, and the client lists only read tools |
| No `scope` claim | Same as `mcp` |
| Scopes, none of them Bumpkit's | Refused with `invalid_scope` |

If both scopes are present, `mcp` wins. Athenz role scopes map the same way, only for the **Athenz domain** set on the card: `<domain>:role.mcp` is `mcp` and `<domain>:role.mcp-read` is `mcp:read`. Roles from any other domain are ignored. A client may ask for a narrower scope on the token request, for example `scope=mcp:read`; asking for more than the ID-JAG grants is refused.

## What Bumpkit accepts

Most identity providers set these for you. Use this table when an ID-JAG is refused.

| Part | Requirement |
|---|---|
| `typ` header | `oauth-id-jag+jwt` |
| Signature | `RS256`, `RS384`, `RS512`, `PS256`, `PS384`, `PS512`, `ES256`, `ES384`, `ES512` or `EdDSA`, with a key from the issuer's published keys. RSA keys need at least 2048 bits |
| `iss` | Exactly your workspace's OIDC issuer URL. Bumpkit finds the keys at `<iss>/.well-known/openid-configuration`, or `<iss>/.well-known/oauth-authorization-server` |
| `aud` | Exactly `https://bumpkit.dev` (a one-element array is fine) |
| `client_id` | A client ID you registered in this workspace, and the same one the MCP client sends |
| `resource` | Optional; if present, every value must be `https://bumpkit.dev/mcp` |
| `cnf` | Not accepted: sender-constrained ID-JAGs are refused |
| `tenant` | Only for identity providers that serve several tenants from one issuer: it must equal the **Identity provider tenant** you set on the card, and an ID-JAG without it is refused once you set one |
| `sub`, `jti`, `exp`, `iat` | Required. Each `jti` is accepted once |
| Lifetime | `exp` at most 1 hour after `iat`; clocks may differ by up to 60 seconds |
| `scope` | Optional; see [Roles and scopes](#roles-and-scopes) |
| `aud_tenant` | Optional; if present it must be your workspace ID or `https://bumpkit.dev`. The workspace always comes from the registered client |
| `email`, `email_verified` | Optional; used to match a member the first time |

## Identity provider guides

These identity providers document how to issue ID-JAGs. Their consoles change, so each guide links the provider's own steps and lists the Bumpkit values to enter. Whatever the provider, check the result with **Test an ID-JAG**.

### Okta (Cross App Access)

Okta calls this Cross App Access (XAA). Okta's [Cross App Access overview](https://help.okta.com/oie/en-us/Content/Topics/apps/apps-cross-app-access.htm) says it's available to orgs subscribed to its Single Sign-On product.

1. **Use your Okta org URL as the issuer.** Okta's org authorization server issues ID-JAGs, so the **OIDC issuer URL** in Bumpkit must be your org URL, like `https://acme.okta.com`, not a custom authorization server such as `https://acme.okta.com/oauth2/default`. If your Bumpkit SSO uses a custom authorization server, switch it to the org URL first.
2. **Turn on Cross App Access for Bumpkit's app.** Following [Configure the resource app](https://help.okta.com/oie/en-us/Content/Topics/apps/apps-config-resource-app.htm), open the OIDC app you use for Bumpkit sign-in under **Applications and Resources → Applications**, go to **Machine Assignments → Callers**, click **Edit** next to **Cross App Access (XAA)** and select **Enable**. Enter the **Audience** value from Bumpkit (`https://bumpkit.dev`) as the **Issuer URL**. Leave **Audience/tenant ID** empty, or enter the workspace ID shown as **Audience tenant** on the card; confirm with **Test an ID-JAG**. Add the scopes `mcp` and `mcp:read`, and click **Save**.
3. **Connect the MCP client.** Following [Configure the Cross App Access connection](https://help.okta.com/oie/en-us/Content/Topics/apps/apps-config-xaa-connection.htm), go to **Directory → AI Agents**, select the agent for your MCP client, open **Resource connections** and click **Add resource connection**. Select the Bumpkit app and **Enable**, set **Resource indicator** to `https://bumpkit.dev/mcp`, set **AI agent's client ID registered in this app** to the `bkmcp_ema_...` client ID from Bumpkit, choose which scopes it may request, and click **Add**. To register the agent itself, see [Manage Cross App Access connections](https://help.okta.com/oie/en-us/Content/Topics/apps/apps-manage-cross-app-access.htm).
4. **Assign people.** Assign the people or groups who may use Bumpkit to the app, as you do for sign-in.

Okta documents a limit of 250 ID-JAGs per user, per resource app, per month, and says each XAA user must be a licensed, active SSO user. Bumpkit's access tokens last up to 1 hour, so a client needing access all day asks for about one ID-JAG an hour: plan for that against the cap, or ask Okta about higher volumes.

### PingFederate 13.1 and later

PingFederate 13.1 issues ID-JAGs through OAuth token exchange. Follow Ping's [Configuring a refresh token to ID-JAG exchange](https://docs.pingidentity.com/pingfederate/13.1/administrators_reference_guide/pf_configuring_refresh_token_to_idjag_exchange.html), with these Bumpkit values:

- **SP connection:** on **General Info**, set **Partner's Entity ID** to `https://bumpkit.dev`. PingFederate uses it as the ID-JAG's `aud` when the client sends no `audience`.
- **Protocol Settings:** add `https://bumpkit.dev/mcp` as a resource URI and, under **Allowed Requested Token Types**, select **Identity Assertion JWT (ID-JAG)**.
- **Token Lifetime:** keep it at 1 hour or less.
- **Client ID:** the ID-JAG's `client_id` must be a client ID registered in Bumpkit. Ping's guide doesn't say how the claim is set: if Ping sends its own client ID, register a client with that ID under **Client ID from your IdP**. Check it with **Test an ID-JAG**.

Ping's guide covers the token processor, the processor policy and enabling token exchange on the OAuth client. Its 13.1 [release notes](https://docs.pingidentity.com/pingfederate/13.1/release_notes/pf_release_notes_131.html) list the ID-JAG changes.

### Descope

Descope issues ID-JAGs for third-party MCP servers. Follow Descope's [Issue ID-JAGs for your agents](https://docs.descope.com/agentic-identity-hub/enterprise-managed-authorization/issue-id-jags), with these Bumpkit values:

- **Issuer:** your Descope issuer, `https://api.descope.com/v1/apps/<ProjectID>` or your custom domain, must be the OIDC issuer URL in Bumpkit.
- **Resource:** create a Resource of type **API** with the **Resource identifier** `https://bumpkit.dev/mcp`, exactly, and add the scopes `mcp` and `mcp:read`.
- **Cross App Access:** turn on **Enable** and set **Authorization server URL** to `https://bumpkit.dev` (**Discover** finds it). Set **Tenant ID in target** to your Bumpkit workspace ID, the **Audience tenant** on the card. If it's left empty, Descope may send its own tenant ID, which Bumpkit refuses: confirm with **Test an ID-JAG**.
- **Client ID:** under the client's **Cross App Access Targets**, map the Bumpkit Resource and enter the `bkmcp_ema_...` client ID from Bumpkit.
- **Policy:** add a **Delegated access (token exchange)** policy that lets your users or agents reach the Bumpkit Resource with the scopes you choose. Without one, the exchange fails.

### Janssen

The Janssen Server documents its ID-JAG settings in the [Janssen Auth Server configuration properties](https://docs.jans.io/head/janssen-server/reference/json/properties/janssenauthserver-properties/), including `idJagLifetime`, the lifetime of the ID-JAGs it issues (300 seconds by default). Its docs don't have a step-by-step guide for issuing ID-JAGs to another authorization server yet, so configure the client with these Bumpkit values and check the result with **Test an ID-JAG**:

- **Issuer:** the Janssen issuer must be the OIDC issuer URL in Bumpkit.
- **Audience:** `https://bumpkit.dev`. **Resource:** `https://bumpkit.dev/mcp`.
- **Client ID:** the `bkmcp_ema_...` client ID from Bumpkit. **Scopes:** `mcp` or `mcp:read`.
- **Lifetime:** keep `idJagLifetime` at 3600 seconds or less.

### ThunderID

ThunderID issues ID-JAGs through token exchange. Its guide, [Issue identity assertions](https://github.com/thunder-id/thunderid/blob/main/docs/content/guides/protocols/oauth-oidc/identity-assertion-grant/issue-identity-assertions.mdx), is published with its documentation source. Follow it with these Bumpkit values:

- **Application or agent:** on the **Advanced** tab, turn on **Identity assertions (ID-JAG)** and add `https://bumpkit.dev` under **Allowed audiences**. Keep **Assertion validity** at 3600 seconds or less (300 by default). The client must be confidential.
- **Token exchange request:** the client sends `audience=https://bumpkit.dev`, `resource=https://bumpkit.dev/mcp` and `scope=mcp` or `scope=mcp:read`.
- **Client ID:** ThunderID puts the requesting app's own client ID in the ID-JAG's `client_id`. Register a client in Bumpkit with that ID under **Client ID from your IdP** (`--client-id`), and give the MCP client that ID with the secret Bumpkit generates. Check with **Test an ID-JAG** before rolling out.
- **Issuer:** the ThunderID issuer must be the OIDC issuer URL in Bumpkit.

### Athenz

Athenz ZTS issues ID-JAGs in exchange for an ID token. Its [token exchange requirements](https://github.com/AthenZ/athenz/blob/master/docs/zts_token_exchange_requirements.md) describe the request and the policy it needs. With Bumpkit:

- **Roles:** in your Athenz domain, create the roles `mcp` (full access) and `mcp-read` (read-only) and add the people who may use Bumpkit. Request them as `scope=<domain>:role.mcp` or `<domain>:role.mcp-read`; Set the domain as **Athenz domain** on the card (`--role-domain`); Bumpkit maps that domain's roles to `mcp` and `mcp:read` and ignores other domains.
- **Policy:** grant the requesting principal the action `zts.jag_exchange` on `<domain>:role.mcp` and `<domain>:role.mcp-read`.
- **Audience:** send `audience=https://bumpkit.dev`.
- **Issuer and client ID:** the ZTS issuer must be the OIDC issuer URL in Bumpkit, and the ID-JAG's `client_id`, which Athenz sets to the requesting service's client ID, must be registered in Bumpkit under **Client ID from your IdP**. Check both with **Test an ID-JAG**.

## For MCP client developers

Bumpkit advertises enterprise-managed authorization in its OAuth metadata. `https://bumpkit.dev/.well-known/oauth-authorization-server` lists `urn:ietf:params:oauth:grant-type:jwt-bearer` in `grant_types_supported`, `urn:ietf:params:oauth:grant-profile:id-jag` in `authorization_grant_profiles_supported`, `client_secret_basic` and `client_secret_post` in `token_endpoint_auth_methods_supported`, the scopes `mcp` and `mcp:read`, and the extension under `extensions`. `https://bumpkit.dev/.well-known/oauth-protected-resource` lists the same scopes and extension.

Trade the ID-JAG at the token endpoint:

```bash
curl -X POST https://bumpkit.dev/oauth/token \
  -u "$BUMPKIT_MCP_CLIENT_ID:$BUMPKIT_MCP_CLIENT_SECRET" \
  -d grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer \
  --data-urlencode "assertion=$ID_JAG" \
  -d resource=https://bumpkit.dev/mcp
```

The client authenticates with HTTP Basic or with `client_id` and `client_secret` in the form. `resource` and `scope` are optional; `resource`, if sent, must be `https://bumpkit.dev/mcp`. The response has `access_token`, `token_type` `Bearer`, `expires_in` of at most `3600` and the granted `scope`, with no `refresh_token`. Send the token to `https://bumpkit.dev/mcp` as `Authorization: Bearer ...`. Errors use the standard OAuth `error` and `error_description`; see [Troubleshooting](#troubleshooting).

A client that declares the extension in its MCP request's `_meta` client capabilities and calls `/mcp` without a token gets a `401` whose message names the token endpoint and grant to use.

## CLI and MCP

| CLI | MCP tool | What it does |
|---|---|---|
| `bumpkit identity sso managed-mcp view` | `identity_sso_managed_mcp_view` | Whether it's on or required, the issuer, audience, resource, token endpoint, audience tenant, scopes and registered clients, without secrets |
| `bumpkit identity sso managed-mcp set --enabled [--required] [--tenant TENANT] [--role-domain DOMAIN] --yes` | `identity_sso_managed_mcp_set` | Only what you pass changes. Turn it on or off, require it, and set the identity provider tenant and Athenz domain; the tool takes `enabled`, `required`, `tenant`, `role_domain` and `confirm` |
| `bumpkit identity sso managed-mcp clients add NAME [--client-id ID]` | `identity_sso_managed_mcp_clients_add` | Register a client, with the client ID your identity provider uses if it chooses its own; prints its `client_id` and `client_secret` once |
| `bumpkit identity sso managed-mcp clients remove CLIENT_ID --yes` | `identity_sso_managed_mcp_clients_remove` | Remove a client and stop every token issued to it; the tool takes `client_id` and `confirm` |
| `bumpkit identity sso managed-mcp test --file FILE` | `identity_sso_managed_mcp_test` | Check an ID-JAG without using it up |

`set` with no flags turns IdP-managed access off. `test` reads the ID-JAG from `--file` or, with `--file -` (the default), from standard input: `bumpkit identity sso managed-mcp test < id-jag.txt`. It never takes the token as a flag. The `identity_sso_managed_mcp_test` tool reads it from an environment variable in the server's environment (`assertion_env`) or a private file (`assertion_file`, mode 600), so it runs on the [local MCP server](/docs/mcp#the-local-mcp-server). `identity_sso_managed_mcp_clients_add` is also local-only, because the result holds a secret; the hosted connector has the other three tools.

The API is `GET` and `PUT /v1/identity/sso/managed-mcp`, `POST /v1/identity/sso/managed-mcp/clients`, `DELETE /v1/identity/sso/managed-mcp/clients/{id}` and `POST /v1/identity/sso/managed-mcp/test`. See the [Admin API](/docs/api-admin#idp-managed-mcp-access).

## Audit log

Every change and every token exchange is in the [audit log](/docs/compliance#audit-log):

| Event | When |
|---|---|
| `sso.managed_mcp` | IdP-managed access turned on, off or required |
| `sso.managed_mcp_client_create`, `sso.managed_mcp_client_delete` | A client registered or removed |
| `sso.managed_mcp_test` | An admin tested an ID-JAG, with the verdict |
| `mcp.managed.exchange` | An ID-JAG was traded for a token, with the client, role and issuer |
| `sso.removed_refused`, `sso.relink_refused` | A dashboard sign-in was refused because the person was removed or their link belongs to a different identity provider account |
| `mcp.managed.refused` | An ID-JAG was refused, with the error and why |
| `mcp.auth.failed` | A token stopped working, for example because the member was deactivated or IdP-managed access is required |

## Troubleshooting

Each refusal comes back as an OAuth error with a description that says what to fix. **Test an ID-JAG** returns the same error and description.

### invalid_client

The `client_id` or `client_secret` is wrong or missing, or the client was removed. Copy the client ID and secret from **Settings → Single sign-on**. Bumpkit finds the workspace from the client, so register a client in each workspace whose members use it. In a test, it means the ID-JAG's `client_id` isn't a client of this workspace: register one and set it as the client ID in your identity provider.

### invalid_grant

The ID-JAG itself was refused. The description says which check failed:

| Description says | Fix |
|---|---|
| Bumpkit doesn't accept ID-JAGs from this issuer | Set it as the OIDC issuer URL in **Settings → Single sign-on** and turn on IdP-managed access. For Okta, use the org URL |
| isn't a JWT signed with an asymmetric algorithm | Sign with one of the [accepted algorithms](#what-bumpkit-accepts) |
| typ header must be `oauth-id-jag+jwt` | Send the ID-JAG, not an ID token or access token |
| couldn't read the OpenID configuration or signing keys | Make sure `<issuer>/.well-known/openid-configuration` and its keys are reachable over HTTPS, then try again in a minute |
| keys have no signing key | If the identity provider just rotated keys, try again in a minute: Bumpkit refetches keys at most once a minute, and remembers a failed fetch for a minute |
| signature doesn't match | Get a new ID-JAG from the identity provider; it was changed or signed with another key |
| missing a required claim | The identity provider must set `iss`, `sub`, `aud`, `client_id`, `jti`, `exp` and `iat` |
| `aud` must be exactly `https://bumpkit.dev` | Set the audience or issuer URL for Bumpkit in your identity provider to the **Audience** value |
| `resource` must be `https://bumpkit.dev/mcp` | Fix the resource or resource indicator in your identity provider |
| issued to a different client | The ID-JAG's `client_id` must match the client ID the MCP client sends |
| lives longer than 1 hour | Shorten the ID-JAG lifetime in your identity provider |
| expired, isn't valid yet, or issued in the future | Get a new ID-JAG; if it keeps happening, check the identity provider's clock |
| already used | Each ID-JAG works once; get a new one |
| bound to a key (`cnf`) | Have the identity provider issue the ID-JAG without proof-of-possession binding |
| names tenant ..., so its issuer serves more than one tenant | Set **Identity provider tenant** on the card to that tenant |
| tenant ... isn't the tenant set for this workspace | The ID-JAG names another tenant, or none while you set one; fix the tenant in your identity provider or on the card |

### invalid_target

- **The ID-JAG's `aud_tenant` isn't the client's workspace or Bumpkit's audience.** Set it to the workspace ID on the card, or leave it out. With Descope, set **Tenant ID in target**.
- **The token request's `resource` isn't `https://bumpkit.dev/mcp`.** Send that value or leave `resource` out.

### invalid_scope

The ID-JAG has scopes, but none of them is Bumpkit's, or the client asked for more than the ID-JAG grants. In your identity provider, grant `mcp` for full access or `mcp:read` for read-only access.

### access_denied

The ID-JAG is valid, but the person can't use Bumpkit, or your organization requires IdP-managed access:

| Description says | Fix |
|---|---|
| No Bumpkit member matches | Have the person sign in to Bumpkit with SSO once, make sure the ID-JAG carries a verified `email` on a domain this workspace verified, add them on the Team page, or turn on **Create accounts on first sign-in** |
| already linked to a different identity provider account | The member is linked to another account at your identity provider; check the person's account in your identity provider and on the Team page |
| account ... is deactivated | Restore the member on the Team page or through SCIM |
| manages MCP access through its identity provider | Your organization requires IdP-managed MCP access, so Bumpkit sign-in and API keys for MCP clients are off for people on your verified domains. Connect through the MCP client your admin set up with your identity provider. An existing connection or API key that stops working with `401` after the requirement is turned on has the same cause |

### unsupported_grant_type

The token request used a grant other than `authorization_code`, `refresh_token` or `urn:ietf:params:oauth:grant-type:jwt-bearer`, or IdP-managed access isn't available on this server.
