ZuploZuplo
LoginStart for Free
  • Documentation
  • API Reference

MCP Keycloak OAuth Policy

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.

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 <string> - The name of your policy instance. This is used as a reference in your routes.
  • policyType <string> - The identifier of the policy. This is used by the Zuplo UI. Value should be mcp-keycloak-oauth-inbound.
  • handler.export <string> - The name of the exported type. Value should be McpKeycloakOAuthInboundPolicy.
  • handler.module <string> - The module containing the policy. Value should be $import(@zuplo/runtime).
  • handler.options <object> - The options for this policy. See Policy Options below.

Policy Options

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

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

Code
{ "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:

Code
{ "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

Edit this page
Last modified on June 6, 2026
On this page
  • Configuration
    • Policy Configuration
    • Policy Options
  • Using the Policy
JSON
JSON
JSON