
# MCP Keycloak OAuth Policy

:::note{title="MCP Gateway Policy"}

This policy is for use with the [MCP Gateway](/mcp-gateway/introduction). See
the MCP Gateway documentation to learn how to proxy and secure MCP servers with
Zuplo.

:::

Use the MCP Keycloak OAuth policy to protect an MCP virtual server with
gateway-issued OAuth tokens while delegating browser login to a Keycloak realm.

## Configuration

The configuration shows how to configure the policy in the 'policies.json' document.

```json title="config/policies.json"
{
  "name": "my-mcp-keycloak-oauth-inbound-policy",
  "policyType": "mcp-keycloak-oauth-inbound",
  "handler": {
    "export": "McpKeycloakOAuthInboundPolicy",
    "module": "$import(@zuplo/runtime)",
    "options": {
      "browserLoginOverrides": {
        "remoteTimeoutMs": 10000,
        "sessionTtlSeconds": 28800,
        "stateTtlSeconds": 900
      },
      "clientId": "$env(KEYCLOAK_CLIENT_ID)",
      "clientSecret": "$env(KEYCLOAK_CLIENT_SECRET)",
      "gateway": {
        "accessTokenTtlSeconds": 900,
        "cimdEnabled": true,
        "refreshTokenTtlSeconds": 2592000
      },
      "keycloakBaseUrl": "https://sso.example.com",
      "realm": "master",
      "scope": "openid profile email"
    }
  }
}
```

### Policy Configuration

- `name` <code className="text-green-600">&lt;string&gt;</code> - The name of your policy instance. This is used as a reference in your routes.
- `policyType` <code className="text-green-600">&lt;string&gt;</code> - The identifier of the policy. This is used by the Zuplo UI. Value should be `mcp-keycloak-oauth-inbound`.
- `handler.export` <code className="text-green-600">&lt;string&gt;</code> - The name of the exported type. Value should be `McpKeycloakOAuthInboundPolicy`.
- `handler.module` <code className="text-green-600">&lt;string&gt;</code> - The module containing the policy. Value should be `$import(@zuplo/runtime)`.
- `handler.options` <code className="text-green-600">&lt;object&gt;</code> - The options for this policy. [See Policy Options](#policy-options) below.

### Policy Options

The options for this policy are specified below. All properties are optional unless specifically marked as required.

- `keycloakBaseUrl` **(required)** <code className="text-green-600">&lt;string&gt;</code> - The absolute URL for the Keycloak server root. Do not include /realms/`{realm}`; set the realm option separately.
- `realm` **(required)** <code className="text-green-600">&lt;string&gt;</code> - The Keycloak realm name.
- `clientId` **(required)** <code className="text-green-600">&lt;string&gt;</code> - The Keycloak OIDC client_id registered for the gateway's browser login flow.
- `clientSecret` **(required)** <code className="text-green-600">&lt;string&gt;</code> - The Keycloak OIDC client_secret. Use $env(...) to source from a secret environment variable.
- `scope` <code className="text-green-600">&lt;string&gt;</code> - OIDC scopes requested during browser login. Defaults to `"openid profile email"`.
- `gateway` <code className="text-green-600">&lt;object&gt;</code> - Gateway-side OAuth token settings. The gateway issuer and advertised URLs are derived from the incoming request origin.
  - `accessTokenTtlSeconds` <code className="text-green-600">&lt;integer&gt;</code> - Lifetime of access tokens issued by /oauth/token. Defaults to `900`.
  - `refreshTokenTtlSeconds` <code className="text-green-600">&lt;integer&gt;</code> - Lifetime of refresh tokens issued by /oauth/token. Defaults to `2592000`.
  - `cimdEnabled` <code className="text-green-600">&lt;boolean&gt;</code> - Whether to advertise client_id_metadata_document_supported in AS metadata. Defaults to `true`.
- `browserLoginOverrides` <code className="text-green-600">&lt;object&gt;</code> - Optional overrides for the derived browser-login settings.
  - `remoteTimeoutMs` <code className="text-green-600">&lt;integer&gt;</code> - No description available. Defaults to `10000`.
  - `stateTtlSeconds` <code className="text-green-600">&lt;integer&gt;</code> - No description available. Defaults to `900`.
  - `sessionTtlSeconds` <code className="text-green-600">&lt;integer&gt;</code> - No description available. Defaults to `28800`.

## Using the Policy

# MCP Keycloak OAuth

The MCP Keycloak OAuth policy is a provider-specific wrapper around the generic
MCP OAuth inbound policy. It keeps the gateway OAuth behavior the same, but lets
you configure Keycloak with the values an administrator normally has:

- `keycloakBaseUrl`
- `realm`
- `clientId`
- `clientSecret`

The policy derives the Keycloak realm issuer, JWKS URL, authorization endpoint,
and token endpoint from Keycloak's OpenID Connect endpoint layout.

```json
{
  "name": "keycloak-oauth",
  "policyType": "mcp-keycloak-oauth-inbound",
  "handler": {
    "export": "McpKeycloakOAuthInboundPolicy",
    "module": "$import(@zuplo/runtime)",
    "options": {
      "keycloakBaseUrl": "https://sso.example.com",
      "realm": "customer-portal",
      "clientId": "$env(KEYCLOAK_CLIENT_ID)",
      "clientSecret": "$env(KEYCLOAK_CLIENT_SECRET)"
    }
  }
}
```

If your Keycloak deployment uses a path prefix, include it in `keycloakBaseUrl`:

```json
{
  "keycloakBaseUrl": "https://sso.example.com/auth",
  "realm": "customer-portal",
  "clientId": "$env(KEYCLOAK_CLIENT_ID)",
  "clientSecret": "$env(KEYCLOAK_CLIENT_SECRET)"
}
```

Do not include `/realms/{realm}` in `keycloakBaseUrl`; set `realm` separately.

Read more about [how policies work](/articles/policies)
