PingGateway

Protect an MCP server with PingOne Advanced Identity Cloud

Task 1: Before you begin

Task 2: Prepare Advanced Identity Cloud as the AS

  1. Sign on to the Advanced Identity Cloud admin UI as an administrator.

  2. Ensure the OAuth2 Provider for your realm has an OAuth2 Access Token Modification script to support RFC 8707, Resource Indicators for OAuth 2.0 as required by MCP.

    If the OAuth2 Provider for your realm already uses an OAuth2 Access Token Modification script, add code to set the audience claim in the access token as shown for the following script.

    To add a new script, go to code Scripts > Auth Scripts > New Script > OAuth2 / OIDC > OAuth2 Access Token Modification, click Next, add the following script, and click Save and Close:

    • Name: MCP aud script

    • JavaScript:

      // Make sure the `audience` claim matches the resource identifier
      (function () {
      	  accessToken.setField("audience", requestProperties.get("requestParams").get("resource").get(0));
      	  // No return value is expected. Leave it undefined.
      	  }());

      The PingGateway McpProtectionFilter consumes the claim specified in its "resourceIdPointer" setting.

  3. Using the AM admin UI, update the OAuth2 Provider service settings for the realm.

    In the AM admin UI at open_in_new Native Consoles > Access Management, go to Services and change the following settings for the OAuth2 Provider service:

    • Advanced tab > Client Registration Scope Allowlist: Add test.

    • Client Dynamic Registration tab > Allow Open Dynamic Client Registration: Enable and click Save Changes.

    • Plugins tab > Access Token Modification Script: Set to MCP aud script if you added it as a new script and click Save Changes.

You have successfully prepared Advanced Identity Cloud to act as the AS.

Task 3: Configure PingGateway

Previously, you prepared PingGateway for HTTPS as described in Task 1: Before you begin. Now, configure PingGateway to protect the sample MCP server:

  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 updating "properties" as needed for your deployment:

    Linux

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

    Windows

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

    {
      "name": "mcp-aic",
      "condition": "${find(request.uri.path, '^/aic-mcp')}",
      "properties": {
        "amRealm": "/alpha",
        "gatewayUrl": "https://ig.example.com:8443",
        "mcpServerUrl": "http://localhost:8000",
        "tenantHostname": "myTenant.forgeblocks.com"
      },
      "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": "SecretsPasswords",
          "type": "Base64EncodedSecretStore",
          "config": {
            "secrets": {
              "agent.secret.id": "cGFzc3dvcmQ="
            }
          }
        },
        {
          "name": "AmService",
          "type": "AmService",
          "config": {
            "realm": "&{amRealm}",
            "agent": {
              "username": "ig_agent",
              "passwordSecretId": "agent.secret.id"
            },
            "secretsProvider": "SecretsPasswords",
            "sessionCache": {
              "enabled": true
            },
            "url": "https://&{tenantHostname}/am/"
          }
        },
        {
          "name": "rsFilter",
          "type": "OAuth2ResourceServerFilter",
          "config": {
            "scopes": [
              "test"
            ],
            "accessTokenResolver": {
              "type": "TokenIntrospectionAccessTokenResolver",
              "config": {
                "amService": "AmService",
                "providerHandler": {
                  "type": "Chain",
                  "config": {
                    "filters": [
                      {
                        "type": "HttpBasicAuthenticationClientFilter",
                        "config": {
                          "username": "ig_agent",
                          "passwordSecretId": "agent.secret.id",
                          "secretsProvider": "SecretsPasswords"
                        }
                      }
                    ],
                    "handler": "ClientHandler"
                  }
                }
              }
            }
          }
        }
      ],
      "handler": {
        "type": "Chain",
        "capture": "all",
        "config": {
          "filters": [
            {
              "type": "McpAuditFilter",
              "config": {
                "auditService": "AuditService"
              }
            },
            {
              "type": "UriPathRewriteFilter",
              "config": {
                "mappings": {
                  "/aic-mcp": "/"
                }
              }
            },
            {
              "type": "McpProtectionFilter",
              "config": {
                "resourceId": "&{gatewayUrl}/aic-mcp",
                "authorizationServerUri": "https://&{tenantHostname}/am/oauth2/realms/root/realms&{amRealm}",
                "resourceServerFilter": "rsFilter",
                "supportedScopes": [
                  "test"
                ],
                "resourceIdPointer": "/audience"
              }
            },
            {
              "type": "McpValidationFilter",
              "config": {
                "acceptedOrigins": ".*"
              }
            }
          ],
          "handler": {
            "type": "ReverseProxyHandler",
            "config": {
              "soTimeout": "20 seconds"
            }
          }
        }
      }
    }

    Source: mcp-aic.json

    Notice the following features of the route:

    • The sample route uses a base-64 encoded secret to connect to the AS. In production, don’t include secrets in route files.

    • 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 UriPathRewriteFilter sends the request to the root resource of the MCP server. The MCP server expects requests at /.

    • The McpProtectionFilter uses the RS configuration, extending it for MCP.

    • PingGateway validates MCP requests with an McpValidationFilter.

    • 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

In the directory where you unpacked the sample MCP agent, start the sample MCP agent again. This time, point it to the PingGateway route for MCP requests:

$ python3 sample-mcp-agent.py --mcp-server-url https://ig.example.com:8443/aic-mcp

You have successfully started the sample MCP agent.

Validation

With PingGateway protecting the MCP server, the sample MCP agent directs your browser to the AS 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 the end-user credentials (username: demo password: Ch4ng3!t) and consent to the requested test scope before closing the browser tab as prompted.

    You set these credentials when you set up a demo user in PingOne Advanced Identity Cloud.

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

    [INFO] Discovered tools [https://ig.example.com:8443/aic-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.