---
title: Protect an MCP server with PingOne
description: Tutorial to protect a sample MCP server using PingGateway and PingOne as the OAuth 2.1 authorization server
component: pinggateway
version: 2026
page_id: pinggateway:mcp:tutorial-pingone
canonical_url: https://docs.pingidentity.com/pinggateway/2026/mcp/tutorial-pingone.html
llms_txt: https://docs.pingidentity.com/pinggateway/llms.txt
docs_for_agents: https://developer.pingidentity.com/build-with-ai/docs-for-agents.md
revdate: 2026-09-15T10:10:08Z
keywords: ["AI", "Model Context Protocol", "OAuth 2.1", "Security"]
section_ids:
  before-you-begin: "Task 1: Before you begin"
  configure-as: "Task 2: Prepare PingOne as the AS"
  create-resource: Create a resource
  create-native-app: Create a native application
  configure-gateway: "Task 3: Configure PingGateway"
  start-mcp-agent: "Task 4: Start the MCP agent"
  validate-mcp: Validation
---

# Protect an MCP server with PingOne

## Task 1: Before you begin

* Complete the preparation described in [Prepare the sample MCP software](index.html#preparation).

* 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 [icon: plus-circle, set=fa].

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:

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

   The [SystemAndEnvSecretStore](../reference/SystemAndEnvSecretStore.html) 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 [icon: plus-circle, set=fa].

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 [icon: pencil-alt, set=fa], 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](#start-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)](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events), update the `admin.json` file for PingGateway to [enable streaming](../reference/AdminHttpApplication.html#AdminHttpApplication-streamingEnabled), 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`

   ```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](../_attachments/config/routes/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](#create-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](../reference/McpAuditFilter.html) audits MCP requests. PingGateway records MCP audit events in an `audit/mcp.audit.json` file.

   * The [McpProtectionFilter](../reference/McpProtectionFilter.html) 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](../reference/UriPathRewriteFilter.html) 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](../reference/TokenIntrospectionAccessTokenResolver.html) calls the PingOne introspect endpoint with the resource client credentials from the environment variable.

   * The [McpValidationFilter](../reference/McpValidationFilter.html) validates MCP protocol compliance for each request.

   * The [UriPathRewriteFilter](../reference/UriPathRewriteFilter.html) rewrites the route path prefix before the request reaches the MCP server.

   * The [ReverseProxyHandler](../reference/ReverseProxyHandler.html) uses a long `"soTimeout"` setting to accommodate an MCP agent receiving few or infrequent SSE updates.

   This simple route doesn't show [throttling](../reference/ThrottlingPolicies.html) 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](#create-native-app):

```console
$ 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:

   ```text
   [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:

   ```text
   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.
