Enable OAuth authentication for the CloudBees CI MCP Router

11 minute read

OAuth is the recommended way for the CloudBees CI MCP Router to authenticate to CloudBees CI. Rather than relying on one shared token, each request carries the identity of the person or tool that made it, and the CloudBees CI MCP Router holds only short-lived tokens scoped to the controllers it needs to reach. OAuth uses the following components:

  • An MCP client (such as Claude Code or GitHub Copilot) signs in once and sends an OAuth token to the CloudBees CI MCP Router.

  • The CloudBees CI MCP Router exchanges that token for a short-lived token scoped to the controller it needs to reach, then forwards the request.

  • The controller validates the token and runs the request as the signed-in user.

Authenticating with an API token will be deprecated in the future. For new deployments, CloudBees recommends that you use OAuth, which authenticates each request individually rather than relying on a shared token.

Review the Prerequisites and Pre-seeded OAuth clients, and then choose one of the following methods to enable OAuth:

Prerequisites

Before you begin, ensure that you have the following:

  • The CloudBees OAuth Resource Server plugin (cloudbees-oauth-resource), installed on the operations center and on every controller that should accept OAuth, and each instance restarted after installation.

    If your controllers are connected to the operations center, you do not need to configure each controller manually. When a controller connects to the operations center, it is automatically configured with the authorization server to trust.
  • The CloudBees OAuth Authorization Server plugin (cloudbees-oauth-server), installed on the operations center and the instance restarted after installation.

  • The CloudBees CI MCP Router installed and running.

Pre-seeded OAuth clients

The authorization server ships with two pre-seeded OAuth clients, so no client registration is required to connect an MCP agent:

  • cloudbees-mcp-router: A confidential client, using the token-exchange grant, that the CloudBees CI MCP Router uses to exchange a user token for a controller-scoped token.

  • ci-mcp-client: A public client, using Proof Key for Code Exchange (PKCE), that MCP agents such as Claude Code use to sign in. Select it with the --client-id ci-mcp-client flag when you connect an agent.

Because these two clients are pre-seeded, an MCP agent using ci-mcp-client connects without needing to register first. On first use, you are asked to approve the client’s access, unless an administrator has enabled Auto-approve seeded clients on the authorization server.

Dynamic client registration (DCR) is disabled by default. Requests to the /oauth-server/register endpoint return a 403 error unless you re-enable DCR in the CloudBees OAuth Authorization Server configuration, or by setting dcrEnabled: true in CasC.

Re-enable it only for environments that need self-service registration, such as test clusters or the MCP Inspector. Any client you create manually still prompts for consent on first use.

Enable OAuth with Helm

If you already deploy CloudBees CI with the cloudbees-core Helm chart, you can deploy the CloudBees CI MCP Router and enable OAuth together by adding the following values.

Before proceeding, ensure you have the following:

  • CloudBees CI version 2.541.2.35785 or later.

  • The MCP Server plugin, version 0.148.v6b_c057f27738 or later, installed on one or more controllers.

The following steps cover the settings sufficient for most deployments. For additional tuning options, refer to (Optional) Tune the Helm configuration.

  1. Add the following to your Helm values:

    OperationsCenter: OAuthServer: Enabled: true(1) McpRouter: enabled: true(2) upstreamMode: AUTO(3)
    ▼
    1 Turns on the authorization server and its discovery endpoints.
    2 Deploys the CloudBees CI MCP Router as part of the chart. Omit if you already manually deployed the CloudBees CI MCP Router.
    3 AUTO accepts both OAuth bearer tokens and existing Authorization headers (such as Basic or API token), which is the recommended choice when you migrate to OAuth. Update to OAUTH_ONLY after every controller has the CloudBees OAuth Resource Server plugin installed and configured.
  2. Apply the values:

    helm upgrade cloudbees-core cloudbees/cloudbees-core --reset-then-reuse-values \ --set OperationsCenter.OAuthServer.Enabled=true \ --set McpRouter.enabled=true(1)
    ▼

    The chart automatically:

    • Configures the authorization server with the addresses allowed to receive tokens (the CloudBees CI MCP Router and the controllers).

    • Configures the operations center and controllers with the authorization server to trust.

    • Exposes the OAuth discovery endpoints so that MCP clients can locate them.

    • Runs the CloudBees CI MCP Router under a dedicated Kubernetes service account and grants it a projected token (audience cjoc) that the authorization server validates to authenticate token-exchange requests; no shared secret is required.

(Optional) Tune the Helm configuration

The values described in Enable OAuth with Helm are sufficient for most deployments. The following values let you tune the authorization server and the CloudBees CI MCP Router when the defaults do not suit your environment.

Tune authorization server values
Value Description

OperationsCenter.OAuthServer.Enabled

Renders the OAuth discovery endpoints and configuration that seeds the CloudBees OAuth Authorization Server and CloudBees OAuth Resource Server plugins on every operations center start. Grants the CloudBees CI MCP Router a projected service account token to authenticate to the authorization server. Set to true when the CloudBees OAuth Authorization Server plugin is installed on the operations center.
Default: false

OperationsCenter.OAuthServer.RouterAudience

Public address of the CloudBees CI MCP Router that the authorization server expects in the aud claim of exchanged tokens. Ignored when McpRouter.enabled is true, because the chart then derives the audience from the cluster URL. Set it only for a CloudBees CI MCP Router that runs outside the cluster.
Default: http://localhost:9000

Tune CloudBees CI MCP Router deployment values
Value Description

McpRouter.enabled

The chart renders a Deployment, Service, and Ingress wired to the operations center. Set to true to deploy the CloudBees CI MCP Router as part of the chart.
Default: false

McpRouter.upstreamMode

Authentication mode forwarded to controllers.
Default: AUTO

  • AUTO: Exchanges a validated OAuth bearer token for a controller-scoped token, and forwards any other Authorization header (such as Basic or API token) verbatim.

  • OAUTH_ONLY: Requires an OAuth bearer token and rejects any other request.

Use AUTO while migrating, then switch to OAUTH_ONLY once every controller has the CloudBees OAuth Resource Server plugin installed and configured. OAUTH_ONLY requires OperationsCenter.OAuthServer.Enabled to be true.

McpRouter.privateKeyJwt.enabled
McpRouter.privateKeyJwt.keySecretName
McpRouter.privateKeyJwt.keySecretKey

By default, the CloudBees CI MCP Router authenticates to the authorization server with its projected service account token. To use an RFC 7523 private_key_jwt assertion instead, set these values:

  • enabled: NEVER (default), ALWAYS, or AUTO.

  • keySecretName: The name of a Kubernetes Secret holding the RSA signing key in PEM format.

  • keySecretKey: The entry within that Secret.

When enabled is ALWAYS and no keySecretName is set, the chart generates a signing key and stores it in a Secret named mcp-router-signing-key.
Default: enabled: NEVER

McpRouter.replicas

Number of CloudBees CI MCP Router replicas. The CloudBees CI MCP Router keeps only in-memory caches, so one replica is enough for most fleets.
Default: 1

McpRouter.image.registry
McpRouter.image.repository
McpRouter.image.tag

CloudBees CI MCP Router image coordinates. Override any of these to pull from a private registry or to pin a specific version. Set McpRouter.image.dockerImage to a full image path to override all image coordinates at once.
Default: Chart default values.

McpRouter.image.pullPolicy

Image pull policy for the CloudBees CI MCP Router container.
Default: IfNotPresent

McpRouter.resources

CPU, memory requests, and limits for the CloudBees CI MCP Router container. Requests and limits are set to the same value so the pod runs in the Kubernetes Guaranteed QoS class.
Default: 1 CPU / 2Gi for both requests and limits

McpRouter.extraEnv

Extra environment variables for the CloudBees CI MCP Router container, for example to set mcp.router.* properties, such as the controller cache TTL.
Default: []

McpRouter.imagePullSecrets

Image pull secrets for a private registry.

McpRouter.nodeSelector
McpRouter.tolerations
McpRouter.annotations
McpRouter.podSecurityContext
McpRouter.containerSecurityContext

Standard Kubernetes scheduling, annotation, and security context settings for the CloudBees CI MCP Router pod and container.

Manually configure OAuth

If you do not use the Helm chart, you must configure the OAuth components manually. Configure the authorization server and resource server in CloudBees CI, and then start the CloudBees CI MCP Router with the corresponding environment variables.

To manually configure OAuth:

  1. Configure the authorization server and resource server using one of the following methods:

  2. Configure the CloudBees CI MCP Router.

Configure the authorization server and resource server using the UI

To configure the authorization server and resource server in the CloudBees CI UI:

The following steps cover the settings sufficient for most deployments. For additional tuning options, refer to (Optional) Tune the manual configuration.

Configure the resource server (operations center and controllers)

To configure the resource server on the operations center and on every controller that should accept OAuth:

  1. In the operations center and on each controller, navigate to Manage Jenkins  Security and scroll down to CloudBees OAuth Resource Server.

  2. Set Trusted issuer to the issuer URL of the authorization server.

    This value must match the Issuer URL of the authorization server exactly. If you use the default authorization server URL, enter <your-oc-url>/oauth-server. If left blank, OAuth enforcement is disabled and all requests pass through to Jenkins.

  3. If the canonical address of this instance differs from its root URL, set Accepted audience to the canonical address. Otherwise, leave it blank to use the root URL.

  4. Set Filter mode to one of the following:

    • AUTO (recommended): Accepts both OAuth bearer tokens and existing sign-ins; non-Bearer requests are handled by Jenkins directly.

    • OAUTH_ONLY: Accepts the same bearer tokens as AUTO, but challenges non-Bearer requests to protected paths with 401 + PRM.

    • ROUTER_ONLY: Accepts only tokens issued through the CloudBees CI MCP Router and rejects all other requests to protected paths.

  5. Select Save to apply the changes.

Configure the authorization server (operations center only)

To configure the authorization server on the operations center:

  1. In the operations center, navigate to Manage Jenkins  Security and scroll down to CloudBees OAuth Authorization Server.

  2. Leave Issuer URL blank to use the default, <your-oc-url>/oauth-server, which matches the Trusted issuer value set on the resource server. If the authorization server is reachable at a different public address than the operations center, set Issuer URL to that address instead.

  3. Leave Internal JWKS URL blank in most cases; controllers fetch the signing keys from the issuer’s public /jwks endpoint. Set it only if a controller cannot reach that public address, for example an internal address reachable only over HTTP. For more information, refer to Resolve OAuth configuration issues.

  4. For Allowed audiences, add the addresses of the controllers and the CloudBees CI MCP Router that are allowed to receive exchanged tokens, one per line.

  5. Set Router client secret to a strong random secret. Record it, as you will need it when you configure the CloudBees CI MCP Router, where it is set as ROUTER_CLIENT_SECRET.

  6. Select Save to apply the changes.

(Optional) Tune the manual configuration

The settings above are sufficient for most deployments. The following settings let you tune the resource server and authorization server when the defaults do not fit your environment.

Tune resource server settings (operations center and controllers)
  1. In the operations center and on each controller, navigate to Manage Jenkins  Security and scroll down to CloudBees OAuth Resource Server.

  2. Leave JWKS URL override blank in most cases; by default the JWKS is fetched from {trustedIssuer}/jwks over HTTPS. Set it only if this instance’s public hostname is unreachable from inside the cluster (hairpin NAT), to an internal address such as http://cjoc/cjoc/oauth-server/jwks.

  3. If you selected ROUTER_ONLY for Filter mode, set PRM resource URI to the CloudBees CI MCP Router URL, so MCP clients discover CloudBees CI MCP Router instead of connecting to Jenkins directly.

  4. If MCP endpoints on this instance are served from a path other than /mcp-server/*, add those paths to Protected paths, one per line or comma-separated.

  5. If you need to tune how long JWKS keys are cached, adjust JWKS cache TTL (default: 300 seconds).

  6. If you need to adjust the leeway for token expiry validation, adjust Clock skew (default: 60 seconds).

Tune authorization server settings (operations center only)
  1. In the operations center, navigate to Manage Jenkins  Security and scroll down to CloudBees OAuth Authorization Server.

  2. If you want the operations center to overwrite any locally set trusted issuer on connected controllers, select Enforce trusted issuer on controllers. Otherwise, the operations center seeds its issuer only on controllers that have none, leaving deliberate local values alone.

  3. If your security policy requires shorter token lifetimes than the defaults, adjust Access token TTL (default: 600 seconds) and Exchanged token TTL (default: 300 seconds). Exchanged tokens should be shorter than access tokens, because they can always be re-exchanged.

  4. To let untrusted clients self-register (for example, MCP Inspector or similar tooling that automatically registers), select Enable Dynamic Client Registration. For more information, refer to Pre-seeded OAuth clients.

    Leave this disabled on a publicly reachable authorization server to prevent unbounded client creation.
  5. If you selected Enable Dynamic Client Registration and an MCP client fails to register because it looks for /register at the Jenkins root instead of at /oauth-server/register, enable Also serve /register at Jenkins root.

  6. If you selected Enable Dynamic Client Registration and your security policy requires different DCR client lifetimes than the defaults, adjust DCR client secret TTL (default: 180 days), DCR refresh-token TTL (default: 30 days), and Idle DCR client TTL (default: 90 days).

    These lifetimes are entered in seconds in the UI.
  7. If you selected Enable Dynamic Client Registration and want to manage clients manually through the UI, disable Evict idle DCR clients. Otherwise, leave it enabled so the daily sweep evicts idle DCR clients.

  8. To bypass the consent prompt on first use for the pre-seeded cloudbees-mcp-router and ci-mcp-client clients, enable Auto-approve seeded clients.

    Enable this only if you accept that these platform clients act on a user’s identity without explicit approval.
  9. If clients authenticate with private_key_jwt and your security policy requires different client assertion or JWKS lifetimes than the defaults, adjust Client assertion max lifetime (default: 300 seconds), Client assertion clock skew (default: 30 seconds), and JWKS cache TTL (default: 300 seconds).

    This JWKS cache TTL is separate from the resource server setting of the same name. Here it controls how long the authorization server caches the public keys it fetches to verify client assertions.

  10. If you use the RFC 7523 jwt-bearer grant to trade external JWTs (such as Kubernetes service account tokens or GitHub Actions OIDC tokens) for CloudBees CI MCP Router tokens, under Trusted assertion issuers, select + Add trusted issuer, and then configure the following for each external issuer:

    • Set Issuer (iss) to the exact value of the assertion’s iss claim, for example, https://token.actions.githubusercontent.com or the Kubernetes API server issuer.

    • Set Expected audience (aud) to a value unique to this authorization server, so a token minted for another service cannot be replayed at this endpoint.

    • Set Bound Jenkins user id to the Jenkins user that every assertion from this issuer maps to. The external token never chooses its own identity; you pin it here.

    • Provide the issuer’s public keys as either Inline JWKS or a JWKS URL. Prefer JWKS URL when the issuer rotates keys.

    • Optionally, in Allowed audiences, add the audiences this issuer’s tokens may target, one per line. Leave blank to inherit the global Allowed audiences list.

    • Optionally, in Allowed subjects, add the external sub values to accept from this issuer, one per line. Leave blank to accept any subject the issuer vouches for.

      For a shared issuer such as GitHub Actions, set Allowed subjects so that only specific external identities can become the bound user, rather than anyone who can mint a token for the expected audience.

Configure the authorization server and resource server using Configuration as Code

To configure using Configuration as Code (CasC):

  1. In the operations center CasC bundle, add the following to your jenkins.yaml file:

    security: oauthAuthorizationServer: issuer: "https://oc.example.com/oauth-server"(1) routerClientSecret: "<your-shared-secret>"(2) allowedAudiences: |(3) https://controller-a.example.com https://mcp-router.example.com internalJwksUrl: "<internal-address>"(4)
    ▼
    1 Public address of the authorization server.
    2 Shared secret the CloudBees CI MCP Router uses to authenticate to the authorization server. Use the same value as ROUTER_CLIENT_SECRET (refer to Configure the CloudBees CI MCP Router).
    3 Addresses of the controllers and the CloudBees CI MCP Router that are allowed to receive exchanged tokens, with one address per line.
    4 Optional; usually leave it unset so controllers derive the issuer’s public HTTPS /jwks endpoint. Set it only if a controller cannot reach that public address, for example an internal address reachable only over HTTP. For more information, refer to Resolve OAuth configuration issues.
  2. In the CasC bundle for the operations center and each controller, add the following to your jenkins.yaml file:

    security: oauthResourceServer: trustedIssuer: "https://oc.example.com/oauth-server"(1) acceptedAudience: "https://controller-a.example.com"(2) filterMode: "AUTO"(3)
    ▼
    1 Trusted issuer URL of the authorization server. It must match issuer exactly. OAuth remains disabled until this is set.
    2 Canonical address of this instance. Defaults to the instance root URL.
    3 AUTO accepts both OAuth and existing sign-ins. OAUTH_ONLY accepts the same bearer tokens as AUTO, but challenges non-Bearer requests to protected paths with 401 + PRM. ROUTER_ONLY accepts only tokens issued through the CloudBees CI MCP Router.
  3. Apply the bundle to your operations center and controllers. For more information, refer to Update a CasC bundle.

Configure the CloudBees CI MCP Router

Start the CloudBees CI MCP Router with the following environment variables:

docker run -p 9000:9000 \ -e OC_URL=https://oc.example.com \ -e OAUTH_ENABLED=true \ -e OAUTH_ISSUER=https://oc.example.com/oauth-server \ -e OAUTH_ROUTER_AUDIENCE=https://mcp-router.example.com \ -e ROUTER_CLIENT_ID=cloudbees-mcp-router \ -e ROUTER_CLIENT_SECRET=<your-shared-secret> \ -e MCP_AUTH_UPSTREAM_MODE=AUTO \ cloudbees/ci-mcp-router:<my-tag>
▼

The following values must match the authorization server configuration:

  • OAUTH_ISSUER must match the Issuer URL.

  • ROUTER_CLIENT_SECRET must match the Router client secret.

  • OAUTH_ROUTER_AUDIENCE is the public address of the CloudBees CI MCP Router, and must appear in the Allowed audiences list of the authorization server.

The router client secret allows the CloudBees CI MCP Router to obtain tokens, so protect it as you would a password. Use a Kubernetes Secret or a secret store rather than passing it on the command line, where it can be exposed in shell history or process listings.

Verify OAuth is working

  1. Verify that the authorization server is published. The following command returns a JSON document that describes the available endpoints:

    curl https://oc.example.com/oauth-server/oauth-authorization-server
    ▼
  2. Verify that the signing keys are available:

    curl https://oc.example.com/oauth-server/jwks
    ▼
  3. Verify that a controller advertises OAuth:

    curl https://controller-a.example.com/.well-known/oauth-protected-resource
    ▼
  4. After connecting an MCP agent as described in Connect the MCP agent, verify that you are prompted to sign in through OAuth instead of entering an API token.

    If verification fails, refer to Resolve OAuth configuration issues.