PingGateway

McpProtectionFilter

Protects a Model Context Protocol (MCP) server as an OAuth 2.0 protected resource.

This feature has Evolving interface stability. It’s subject to change without notice, even in a minor or maintenance release.

This filter has the following additional OAuth 2.0 resource server capabilities:

  • Registers a static OAuth 2.0 protected resource metadata at the /.well-known/oauth-protected-resource endpoint.

  • Adapts any WWW-Authenticate response header to ensure it includes a resource_metadata directive.

  • Validates the aud claim in the OAuth 2.0 access token to ensure it matches the "resourceId" setting for this filter.

The MCP resource parameter implementation for version 2025-11-25 (also documented for version 2025-06-18) depends on RFC 9728, OAuth 2.0 Protected Resource Metadata, which defines the resource identifier as an HTTPS URL.

In other words, in MCP the resource server protects access to the resource with HTTPS. For this filter, you must therefore access remote resources over HTTPS.

Usage

{
  "name": string,
  "type": "McpProtectionFilter",
  "config": {
    "resourceId": configuration expression<string>,
    "authorizationServerUri": config expression<url>,
    "resourceServerFilter": Filter reference,
    "supportedScopes": [ configuration expression<string>, …​ ]
    "realm": configuration expression<string>,
    "resourceIdPointer": configuration expression<string>,
    "wellKnownFilter": Filter reference
    }
  }
}

Properties

"resourceId": configuration expression<string>, required

The protected resource identifier to return, an https:// URL with no fragment.

"authorizationServerUri": configuration expression<url>, required

The URL of the OAuth 2.0 authorization server to validate access tokens.

"resourceServerFilter": Filter reference, required_

The OAuth2ResourceServerFilter to use.

"supportedScopes": _array of configuration expression<string>, optional

List of supported scopes to return in the resource metadata.

These should match the scopes in the settings of the filter that the "resourceServerFilter" references.

Default: none.

"realm": _configuration expression<string>, optional

Name of the realm for authentication challenges and returned to the client application on error.

This should match the realm in the settings of the filter that the "resourceServerFilter" references.

Default: no realm.

"resourceIdPointer": configuration expression<string>, optional

JSON pointer to the resource ID claim in the access token.

Default: "/aud".

"wellKnownFilter": configuration expression<url>, optional

The filter for requests targeting the well-known endpoint. For example, use a CorsFilter to allow preflight requests in the browser.

If you don’t specify a filter, PingGateway logs a warning at startup time.

Default: none.

Examples

Protect an MCP server with PingFederate

When PingFederate is the OAuth 2.0 authorization server, the MCP route configuration differs from the PingOne Advanced Identity Cloud or PingAM configuration in several ways. The following route is based on the tutorial in MCP security gateway, adapted for PingFederate:

{
  "name": "mcp",
  "condition": "${find(request.uri.path, '^/mcp')}",
  "properties": {
    "pfUrl": "https://pf.example.com:9031",
    "gatewayUrl": "https://ig.example.com:8443",
    "mcpServerUrl": "http://localhost:8000"
  },
  "baseURI": "&{mcpServerUrl}",
  "heap": [
    {
      "name": "AuditService",
      "type": "AuditService",
      "config": {
        "eventHandlers": [
          {
            "class": "org.forgerock.audit.handlers.json.JsonAuditEventHandler",
            "config": {
              "name": "json",
              "logDirectory": "&{ig.instance.dir}/audit",
              "topics": [ "access", "mcp" ]
            }
          }
        ]
      }
    },
    {
      "name": "PingFederateClientHandler",
      "type": "ClientHandler",
      "config": {
        "tls": {
          "type": "ClientTlsOptions",
          "config": {
            "trustManager": {
              "type": "TrustAllManager"
            },
            "hostnameVerifier": "ALLOW_ALL"
          }
        }
      }
    },
    {
      "name": "rsFilter",
      "type": "OAuth2ResourceServerFilter",
      "config": {
        "scopes": [ "test" ],
        "accessTokenResolver": {
          "type": "StatelessAccessTokenResolver",
          "config": {
            "secretsProvider": {
              "type": "JwkSetSecretStore",
              "config": {
                "jwkUrl": "&{pfUrl}/pf/JWKS",
                "handler": "PingFederateClientHandler"
              },
            "issuer": "&{pfUrl}",
            "verificationSecretId": "gateway.verification.pf.key"
            }
          }
        }
      }
    }
  ],
  "handler": {
    "type": "Chain",
    "capture": "all",
    "config": {
      "filters": [
        {
          "type": "McpAuditFilter",
          "config": {
            "auditService": "AuditService"
          }
        },
        {
          "type": "UriPathRewriteFilter",
          "config": {
            "mappings": {
              "/mcp": "/"
            }
          }
        },
        {
          "type": "McpProtectionFilter",
          "config": {
            "resourceId": "&{pfUrl}",
            "authorizationServerUri": "&{pfUrl}",
            "resourceServerFilter": "rsFilter",
            "supportedScopes": [ "test" ]
          }
        },
        {
          "type": "McpValidationFilter",
          "config": {
            "acceptedOrigins": ".*"
          }
        }
      ],
      "handler": {
        "type": "ReverseProxyHandler",
        "config": {
          "soTimeout": "20 seconds"
        }
      }
    }
  }
}

Notice the following differences from an PingOne Advanced Identity Cloud or PingAM configuration:

  • There is no AmService heap object.

  • A dedicated PingFederateClientHandler uses TrustAllManager with hostnameVerifier: ALLOW_ALL because PingFederate uses a self-signed certificate by default.

    Replace TrustAllManager with a proper trust store in production.
  • The rsFilter uses a StatelessAccessTokenResolver to introspect tokens. The JwkSetSecretStore resolves the gateway.verification.pf.key value.

  • The McpProtectionFilter omits realm because PingFederate has no realm concept. The authorizationServerUri is the PingFederate base URL without a realm path segment. The resourceIdPointer uses the default value (/aud), which matches the standard aud claim PingFederate includes in access tokens.

  • The cache properties amService and onNotificationDisconnection in OAuth2ResourceServerFilter don’t apply to PingFederate because PingFederate has no WebSocket notification service.