Understand tool access revocation
Tool revocation uses the authorization setup where tool access is controlled by roles in identity provider JWT tokens. Revoking a tool means removing the corresponding role from a user or group, so their next token no longer grants access.
|
Tip
|
You cannot revoke a specific in-flight token or revoke access instantly. JWT tokens govern access, and these tokens are valid until they expire. Revocation relies on the token expiring and being reissued with changed permissions. To force faster revocation, reduce the access-token lifespan in your identity provider. |
The following two enforcement points apply:
-
tools/call: TheAuthPolicycustom resource (CR) CEL expression checks thex-mcp-toolnameheader against the user’sresource_accessroles. A revoked tool returns afailed to create session for mcp server: failed to create client: transport error: server returned 4xx for initialize POST, likely a legacy SSE servererror. -
tools/list: The broker filters the tools list by using the signedx-mcp-authorizedheader. A revoked tool is no longer displayed in the list.
The timing of tool revocation depends on the following session events:
-
New sessions: Users who authenticate after revocation receive a token without the revoked tool. Denial is immediate.
-
Existing sessions: Users with an active token keep access until the token expires. You configure token lifetime in your identity provider, for example, the Access Token Lifespan setting in Red Hat build of Keycloak. Shorter token lifetimes mean that tokens are refreshed more often, picking up role changes sooner. You must decide on the balance between revocation latency and token-refresh resource use.
-
In-flight requests: A
tools/callrequest that is already being processed completes normally. The authorization check occurs before the request reaches the backend MCP server. Only new requests are denied.
Revoke tool and prompt call requests
Use your existing authorization setup to revoke tool and prompt requests for individual users and groups by removing either role from the user or group in your identity provider.
In this example, Red Hat build of Keycloak is used. Remove the client-role mapping. The client name corresponds to the namespaced MCPServerRegistration custom resource (CR), such as mcp-test/test-server1. Each role represents a tool name, such as greet, or headers. Use the same process for prompts.
-
You installed MCP gateway.
-
You configured a
Gatewayobject. -
You are logged into a running OpenShift Container Platform cluster with an
adminrole. -
You configured an
HTTPRouteobject for the gateway. -
You registered a Model Context Protocol (MCP) server.
-
You created a
SecretCR for authentication. -
You configured authorization with a tool-level
AuthPolicyCR.
-
Revoke a tool for a group by using the Red Hat build of Keycloak interface:
-
Go to Groups > select the group, such as
accounting. -
Go to Role mapping > remove the tool role from the relevant client.
-
-
Revoke a tool for a single user by using the interface:
-
Go to Users > select the user.
-
Go to Role mapping > remove the tool role from the relevant client.
-
-
After revoking a tool, verify that the user can no longer call it by using MCP Inspector.
-
Log out of any existing MCP Inspector session and log back in as the affected user to get a fresh token.
-
Open MCP Inspector and connect to your gateway’s
/mcpendpoint. Authenticate through the OAuth flow. -
Under Tools > List Tools, the revoked tool is displayed. Try calling the revoked tool. The request should return
500.
-
Filter revoked tools and prompts from display
After you revoke a tool or prompt by using your identity provider, requests are blocked. However, the revoked capability is still displayed in responses.
To filter revoked tools and prompts from the displayed list, you must give the Model Context Protocol (MCP) broker component a signed header that carries the user’s authorized capabilities.
You can remove blocked tools from responses by configuring the Authorino Operator to generate the header by using a wristband JSON web token (JWT) signed with an Elliptic Curve Digital Signature Algorithm (ECDSA) key pair.
-
You installed MCP gateway.
-
You configured a
Gatewayobject with bothmcpandmcpslisteners. -
You are logged into a running OpenShift Container Platform cluster with an
adminrole. -
You configured an
HTTPRouteobject for the gateway. -
You registered an MCP server.
-
You configured authentication with an
AuthPolicyCR on themcplistener. -
You configured authorization with a tool-level
AuthPolicyCR on themcpslistener.
-
Generate an ECDSA key pair by running the following commands:
-
Generate the private key and save it to a file by running the following command:
$ openssl ecparam -name prime256v1 -genkey -noout -out private-key.pem -
Generate the public key and save it to a file by running the following command:
$ openssl ec -in private-key.pem -pubout -out public-key.pem
-
-
Create a Kubernetes
Secretobject in the Authorino Operator namespace by adding the private key with the following command:$ oc create secret generic trusted-headers-private-key \ --from-file=key.pem=private-key.pem \ -n <kuadrant_system> \ --dry-run=client -o yaml | oc apply -f -Replace
<kuadrant_system>with the namespace where you installed Connectivity Link. -
Create a Kubernetes
Secretobject in the MCP broker component namespace by adding the public key with the following command:$ oc create secret generic trusted-headers-public-key \ --from-file=key=public-key.pem \ -n <mcp_system> \ --dry-run=client -o yaml | oc apply -f -Replace
<mcp_system>with the namespace where MCP gateway is installed. -
Delete only the existing
AuthPolicyCR on themcplistener by running the following command:$ oc delete authpolicy <mcp_authn_policy> -n <authn_namespace> --ignore-not-found-
Replace
<mcp_authn_policy>with the name of yourAuthPolicyCR. -
Replace
<authn_namespace>with the namespace where yourAuthPolicyCR is applied.ImportantYou must delete the
AuthPolicyCR and create a new one. Using$ oc applymerges both the new and old policies.
-
-
Generate a new
x-mcp-authorizedheader by creating and applying a newAuthPolicyCR. Use the following example:ExampleAuthPolicyCR$ oc create -f - <<EOF apiVersion: kuadrant.io/v1 kind: AuthPolicy metadata: name: _<mcp_auth_policy>_ namespace: _<authn_namespace>_ spec: targetRef: group: gateway.networking.k8s.io kind: Gateway name: _<mcp_gateway>_ sectionName: mcp when: - predicate: "!request.path.contains('/.well-known')" rules: authentication: 'keycloak': jwt: issuerUrl: https://keycloak.example.com:port/realms/mcp authorization: 'allow-mcp-method': patternMatching: patterns: - predicate: | !request.headers.exists(h, h == 'x-mcp-method') || (request.headers['x-mcp-method'] in ["tools/list","prompts/list","initialize","notifications/initialized"]) 'authorized-capabilities': opa: rego: | allow = true capabilities = { "tools": { server: tools | server := object.keys(input.auth.identity.resource_access)[_] tools := [substring(r, count("tool:"), -1) | r := input.auth.identity.resource_access[server].roles[_] startswith(r, "tool:") ] }, "prompts": { server: prompts | server := object.keys(input.auth.identity.resource_access)[_] prompts := [substring(r, count("prompt:"), -1) | r := input.auth.identity.resource_access[server].roles[_] startswith(r, "prompt:") ] } } allValues: true response: success: headers: x-mcp-authorized: wristband: issuer: 'authorino' customClaims: 'allowed-capabilities': selector: auth.authorization.authorized-capabilities.capabilities.@tostr tokenDuration: 300 signingKeyRefs: - name: trusted-headers-private-key algorithm: ES256 unauthenticated: headers: 'WWW-Authenticate': value: Bearer resource_metadata=https://mcp.example.com:port/.well-known/oauth-protected-resource body: value: | { "error": "Unauthorized", "message": "Access denied: Authentication required." } unauthorized: code: 401 headers: 'WWW-Authenticate': value: Bearer resource_metadata=https://mcp.example.com:port/.well-known/oauth-protected-resource body: value: | { "error": "Forbidden", "message": "Access denied: Unsupported method. New authentication required (401)." } EOF-
Replace
<mcp_auth_policy>with the name of your newAuthPolicyCR. -
Replace
<authn_namespace>with the namespace where you removed theAuthPolicy CRthat was on themcplistener. -
Replace
<mcp_gateway>with the name of your MCP gateway deployment.
-
-
Configure the MCP broker component to validate the signed header by running the following command:
$ oc patch mcpgatewayextension <mcp_gateway_extension> -n <mcp_system> --type='merge' \ -p='{"spec":{"trustedHeadersKey":{"secretName":"trusted-headers-public-key"}}}'-
Replace
<mcp_gateway_extension>with the name of yourMCPGatewayExtensionCR. -
Replace
<mcp_system>with the namespace where MCP gateway is installed.
-
-
Wait for the automatic MCP broker component redeployment to load the public key from the
SecretCR created in the earlier step by running the following command:$ oc rollout status deployment/<mcp_gateway> -n <mcp_system> --timeout=60s-
Replace
<mcp_gateway>with the name of your MCP gateway deployment. -
Replace
<mcp_system>with the namespace where your MCP gateway deployment is applied.
-
-
If the redeployment does not start, force it by running the following command:
$ oc rollout restart deployment/<mcp_gateway> -n <mcp_system>-
Replace
<mcp_gateway>with the name of your MCP gateway deployment. -
Replace
<mcp_system>with the namespace where your MCP gateway deployment is applied.
-
-
Verify that the new
AuthPolicyCR is enforced by running the following command:$ oc get authpolicy <mcp_auth_policy> -n <authn_namespace> -o jsonpath='{.status.conditions[?(@.type=="Enforced")].status}'-
Replace
<mcp_auth_policy>with the name of your newAuthPolicyCR. -
Replace
<authn_namespace>with the namespace where yourAuthPolicyCR is applied.Example outputTrue
-
-
Log out and log back in with the user credentials that you changed access for to get a fresh token.
-
Request all available tools from your MCP servers for the user or group whose access you revoked. The expected response is a list of all tools that the user or group can access from all registered MCP servers.