---
component: ROOT
version: "2026.3"
slug: ROOT/integrations/authentication
canonical_url: "https://docs.develocity.ai/2026.3/integrations/agentic-ai/mcp-servers/authentication/"
title: "Authentication"
description: "Configure how AI agents authenticate to the Develocity MCP Server, with access keys or OAuth sign-in through your own authorization server."
keywords:
  - "MCP server"
  - "authentication"
  - "admin"
status: current
---

<!-- llms-index: https://docs.develocity.ai/llms.txt -->

# Authentication

<a id="preamble"></a>

An AI agent authenticates to the Develocity MCP Server by sending a credential to the `/mcp` endpoint in an `Authorization: Bearer` header. That credential can either be a Develocity access key or an OAuth access token.

**Develocity access key** — A long-lived key that a developer creates in Develocity and configures in their AI agent. This is the default.

**OAuth access token** — A short-lived token issued by an OAuth 2.0 authorization server that you configure Develocity to trust. The developer signs in through a browser from their AI agent. When using OAuth, developers do not need a Develocity access key.

See [AI Agents](https://docs.develocity.ai/2026.3/integrations/agentic-ai/mcp-servers/ai-agents/) for how developers can configure each credential type in their AI agent. To enable the Develocity MCP Server itself, see [Installation Manual](https://docs.develocity.ai/2026.3/integrations/agentic-ai/mcp-servers/installation/).

<a id="using-oauth-sign-in"></a>

## Using OAuth Sign-In

OAuth sign-in is available only when Develocity has been configured with at least one trusted issuer. Until you configure a trusted issuer, the Develocity MCP Server only accepts access keys.

> [!NOTE]
> Trusted OAuth issuers are separate from Workload Identity. Workload identity authenticates a CI workload, exchanging a CI provider’s OIDC token for a Develocity access token. Workload identity is configured in Develocity’s access control settings. The issuers configured here authenticate a person signing in from an AI agent, and map that person to an existing Develocity user.

An authorization server is the service that signs your developers in and issues OAuth 2.0 access tokens. This page also calls it an issuer, matching the `issuers` key you configure it under.

Each AI agent reaches the Develocity MCP Server through an MCP client. In OAuth terminology, the MCP client is known as the OAuth client.

When an MCP client connects without a credential, Develocity returns the configured trusted authorization servers. This signals the MCP client to run a standard browser sign-in against one of the authorization servers, store the short-lived token it receives, and use the token on later requests.

How the MCP client discovers your authorization servers

The response to a request to `/mcp` without a valid credential is `401 Unauthorized`. The response contains a `WWW-Authenticate` header naming the deployment’s protected resource metadata. This is the discovery document defined by [RFC 9728](https://www.rfc-editor.org/rfc/rfc9728), published at `/.well-known/oauth-protected-resource/mcp`. The MCP client fetches the discovery document, reads the authorization servers from it, and starts the browser sign-in against one of them. Some MCP clients will use the first authorization server listed, but this behavior is not standardized and your MCP client might make a different choice.

See [Confirming Your OAuth Configuration](#confirming-your-oauth-configuration) for example responses from `/mcp` and from `/.well-known/oauth-protected-resource/mcp`.

What an MCP client needs before it can sign in depends on what the MCP client and your authorization server both support. See [Registering MCP Clients](#registering-mcp-clients).

The Develocity MCP Server validates the OAuth token and exchanges it for a Develocity short-lived access token. The short-lived token is securely managed: it is never returned to the MCP client, and it needs no configuration.

Develocity matches the identity in the OAuth token against existing Develocity users. The match is based on a claim in the OAuth token. You choose the claim with `claimMapping`, which defaults to `email`. See [Configuring a Trusted Issuer](#configuring-a-trusted-issuer). Develocity rejects a token that does not match an existing Develocity user.

The authorization server and the MCP client decide how long a token lasts and when the developer signs in again. Some authorization servers and MCP clients can renew the OAuth token automatically. See your authorization server documentation and your MCP client documentation for information about token lifetimes and renewal.

Signing in through OAuth grants no additional access: the caller is subject to the same permissions as any other user, including the [`Access build data via the API and MCP`](https://docs.develocity.ai/2026.3/administration/access-control/permissions-and-roles/#use-mcp-permission) permission.

> [!NOTE]
> Configuring a trusted issuer does not stop the Develocity MCP Server from accepting access keys. To require every caller to sign in using OAuth, set mcpServer.accessKeys.enabled to false. However, Develocity will refuse to start if mcpServer.accessKeys.enabled is set to false unless at least one trusted issuer is configured.

<a id="configuring-a-trusted-issuer"></a>

### Configuring a Trusted Issuer

To configure trusted OAuth issuers, add them under `accessControl.externalOauth` in your Develocity `values.yaml` file. Develocity handles token exchange and maintains the trusted issuer list, which is automatically shared with the Develocity MCP Server. Issuers specified under `mcpServer` are not supported and are rejected.

**values.yaml:**

```
accessControl:
  externalOauth:
    issuers:
      - uri: https://idp.example.com (1)
        accessTokenFormat: jwt (2)
        allowedDomains: [example.com] (3)
        allowedJwtAudiences: ["https://develocity.example.com/mcp"] (4)
        jwksUri: https://idp.example.com/keys (5)
        claimMapping: email (6)
```

1. The issuer URI. This must match the iss claim present in issued tokens. Required.
2. Whether this issuer mints jwt or opaque access tokens, which decides how Develocity validates them. Required, with no default.
3. Allowed email domains for users authenticating through this issuer. Required unless restrictByDomain is set to false.
4. Accepted aud values for JWT validation, identifying this Develocity instance as the resource the token was issued for. Required for a jwt issuer unless restrictByJwtAudience is false. See Choosing the Audience Value.
5. The JWKS endpoint used to fetch and update public key sets for signature verification.
6. The token claim used to identify the user. Supported values are email, sub, and preferredUsername. Defaults to email.

Develocity maps the token claim to user attributes as follows:

  
| `claimMapping` | Claim read from the token | Matched against |
| --- | --- | --- |
| `email` (the default) | `email` | The user’s email address |
| `sub` | `sub` | The username |
| `preferredUsername` | `preferred_username` | The username |

The `aud` values must match the values the authorization server uses when issuing OAuth tokens. See [Choosing the Audience Value](#choosing-the-audience-value).

A `jwt` issuer needs exactly one of `jwksUri` and `jwkSet`. An `opaque` issuer needs no keys, because the authorization server validates the tokens it issued. Develocity refuses an `opaque` issuer that sets `jwksUri` or `jwkSet`.

> [!IMPORTANT]
> Identity claims must be included in the access token itself rather than the ID token. If domain restriction is enabled, the access token must contain an email claim even if mapping by sub.

For additional parameters such as `requireEmailVerified` or `allowedJwtTypes`, see [Trusted OAuth Issuers](https://docs.develocity.ai/2026.3/reference/helm-charts/standalone/#trusted-oauth-issuers) for standalone deployments or [Trusted OAuth Issuers](https://docs.develocity.ai/2026.3/reference/helm-charts/cluster/#trusted-oauth-issuers) for cluster deployments.

<a id="choosing-the-audience-value"></a>

### Choosing the Audience Value

Develocity uses the `aud` claim to verify that incoming tokens were specifically generated for this instance rather than another protected service. If `allowedJwtAudiences` omits a value your authorization server mints, every sign-in fails with `401`, whichever way the client registered.

Rather than constructing an audience from a host domain, obtain the published resource string directly. This string appears in the `resource` attribute of the protected resource metadata document (see [Confirming Your OAuth Configuration](#confirming-your-oauth-configuration)). Ensure the value is copied verbatim, including any trailing slashes, as OAuth specifications treat variations as distinct entities.

Determine configuration requirements based on how your authorization server handles the `aud` parameter:

**Opaque token issuers** — No audience settings are required. The authorization server validates the token, and setting `allowedJwtAudiences` on an opaque issuer triggers an error.

**Resource indicators supported (RFC 8707)** — When the MCP client requests tokens using [RFC 8707](https://www.rfc-editor.org/rfc/rfc8707) resource indicators, the authorization server populates `aud` with that value. Add the resource URI to your configuration. Prefer resource indicators where your authorization server supports them: every client then behaves the same way.

**Custom API or resource definitions** — If your authorization server requires registering target APIs under custom identifiers, set the audience value to match the published resource URI and include it in your settings.

**Client ID populated in aud** — Explicitly list each client ID. Hosted metadata URLs give a client ID you can configure in advance, and dynamic registration generates a client ID you cannot know in advance, so pre-register the client instead. See [Registering MCP Clients](#registering-mcp-clients).

> [!WARNING]
> Do not disable validation by setting restrictByJwtAudience to false to bypass mismatched values. With this set to false, Develocity accepts a token issued for any service on that issuer. A token stolen from another service then works against Develocity. The Model Context Protocol specification mandates verifying intended token target scope.

Validate settings against active tokens prior to deployment. Request an access token using standard client procedures, then inspect its contents:

```shell
echo "ACCESS_TOKEN" | jq -R 'split(".")[0] | gsub("-";"+") | gsub("_";"/") | @base64d | fromjson | {alg, typ}'
echo "ACCESS_TOKEN" | jq -R 'split(".")[1] | gsub("-";"+") | gsub("_";"/") | @base64d | fromjson | {iss, aud, email, email_verified}'
```

Add the extracted `aud` value to your configuration, and verify that `iss` matches the configured issuer `uri`. Additionally, inspect the `typ` header; tokens specifying types not listed in `allowedJwtTypes` will be rejected.

<a id="trusting-multiple-issuers"></a>

### Trusting Multiple Issuers

You can configure Develocity to accept credentials from multiple OAuth authorization servers. The protected resource metadata publishes these issuers in the exact sequence specified within `issuers` (see [Confirming Your OAuth Configuration](#confirming-your-oauth-configuration)).

> [!IMPORTANT]
> Some MCP clients automatically pick the first authorization server provided. If a client lacks an option to choose another issuer, developers whose accounts belong to subsequent issuers cannot complete sign-in. Order your values.yaml entries so the primary authorization server used by most of your team appears at the top of the list.

Because a JWT names its issuer in the `iss` claim, validating multiple JWT issuers costs nothing extra per request. An opaque token names no issuer, so Develocity trusts at most one `opaque` issuer by default. To allow several opaque issuers, enable `opaqueTokenFanOut: true` so Develocity queries each configured authorization server until one succeeds:

**values.yaml:**

```
accessControl:
  externalOauth:
    opaqueTokenFanOut: true
```

Fan-out sends a token to an authorization server that did not mint it, which is why fan-out is off by default. Before enabling fan-out, confirm that you trust every opaque issuer with tokens the other issuers minted.

<a id="reaching-your-provider"></a>

### <a id="reaching-your-authorization-server"></a>Reaching Your Authorization Server

Network requirements depend on how tokens are validated:

**Opaque tokens** — Validated on each request against the authorization server’s UserInfo endpoint, so both Develocity and the Develocity MCP Server need a network path to the authorization server. The Develocity MCP Server reuses a validation result briefly, which lowers the load on your authorization server and sets how long a revoked token keeps working.

**JWTs verified against a fetched key set** — Configured with `jwksUri`. Develocity fetches the keys from that endpoint and the Develocity MCP Server discovers them from the issuer, so both need a path to the authorization server.

**JWTs verified against a provisioned key set** — Configured with an inline `jwkSet`. Verification is local and needs no network path, though you update the provisioned keys yourself when the authorization server rotates them.

For air-gapped or hybrid environments unable to reach the authorization server, use a JWT issuer with a provisioned `jwkSet`:

**values.yaml:**

```
accessControl:
  externalOauth:
    issuers:
      - uri: https://idp.example.com
        accessTokenFormat: jwt
        allowedDomains: [example.com]
        allowedJwtAudiences: ["https://develocity.example.com/mcp"]
        jwkSet: '{"keys":[...]}'
        claimMapping: email
```

An issuer with neither `jwksUri` nor `jwkSet` stops Develocity from starting, reporting `configure jwkSet or jwksUri` for that issuer. An authorization server that Develocity or the Develocity MCP Server cannot reach is a different failure. Develocity starts normally, and the Develocity MCP Server rejects each request it cannot validate.

<a id="confirming-your-oauth-configuration"></a>

### Confirming Your OAuth Configuration

You can verify issuer configuration using two unauthenticated HTTP requests.

An unauthenticated request returns a challenge header pointing to discovery metadata:

```shell
curl -i -X POST https://develocity.example.com/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}'
```

**Output:**

```
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="mcp", resource_metadata="https://develocity.example.com/.well-known/oauth-protected-resource/mcp"
```

Fetching the metadata document returns the list of advertised issuers:

```shell
curl https://develocity.example.com/.well-known/oauth-protected-resource/mcp
```

**Output:**

```
{
  "resource": "https://develocity.example.com/mcp",
  "authorization_servers": ["https://idp.example.com"],
  "bearer_methods_supported": ["header"],
  "scopes_supported": ["openid", "email"]
}
```

Advertised issuers appear in `authorization_servers`. An empty list or a `404` response indicates no OAuth issuers are configured.

<a id="registering-mcp-clients"></a>

### Registering MCP Clients

Develocity is an OAuth resource server. It does not register MCP clients and does not track which client obtained a token: it validates the token it is given. The MCP client registers with your authorization server before it presents a token to Develocity.

An MCP client must obtain a client ID prior to initiating authentication. Under the Model Context Protocol specification, there are [three primary approaches](https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization/client-registration) for securing a client ID, evaluated in the following order:

**Pre-registration** — Manually provision the client within your authorization server. Provide developers with the generated client ID and any associated client secret to configure within their AI agent environment. Every authorization server supports this, so it is the fallback when the other two are not available.

**Client ID metadata documents** — The client ID is an HTTPS URL the MCP client hosts, pointing to a [JSON metadata document](https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html) that describes the client. Your authorization server fetches the document when it needs it. Nobody enters a client ID by hand. Because no records are stored or expired, this method requires zero ongoing maintenance.

**Dynamic client registration** — The MCP client requests a newly generated client ID directly from the authorization server. Each registration issues a new client ID, so you cannot know the client ID in advance, and the client registers again against every new authorization server. Although deprecated in the Model Context Protocol specification in favor of metadata documents, it remains a valid alternative where supported.

   
|  | Pre-registration | Client ID metadata documents | Dynamic client registration |
| --- | --- | --- | --- |
| Administrative effort | One-time setup per client | None required | Initial enablement only |
| Client ID | Issued by authorization server | Hosted HTTPS URI of client | Generated per registration |
| Known in advance | Yes | Yes | No |
| Client secret | Optional | None (uses key pair for confidential clients) | Issued at registration for a confidential client |

Check your authorization server’s discovery document to determine which automated methods are supported. The presence of a `registration_endpoint` indicates dynamic registration availability, whereas `client_id_metadata_document_supported` denotes metadata document capability. Some authorization servers advertise an endpoint that they then refuse to let a client call, so confirm the endpoint works rather than trusting the discovery document. Fall back to manual pre-registration if neither feature is accessible.

Regardless of the selected registration path, ensure the MCP client redirect URI is properly registered on the authorization server. The redirect URI is the address the browser loads after the user signs in to the authorization server. Authorization servers reject authentication attempts redirecting to unregistered targets. While pre-registration requires manual entry of this value, alternative workflows supply redirect URIs within metadata payloads or request parameters. See [AI Agents](https://docs.develocity.ai/2026.3/integrations/agentic-ai/mcp-servers/ai-agents/) for the redirect URI each client uses.

A client that never opens a browser has stalled at your authorization server, after reading Develocity’s discovery document and before signing in. Check the client’s own log for the registration error, then confirm the client ID and redirect URI at your authorization server.

<a id="troubleshooting-oauth-sign-in"></a>

### Troubleshooting OAuth Sign-In

A rejected request says why in the `WWW-Authenticate` response header, so you can tell the causes apart without reading the server logs:

 
| Response | What it means |
| --- | --- |
| `401` with no `error`, naming `resource_metadata` | The request contained no credential. This initiates the standard OAuth sign-in flow. |
| `401 invalid_token`, `Token validation failed` | The token is expired, or its signature, `iss`, `aud`, or `typ` does not match a configured issuer. See [Choosing the Audience Value](#choosing-the-audience-value) if the audience is the mismatch. |
| `401 invalid_token`, `Token does not contain an email claim` | Domain restriction is active, but the access token lacks an email claim. Add the claim or set `restrictByDomain: false`. |
| `401 invalid_token`, `Email domain is not allowed` | The user’s email domain is not listed in `allowedDomains`. |
| `403`, `No Develocity user matches your identity.` | The token was validated, but no Develocity user matches the claim named by `claimMapping`. Check the claim’s value against the user attribute it is compared against, in the table above. |