PingGateway

Protect an MCP server with PingOne

Task 1: Before you begin

  • Complete the preparation described in Prepare the sample MCP software.

  • Make sure you can access the PingOne administration console as an administrator.

  • Set up PingGateway with HTTPS on port 8443.

    The following tasks use https://ig.example.com:8443.

Task 2: Prepare PingOne as the AS

Prepare PingOne to act as the OAuth 2.1 AS for MCP by creating a resource and an application.

Create a resource

  1. In the PingOne admin console, go to Applications > Resources, and click .

  2. Set Resource Name to a name for the resource, for example, MCP Resource, and click Next.

  3. Set Audience to the URL that PingGateway uses as the resource identifier: https://ig.example.com:8443/p1-mcp.

    PingGateway validates the aud claim in each access token against this value. Update this value if you change the gateway URL or route path.

  4. Click Save.

  5. On the Scopes tab, click + Add Scope, set Name to test, and click Save.

  6. Enable the resource using the toggle at the top of the page.

  7. On the Overview tab, note the Client ID and Client Secret.

    PingGateway uses these credentials to call the PingOne token introspection endpoint.

  8. Add the base64-encoded client secret to the PingGateway environment:

    $ export PINGONE_RESOURCE_SECRET=$(echo -n '<resource-client-secret>' | base64)

    The SystemAndEnvSecretStore in the route reads the secret ID pingone.resource.secret from the PINGONE_RESOURCE_SECRET environment variable.

  9. Restart PingGateway to load the secret.

Create a native application

Create the application that the MCP agent uses to authenticate. PingOne requires pre-registered clients. It doesn’t support dynamic client registration.

  1. In the PingOne admin console, go to Applications > Applications, and click .

  2. Select Native App, set Application Name to a name, for example, MCP Client, and click Save.

  3. On the Configuration tab, set:

    • Grant Types: Authorization Code and Refresh Token

    • Token Endpoint Auth Method: None

    • PKCE Enforcement: S256 Required

    • Redirect URIs: http://localhost:3000/callback

  4. Click Save.

  5. On the Resources tab, click , select the resource you created, allow the test scope, and click Save.

  6. On the Overview tab, note the Client ID.

    You pass this value to the sample MCP agent in Task 4: Start the MCP agent.

You have successfully prepared PingOne to act as the AS.

Task 3: Configure PingGateway

  1. If the MCP server uses server-sent events (SSE), update the admin.json file for PingGateway to enable streaming, and restart PingGateway.

    Skip this step for the sample MCP server.

  2. Add the following route to PingGateway and correct the "properties" settings:

    Linux

    $HOME/.openig/config/routes/mcp-p1.json

    Windows

    %appdata%\OpenIG\config\routes\mcp-p1.json

    {
      "name": "mcp-p1",
      "condition": "${find(request.uri.path, '^/p1-mcp')}",
      "properties": {
        "mcpServerUrl": "http://localhost:8000",
        "gatewayUrl": "https://ig.example.com:8443",
        "pingOneAsUrl": "https://auth.pingone.com/<envId>/as",
        "pingOneIntrospectUrl": "https://auth.pingone.com/<envId>/as/introspect",
        "pingOneResourceClientId": "<resourceId>"
      },
      "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": "rsFilter",
          "type": "OAuth2ResourceServerFilter",
          "config": {
            "scopes": [
              "test"
            ],
            "accessTokenResolver": {
              "type": "TokenIntrospectionAccessTokenResolver",
              "config": {
                "endpoint": "&{pingOneIntrospectUrl}",
                "providerHandler": {
                  "type": "Chain",
                  "config": {
                    "filters": [
                      {
                        "type": "ClientSecretBasicAuthenticationFilter",
                        "config": {
                          "clientId": "&{pingOneResourceClientId}",
                          "clientSecretId": "pingone.resource.secret",
                          "secretsProvider": {
                            "type": "SystemAndEnvSecretStore"
                          }
                        }
                      }
                    ],
                    "handler": "ClientHandler"
                  }
                }
              }
            }
          }
        }
      ],
      "handler": {
        "type": "Chain",
        "config": {
          "filters": [
            {
              "type": "McpAuditFilter",
              "config": {
                "auditService": "AuditService"
              }
            },
            {
              "type": "McpProtectionFilter",
              "config": {
                "resourceId": "&{gatewayUrl}/p1-mcp",
                "authorizationServerUri": "&{pingOneAsUrl}",
                "resourceServerFilter": "rsFilter",
                "supportedScopes": [
                  "test"
                ]
              }
            },
            {
              "type": "McpValidationFilter",
              "config": {
                "acceptedOrigins": ".*"
              }
            },
            {
              "type": "UriPathRewriteFilter",
              "config": {
                "mappings": {
                  "/p1-mcp": "/"
                }
              }
            }
          ],
          "handler": {
            "type": "ReverseProxyHandler",
            "config": {
              "soTimeout": "20 seconds"
            }
          }
        }
      }
    }

    Source: mcp-p1.json

    Update the following properties:

    • mcpServerUrl — the URL of the MCP server. The default assumes the sample MCP server runs on your computer.

    • gatewayUrl — the base URL of PingGateway. The route uses this to construct the resource identifier.

    • pingOneAsUrl — the AS base URL for your PingOne environment, for example, https://auth.pingone.eu/<envId>/as for the EU region. Replace <envId> with your PingOne environment ID and adjust the domain for your region.

    • pingOneIntrospectUrl — the introspect endpoint for your environment.

    • pingOneResourceClientId — the resource Client ID noted in Create a resource.

    Notice the following features of the route:

    • PingGateway acts as an OAuth 2.1 resource server (RS) when protecting the sample MCP server.

    • The McpAuditFilter audits MCP requests. PingGateway records MCP audit events in an audit/mcp.audit.json file.

    • The McpProtectionFilter serves the MCP OAuth 2.1 protected resource metadata endpoint and validates that each access token is intended for this resource. The filter comes before UriPathRewriteFilter so it sees the original request path.

    • The McpProtectionFilter uses the default "resourceIdPointer" of /aud, which matches the aud claim PingOne includes in access tokens.

    • The TokenIntrospectionAccessTokenResolver calls the PingOne introspect endpoint with the resource client credentials from the environment variable.

    • The McpValidationFilter validates MCP protocol compliance for each request.

    • The UriPathRewriteFilter rewrites the route path prefix before the request reaches the MCP server.

    • The ReverseProxyHandler uses a long "soTimeout" setting to accommodate an MCP agent receiving few or infrequent SSE updates.

    This simple route doesn’t show throttling or fine-grained access control. Add those features as needed to meet your security requirements.

  3. Verify the PingGateway log shows the route loaded successfully.

You have successfully configured PingGateway to protect the sample MCP server.

Task 4: Start the MCP agent

The --client-id argument used in this task requires the sample MCP agent from PingGateway 2026.9 or later.

In the directory where you unpacked the sample MCP agent, start the sample MCP agent again. This time, point it to the PingGateway route and pass the application client ID you noted in Create a native application:

$ python3 sample-mcp-agent.py \
    --mcp-server-url https://ig.example.com:8443/p1-mcp \
    --client-id <native-app-client-id>

No client secret is needed because the application uses PKCE only.

Passing --client-id pre-registers the client in the agent’s token storage, so the agent skips dynamic client registration and uses the pre-registered client directly.

You have successfully started the sample MCP agent.

Validation

With PingGateway protecting the MCP server, the sample MCP agent directs your browser to PingOne to sign on as an end user and authorize access to make MCP requests. If it can’t open the browser, the sample MCP agent shows the URL you can paste into a private or incognito window.

  1. Sign on with your PingOne user credentials and consent to the requested test scope before closing the browser tab as prompted.

  2. In the terminal where the sample MCP agent runs, notice the available commands:

    [INFO] Discovered tools [https://ig.example.com:8443/p1-mcp]:
    [INFO] - geocode: Returns a list of objects containing city name, latitude, longitude, country, admin1 (region), and timezone for each matching city
    [INFO] - forecast_daily: Returns a multi-day weather forecast for a given location
    [INFO] - forecast_periods: Returns weather forecasts for each representative period of the current day
    [INFO] - forecast_hourly: Returns an hourly weather forecast for the current day
    [INFO] - weather_at_time: Returns the forecasted weather for a specific time at a given location
    
    Enter your message (or 'exit|quit|q'):
  3. Enter a prompt and get a response from the MCP server through PingGateway, then exit the agent:

    The following example uses the forecast_daily tool to get the daily forecast for Tokyo:

    Enter your message (or 'exit|quit|q'): What is the daily forecast for Tokyo?
    Agent: The daily forecast for Tokyo is:
    
    <MCP server response with forecast details>
    
    Enter your message (or 'exit|quit|q'): exit
    User requested exit. Goodbye!

    Find additional details about the MCP request in the PingGateway log.

You have successfully validated PingGateway can protect the MCP server.