{
...
"aud": "https://example.com/mcp",
"scope": "mcp:resources mcp:tools mcp:prompts"
...
}
There are currently five versions of the Model Context Protocol (MCP) specification:
2026-07-28 (latest version)
2025-11-25
2025-06-18
2025-03-26
2024-11-05 (initial version)
The initial version (2024-11-05) does not cover authorization and is therefore not covered in this guide.
This guide shows you the following:
Which MCP version Keycloak supports.
How to set up Keycloak as an authorization server in MCP.
However, the guide does not cover everything you need to know. Therefore, it is recommended that you read the authorization section of the relevant MCP version as well.
According to the MCP specification, there are several standards regarding an authorization server in MCP. The following table shows:
Which MCP version requires an authorization server to support which standards in which level (MUST, SHOULD, MAY).
With which standards Keycloak complies.
| Standard | 2026-07-28 | 2025-11-25 | 2025-06-18 | 2025-03-26 | Keycloak |
|---|---|---|---|---|---|
MUST |
MUST |
MUST |
MUST |
Supported |
|
MUST (or OpenID Connect Discovery 1.0) |
MUST |
MUST |
MUST |
Supported |
|
MUST |
MUST |
MUST |
- |
Experimental |
|
OAuth 2.0 Authorization Server Issuer Identification (RFC 9207) |
SHOULD |
- |
- |
- |
Supported |
MAY |
MAY |
SHOULD |
SHOULD |
Supported |
|
SHOULD |
SHOULD |
- |
- |
Experimental |
The OAuth Client ID Metadata Document support in Keycloak is an experimental feature. It may introduce breaking changes in future versions of Keycloak. To enable it, start Keycloak with --features=cimd.
|
The Resource Indicators for OAuth 2.0 support in Keycloak is an experimental feature. It may introduce breaking changes in future versions of Keycloak. To enable it, start Keycloak with --features=resource-indicators.
|
The MCP specification adopts OAuth 2.0 Protected Resource Metadata (RFC 9728). The standard is for an MCP server and not for an authorization server like Keycloak. Therefore, it is not included in the above table.
In this guide, as criteria for compliance, "Keycloak supports MCP" means that Keycloak meets all MUST and SHOULD requirements by MCP.
According to these criteria, the following table shows which MCP version Keycloak supports.
| MCP Version | Conformance |
|---|---|
Supported |
|
Experimental |
|
Experimental |
|
Experimental |
To gain security benefit, the MCP specification requires an access token to be bound with its audience. In order to do so, the MCP specification requires the following:
An MCP client MUST include the resource parameter defined in Resource Indicators for OAuth 2.0 (RFC 8707) in an authorization request and token request. The parameter’s value MUST identify an MCP server that the MCP client intends to use the token with.
An MCP server MUST validate that tokens presented to them were specifically issued for their use.
The MCP specification does not describe how to do this binding. One method for the binding is to set a value of resource parameter to an aud claim in an access token.
Keycloak supports Resource Indicators for OAuth 2.0 (RFC 8707) as an experimental feature. If the feature is disabled, Keycloak does not recognize the resource parameter. In this case, you can use OAuth 2.0’s scope parameter instead of the resource parameter. If the feature is enabled, Keycloak recognizes and processes the resource parameter as the MCP specification expects.
To show the binding, please consider the following situation:
An MCP server’s URL is https://example.com/mcp
The MCP server supports the following three scopes: mcp:tools, mcp:prompts and mcp:resources.
To get an access token for accessing the MCP server, an MCP client sends to Keycloak an authorization request whose resource parameter value is https://example.com/mcp and scope parameter includes any combination of the three scopes.
Keycloak needs to issue an access token whose aud claim’s value is the MCP server’s URL, namely https://example.com/mcp.
To make Keycloak issue such an access token, you can configure Keycloak as follows:
Add a client scope mcp:tools whose type is Optional.
Add to the client scope a new Audience mapper whose Included Custom Audience field is https://example.com/mcp.
Add a client scope mcp:prompts whose type is Optional.
Add to the client scope a new Audience mapper whose Included Custom Audience field is https://example.com/mcp.
Add a client scope mcp:resources whose type is Optional.
Add to the client scope a new Audience mapper whose Included Custom Audience field is https://example.com/mcp.
Please note that the client scope’s Included Custom Audience field needs to be the same as the authorization request’s resource parameter value and the MCP server’s URL.
With the configuration, if the MCP client sends to Keycloak an authorization request whose resource parameter value is https://example.com/mcp and scope parameter includes mcp:resources, mcp:tools and mcp:prompts, Keycloak can issue the following access token:
{
...
"aud": "https://example.com/mcp",
"scope": "mcp:resources mcp:tools mcp:prompts"
...
}
To make Keycloak process the resource parameter, start Keycloak with the resource-indicators feature enabled:
bin/kc.[sh|bat] start --features=resource-indicators
With the feature enabled, when an authorization request or a token request includes the resource parameter, Keycloak checks whether the value of the resource parameter is configured as a resource URI for the resource server client. If it is, Keycloak issues the access token whose aud claim includes only the value of the resource parameter. If it is not, Keycloak rejects the request with the invalid_target error. Also, the resource parameter value of a token request must match the one of the original authorization request.
Therefore, configure the MCP server as a client in Keycloak and set its resource_url client attribute to the MCP server’s URL, for example through the Admin REST API or realm import. You also need to configure the MCP client as an RP with an Audience mapper whose Included Client Audience targets the MCP server client.
If you use OAuth Client ID Metadata Document, you do not need to configure the Audience mapper by yourself. Instead, you set the MCP server’s URL to the Resource indicator allow list option of the client-id-metadata-document executor. See Setting up the client profile for OAuth Client ID Metadata Document for details.
If you want to use MCP Inspector, an official debugging tool for MCP servers, with Keycloak as an authorization server, you need to do an appropriate setup regarding CORS on Keycloak’s client registration endpoint because MCP Inspector executes JavaScript downloaded from the MCP Inspector’s backend server to register an MCP client dynamically to Keycloak.
You need to do an appropriate setup for Client Registration’s anonymous access policies as follows:
Allowed Client Scopes: Needs to include scopes supported by an MCP server.
Allowed Registration Web Origins: Needs to include web origin of MCP inspector’s backend server.
Trusted Hosts: Needs to include hostname or IP address of the machine that sends a dynamic client registration request to Keycloak, namely the machine your browser runs on.
According to Client Registration Approaches section of the MCP specification, the following three client registration mechanisms are supported and you can choose based on your scenario:
Client ID Metadata Documents: When client and server have no prior relationship (most common)
Pre-registration: When client and server have an existing relationship
Dynamic Client Registration: For backwards compatibility or specific requirements
Keycloak supports OAuth Client ID Metadata Document. To use Client ID Metadata Documents, you need to enable the experimental cimd feature (see Standards Compliance MCP requires) and set up a client policy so that Keycloak processes the client_id parameter formatted as a URL and fetches the client metadata from that URL.
To process an authorization request whose client_id parameter is a URL pointing to a Client ID Metadata Document, you need to create the profile including client-id-metadata-document executor.
To configure the executor, create a client policy profile in the Keycloak Admin Console:
Navigate to Realm Settings → Client Policies → Profiles tab.
Click Create client profile.
Give the profile a name such as cimd-profile and click Save.
Click Add executor and select client-id-metadata-document from the list.
Configure the executor with the following options:
Allow http scheme: If ON, allows http scheme for the Client ID URL and Client Metadata URLs (e.g., client_uri, logo_uri, tos_uri, policy_uri, jwks_uri). This should only be ON in a development environment and must be OFF in a production environment.
Trusted domains: A list of domain patterns (wildcard) that the executor accepts for the Client ID URL and Client Metadata URL properties. For example, use *.example.org to accept any subdomain of example.org. If empty, all domains are denied.
Restrict same domain: If ON, the executor verifies that the Client ID URL and Redirect URI in an authorization request, as well as URL-valued properties of the client metadata, are all under the same trusted domain.
Required properties: A list of client metadata properties that must be present in the Client ID Metadata Document. If the fetched document does not include all the listed properties, the request is rejected.
Only Allow Confidential Client: If ON, the executor only accepts a Client ID Metadata Document representing a confidential client. In this case, the client metadata must include either a jwks or jwks_uri property and must use private_key_jwt or tls_client_auth as the token endpoint authentication method.
Accept Public Client with Confidential-only Grant Types: If ON, the executor accepts a Client ID Metadata Document representing a public client even if it includes grant types that can be used only by a confidential client (e.g., urn:ietf:params:oauth:grant-type:jwt-bearer). The executor excludes such grant types from the client metadata. If OFF, the executor rejects such a Client ID Metadata Document.
Resource indicator allow list: A list of values of the resource parameter that the executor accepts (e.g., https://example.com/mcp). This option is only effective if the resource-indicators feature is enabled. If the resource parameter value of an authorization request is not included in this list, the executor rejects the request. If the authorization request does not include the resource parameter, the executor does not enforce this list. If the list is empty, no resource parameter value is accepted. For an accepted resource parameter value, the executor adds to the client an Audience mapper whose Included Custom Audience field is the value so that the aud claim of an access token includes it.
Click Save.
To trigger the profile created above when the client_id parameter in an authorization request is a URI matching a specified scheme (e.g., https), you need to create the policy including client-id-uri condition.
To configure the condition, create a client policy in the Keycloak Admin Console:
Navigate to Realm Settings → Client Policies → Policies tab.
Click Create client policy.
Give the policy a name such as cimd-policy and click Save.
Under Conditions, click Add condition and select client-id-uri from the list.
Configure the condition with the following options:
URI scheme: A list of URI schemes to match against the client_id parameter (e.g., https). In a production environment, only https should be used.
Trusted domains: A list of domain patterns (wildcard) that the condition accepts for the host part of the client_id URI. If domains are filled, the condition evaluates to true only when the host part of the client_id matches one of the domains. If not filled, the condition evaluates to false regardless. For example, use *.example.org to accept any subdomain of example.org.
Click Save.
Under Associated client profiles, add the cimd-profile profile created in the previous step.
Click Save.
With this configuration, when an MCP client sends an authorization request with a client_id value that is an https URL matching a trusted domain, Keycloak fetches the Client ID Metadata Document from that URL and uses the metadata to process the request.
In accordance with the OAuth Client ID Metadata Document specification, Keycloak validates the client_id URL and rejects the authorization request if it does not meet the following requirements:
The URL MUST have an https scheme (unless Allow http scheme is enabled in the executor configuration).
The URL MUST contain a path component (e.g., https://example.com/mcp is valid, https://example.com without a path is not).
The URL MUST NOT contain single-dot or double-dot path segments (path traversal).
The URL MUST NOT contain a fragment component.
The URL MUST NOT contain a username or password.
The URL MUST NOT include a query string component.
The specification states that the client_id URL SHOULD NOT include a query string component. Keycloak enforces this as a hard requirement and rejects any client_id URL that contains a query string.
|
The client-id-metadata-document executor has the following system-wide settings that control caching and metadata size limits. These settings cannot be configured through the Admin Console. Instead, they are configured as SPI options when starting Keycloak.
min-cache-time: The minimum time (in seconds) that a fetched Client ID Metadata Document is cached. Default: 300 (5 minutes).
max-cache-time: The maximum time (in seconds) that a fetched Client ID Metadata Document is cached. Default: 259200 (3 days).
upper-limit-metadata-bytes: The maximum size (in bytes) of a Client ID Metadata Document that Keycloak accepts. Default: 5000 (5 KB).
To configure these settings, use the --spi-client-policy-executor—client-id-metadata-document--<property>=<value> command-line option when starting Keycloak. For example:
bin/kc.[sh|bat] start --spi-client-policy-executor--client-id-metadata-document--min-cache-time=600 --spi-client-policy-executor--client-id-metadata-document--max-cache-time=86400 --spi-client-policy-executor--client-id-metadata-document--upper-limit-metadata-bytes=10000
Microsoft Visual Studio Code (VS Code) desktop is an MCP client that supports OAuth Client ID Metadata Document. When VS Code desktop connects to an MCP server that requires authorization, it sends an authorization request with a client_id parameter that is an https URL hosted on vscode.dev (e.g., https://vscode.dev/oauth/client-metadata.json). Keycloak fetches the Client ID Metadata Document from this URL and uses the metadata to process the request.
VS Code desktop uses localhost callbacks for the OAuth redirect. It starts a local HTTP server and uses a redirect URI such as http://127.0.0.1:<port>/callback. Because the redirect URI is on 127.0.0.1 rather than on the vscode.dev domain, the Restrict same domain option in the client profile executor must be set to OFF.
To configure Keycloak for VS Code desktop’s MCP client, follow the steps below.
| VS Code desktop is a public client that uses PKCE (Proof Key for Code Exchange) for OAuth. It does not use a client secret. |
Start Keycloak with the cimd feature flag enabled:
bin/kc.[sh|bat] start --features=cimd
Navigate to Realm Settings → Client Policies → Profiles tab.
Click Create client profile.
Give the profile a name such as vscode-cimd-profile and click Save.
Click Add executor and select client-id-metadata-document from the list.
Configure the executor with the following options:
Allow http scheme: OFF
Trusted domains: vscode.dev, 127.0.0.1, code.visualstudio.com (This option is applied not only to the client_id URL but also to the URL-valued properties of the Client ID Metadata Document, such as client_uri, logo_uri, tos_uri, policy_uri, and jwks_uri. VS Code desktop’s Client ID Metadata Document includes a logo_uri property whose value is a URL on code.visualstudio.com. Therefore, this domain must be included in the trusted domains list.)
Restrict same domain: OFF (VS Code desktop uses a localhost redirect URI such as http://127.0.0.1:<port>/callback, which is not on the same domain as vscode.dev)
Only Allow Confidential Client: OFF (VS Code desktop is a public client)
Click Save.
Navigate to Realm Settings → Client Policies → Policies tab.
Click Create client policy.
Give the policy a name such as vscode-cimd-policy and click Save.
Under Conditions, click Add condition and select client-id-uri from the list.
Configure the condition with the following options:
URI scheme: https
Trusted domains: vscode.dev
Click Save.
Under Associated client profiles, add the vscode-cimd-profile profile created in the previous step.
Click Save.
With this configuration, when VS Code desktop sends an authorization request, Keycloak recognizes the client_id as a URL on vscode.dev, fetches the Client ID Metadata Document, and uses a localhost callback to complete the OAuth flow.
VS Code desktop includes the resource parameter whose value is the MCP server’s URL in an authorization request and a token request. The above configuration works without Resource Indicators for OAuth 2.0. If you want Keycloak to process the resource parameter and bind an access token to the MCP server, do the following in addition to the above configuration:
Start Keycloak with both the cimd and resource-indicators features enabled:
bin/kc.[sh|bat] start --features=cimd,resource-indicators
In the vscode-cimd-profile client profile, configure the client-id-metadata-document executor with the following option:
Resource indicator allow list: The URL of the MCP server (e.g., https://example.com/mcp)
Click Save.
With this configuration, Keycloak issues an access token whose aud claim is the MCP server’s URL specified by the resource parameter.
Claude Code is an MCP client that supports OAuth Client ID Metadata Document. When Claude Code connects to an MCP server that requires authorization, it sends an authorization request with a client_id parameter that is an https URL hosted on claude.ai (e.g., https://claude.ai/oauth/claude-code-client-metadata). Keycloak fetches the Client ID Metadata Document from this URL and uses the metadata to process the request.
Claude Code uses localhost callbacks for the OAuth redirect. It starts a local HTTP server and uses a redirect URI such as http://localhost:<port>/callback. Because the redirect URI is on localhost rather than on the claude.ai domain, the Restrict same domain option in the client profile executor must be set to OFF.
To configure Keycloak for Claude Code’s MCP client, follow the steps below.
| Claude Code is a public client that uses PKCE (Proof Key for Code Exchange) for OAuth. It does not use a client secret. |
Start Keycloak with the cimd feature flag enabled:
bin/kc.[sh|bat] start --features=cimd
Navigate to Realm Settings → Client Policies → Profiles tab.
Click Create client profile.
Give the profile a name such as claude-code-cimd-profile and click Save.
Click Add executor and select client-id-metadata-document from the list.
Configure the executor with the following options:
Allow http scheme: OFF
Trusted domains: claude.ai, localhost, 127.0.0.1
Restrict same domain: OFF (Claude Code uses a localhost redirect URI such as http://localhost:<port>/callback, which is not on the same domain as claude.ai)
Only Allow Confidential Client: OFF (Claude Code is a public client)
Click Save.
Navigate to Realm Settings → Client Policies → Policies tab.
Click Create client policy.
Give the policy a name such as claude-code-cimd-policy and click Save.
Under Conditions, click Add condition and select client-id-uri from the list.
Configure the condition with the following options:
URI scheme: https
Trusted domains: claude.ai
Click Save.
Under Associated client profiles, add the claude-code-cimd-profile profile created in the previous step.
Click Save.
With this configuration, when Claude Code sends an authorization request, Keycloak recognizes the client_id as a URL on claude.ai, fetches the Client ID Metadata Document, and uses a localhost callback to complete the OAuth flow.
Claude Code includes the resource parameter whose value is the MCP server’s URL in an authorization request and a token request. The above configuration works without Resource Indicators for OAuth 2.0. If you want Keycloak to process the resource parameter and bind an access token to the MCP server, do the following in addition to the above configuration:
Start Keycloak with both the cimd and resource-indicators features enabled:
bin/kc.[sh|bat] start --features=cimd,resource-indicators
In the claude-code-cimd-profile client profile, configure the client-id-metadata-document executor with the following option:
Resource indicator allow list: The URL of the MCP server (e.g., https://example.com/mcp)
Click Save.
With this configuration, Keycloak issues an access token whose aud claim is the MCP server’s URL specified by the resource parameter.
Claude Desktop is an MCP client that supports OAuth Client ID Metadata Document. When Claude Desktop connects to an MCP server that requires authorization, it sends an authorization request with a client_id parameter that is an https URL hosted on claude.ai (e.g., https://claude.ai/oauth/mcp-oauth-client-metadata). Keycloak fetches the Client ID Metadata Document from this URL and uses the metadata to process the request.
Unlike Claude Code, Claude Desktop does not use localhost callbacks. It uses a redirect URI on the claude.ai domain such as https://claude.ai/api/mcp/auth_callback. Therefore, the Restrict same domain option in the client profile executor can be set to ON.
| Claude Desktop is a public client that uses PKCE (Proof Key for Code Exchange) for OAuth. It does not use a client secret. |
Claude Desktop’s Client ID Metadata Document includes the urn:ietf:params:oauth:grant-type:jwt-bearer grant type in the grant_types property although Claude Desktop is a public client. Because Keycloak does not allow a public client to use the JWT Authorization Grant, the Accept Public Client with Confidential-only Grant Types option in the client profile executor must be set to ON. With the option ON, Keycloak excludes the grant type from the client metadata. As a result, Claude Desktop cannot use the JWT Authorization Grant with Keycloak.
|
To configure Keycloak for Claude Desktop’s MCP client, follow the steps below.
Start Keycloak with the cimd feature flag enabled:
bin/kc.[sh|bat] start --features=cimd
Navigate to Realm Settings → Client Policies → Profiles tab.
Click Create client profile.
Give the profile a name such as claude-desktop-cimd-profile and click Save.
Click Add executor and select client-id-metadata-document from the list.
Configure the executor with the following options:
Allow http scheme: OFF
Trusted domains: claude.ai
Restrict same domain: ON
Only Allow Confidential Client: OFF (Claude Desktop is a public client)
Accept Public Client with Confidential-only Grant Types: ON (Claude Desktop’s client metadata includes the urn:ietf:params:oauth:grant-type:jwt-bearer grant type)
Click Save.
Navigate to Realm Settings → Client Policies → Policies tab.
Click Create client policy.
Give the policy a name such as claude-desktop-cimd-policy and click Save.
Under Conditions, click Add condition and select client-id-uri from the list.
Configure the condition with the following options:
URI scheme: https
Trusted domains: claude.ai
Click Save.
Under Associated client profiles, add the claude-desktop-cimd-profile profile created in the previous step.
Click Save.
With this configuration, when Claude Desktop sends an authorization request, Keycloak recognizes the client_id as a URL on claude.ai, fetches the Client ID Metadata Document, and completes the OAuth flow with the redirect URI on claude.ai.
Both Claude Code and Claude Desktop use a client_id URL on claude.ai, so both client policies shown in this guide match the authorization requests from both of them, and both client profiles are applied. As a result, the Claude Code profile rejects Claude Desktop, and the Claude Desktop profile rejects Claude Code. If you want to use both of them, use a single client policy and a single client profile whose executor options satisfy both of them: Trusted domains: claude.ai, localhost, 127.0.0.1, Restrict same domain: OFF and Accept Public Client with Confidential-only Grant Types: ON.
|
Claude Desktop includes the resource parameter whose value is the MCP server’s URL in an authorization request and a token request. The above configuration works without Resource Indicators for OAuth 2.0. If you want Keycloak to process the resource parameter and bind an access token to the MCP server, do the following in addition to the above configuration:
Start Keycloak with both the cimd and resource-indicators features enabled:
bin/kc.[sh|bat] start --features=cimd,resource-indicators
In the claude-desktop-cimd-profile client profile, configure the client-id-metadata-document executor with the following option:
Resource indicator allow list: The URL of the MCP server (e.g., https://example.com/mcp)
Click Save.
With this configuration, Keycloak issues an access token whose aud claim is the MCP server’s URL specified by the resource parameter.
If you use a single client profile for both Claude Code and Claude Desktop as described in the note above, configure the Resource indicator allow list option in that client profile instead.
ChatGPT is an MCP client that supports OAuth Client ID Metadata Document. However, Keycloak does not support ChatGPT’s Client ID Metadata Document.
ChatGPT’s Client ID Metadata Document does not include the token_endpoint_auth_method property. Instead, it includes the token_endpoint_auth_methods_supported property defined in OpenID Connect Relying Party Metadata Choices 1.0, which lists the token endpoint authentication methods ChatGPT supports (e.g., none and private_key_jwt). Keycloak does not support OpenID Connect Relying Party Metadata Choices 1.0. Therefore, Keycloak rejects ChatGPT’s Client ID Metadata Document.
Implementing this workaround requires a custom client-policy executor that parses token_endpoint_auth_methods_supported, selects a supported method (private_key_jwt preferably, otherwise none), and sets it as token_endpoint_auth_method before the standard CIMD validation runs. The cimd-provider-name setting cannot perform this normalization because a selected CIMD provider’s create/update hooks receive the metadata only after the built-in executor has parsed and validated it.