Identity for AI

Secure a Cloudflare Workers MCP Server with PingOne Advanced Identity Cloud

In this configuration, the Cloudflare Workers Model Context Protocol (MCP) server delegates authentication to PingOne Advanced Identity Cloud. This enables clients, such as AI agents, to call a protected API on behalf of an authenticated end user.

Before you begin

Refer to the Ping Identity cloudflare-mcp Git repository.

Stack

Role Name Description

Platform

Serverless execution

Framework

Lightweight API endpoints

Agent Execution

Base class for implementing the stateful MCP server

Session State

Provides stateful, isolated storage for each MCP connection

Requirements

  • Node.js v20 or later

  • A PingOne Advanced Identity Cloud tenant

  • A Cloudflare account and Wrangler CLI enabled

  • Todo API deployed

Structure

mcp/
├── package.json               # Dependencies and scripts
├── tsconfig.json              # TypeScript compiler settings
├── wrangler.jsonc             # Worker configuration
└── src/
    ├── index.ts               # Defines the HTTP interface, handling MCP server discovery and MCP server routing
    ├── mcp.ts                 # Stateful MCP server as a cloudflare McpAgent (durable object)
    ├── config.ts              # Worker bindings and durable object session data
    ├── auth.ts                # Manages auth middleware and executes token exchange (delegation grant)
    └── todoApi.client.ts      # HTTP client to the downstream Todo API

Tasks

Configure PingOne Advanced Identity Cloud

1. Enable Dynamic Client Registration (DCR) for MCP Client Onboarding

This step allows MCP clients to automatically register themselves as public OpenID Connect (OIDC) clients and request the necessary scopes to call the downstream Todo API. The MCP client uses the MCP server’s protected resource metadata to determine how to register and which scopes to request.

  1. From the PingOne Advanced Identity Cloud Access Management Console, go to Services > OAuth2 Provider.

  2. In the Client Dynamic Registration tab, enable Allow Open Dynamic Client Registration.

  3. In the Advanced tab, whitelist the following Todo API scopes:

    • todo_api:read

    • todo_api:write

2. Configure MCP Server for Token Exchange

This step configures delegation and authorizes the MCP server to act on the end user’s behalf. This allows the MCP server to swap the end user’s token for a new token used to call the downstream Todo API. This new token is properly audienced for the target API and includes the act claim, identifying the MCP server as the trusted intermediary.

  1. Enable grant types and scopes

    1. From the PingOne Advanced Identity Cloud Access Management Console, go to Applications > OAuth2 > Clients.

    2. Create a new client for the MCP server.

    3. In the Core tab, set the scopes to maximum allowable permissions.

    4. In the Advanced tab, add the Client Credentials and Token Exchange grant types, to allow the MCP server to request its own token and perform the delegation, respectively.

  2. Flag eligible tokens for token exchange with the may_act claim

    Run a script upon user token issuance to inject a claim designating the MCP server as the trusted delegate. Because DCR prevents us from knowing the MCP client’s client ID, the script will be applied to all tokens but include safeguards to only target access tokens intended for the MCP server.

    1. From the PingOne Advanced Identity Cloud Access Management Console, go to Services > OAuth2 Provider > Scripts.

    2. Create a new script of type OAuth2 May Act.

    3. Copy and paste the following script, replacing placeholder values with your configuration values:

      (function () {
        var frJava = JavaImporter(org.forgerock.json.JsonValue);
        var normalizeUrl = function(url) { return url.replace(/\/$/, '') };
      
        var mcpServerClientId = 'MCP_SERVER_CLIENT_ID';  // placeholder
        var mcpServerUrl = 'URL_OF_DEPLOYED_MCP_SERVER'; // placeholder
      
        try {
          var requestedResourceList = requestProperties.requestParams.get('resource');
          var requestedResource = String(requestedResourceList.get(0));
      
          // Only modify tokens intended for the MCP server
          if (normalizeUrl(requestedResource) === normalizeUrl(mcpServerUrl)) {
      
            // Explicitly set the MCP server as the audience of this token
            token.setField("aud", mcpServerUrl);
      
            // Authorize MCP server to exchange this token
            var mayAct = frJava.JsonValue.json(frJava.JsonValue.object());
            mayAct.put('client_id', mcpServerClientId);
            mayAct.put('sub', mcpServerClientId);
            token.setMayAct(mayAct);
          };
        } catch (error) {};
      }());
    4. From the PingOne Advanced Identity Cloud Access Management Console, go to Services > OAuth2 Provider.

    5. In the Core tab, set the OAuth2 Access Token May Act Script.

  3. Control Exchanged Token Audience with an Access Token Modification script

    This step ensures the downstream API accepts the exchanged token. Since Advanced Identity Cloud does not automatically set the necessary audience claim during token exchange, use an access token modification script tied specifically to the MCP server’s client profile.

    1. From the Advanced Identity Cloud Access Management Console, go to Services > OAuth2 Provider > Scripts.

    2. Create a new script of type OAuth2 Access Token Modification (Next Gen).

    3. Copy and paste the following script, replacing the placeholder value with your downstream API URL:

      (function () {
        var frJava = JavaImporter(org.forgerock.json.JsonValue);
        var normalizeUrl = function(url) { return url.replace(/\/$/, '') };
      
        // Only modify MCP server access tokens granted with token exchange
        var grantType = requestProperties.requestParams.get('grant_type');
        if (!grantType || grantType.toString() !== '[urn:ietf:params:oauth:grant-type:token-exchange]') {
          return;
        };
      
        // Only allow MCP server to call these APIs
        var whitelistedAPIs = [
          'URL_OF_DEPLOYED_API', // placeholder value
        ];
      
        try {
          var requestedResourceList = requestProperties.requestParams.get('resource');
          var requestedResource = JSON.stringify(requestedResourceList).slice(2, -2);
      
          for (var i = 0; i < whitelistedAPIs.length; i++) {
            var whitelistedApi = normalizeUrl(whitelistedAPIs[i])
            if (normalizeUrl(requestedResource) === whitelistedApi) {
              // Resulting token will be audienced for the requested & whitelisted API
              accessToken.setField("aud", whitelistedApi);
              break;
            };
          };
        } catch (error) {};
      }());

3. Setup Cross-Origin Resource Sharing (CORS)

For external MCP clients to communicate with Advanced Identity Cloud during the OAuth flow, explicitly allow the MCP inspector.

  1. From the PingOne Advanced Identity Cloud dashboard, go to Tenant Settings > Global Settings.

  2. Click Cross-Origin Resource Sharing (CORS), and then Add a CORS Configuration.

  3. Configure the rule for the local MCP Inspector using the following values:

    1. In the Accepted Origins field, enter http://localhost:6277.

    2. In the Accepted Methods field, enter GET and POST.

    3. In the Accepted Headers field, enter authorization and content-type.

  4. Click Save CORS Configuration.

Deploy to Cloudflare

  1. Install dependencies and deploy the MCP server:

    npm install
    npm run deploy
  2. Use the Wrangler CLI to configure the remote environment variables:

    Name Description Example

    PING_AIC_ISSUER

    PingOne AIC environment domain

    https://<ENV>.forgeblocks.com:443/am/oauth2/alpha

    MCP_SERVER_URL

    URL of the deployed MCP server

    https://remote-mcp-ping-aic.<ENV>.workers.dev

    MCP_SERVER_CLIENT_ID

    ID of the MCP server client

    mcp_server

    MCP_SERVER_CLIENT_SECRET

    Secret of the MCP server client

    [A long, random, alphanumeric string]

    API_URL

    URL of the downstream Todo API

    https://todo-api-ping-aic.<ENV>.workers.dev

    wrangler secret put PING_AIC_ISSUER
    wrangler secret put MCP_SERVER_URL
    wrangler secret put MCP_SERVER_CLIENT_ID
    wrangler secret put MCP_SERVER_CLIENT_SECRET
    wrangler secret put API_URL
  3. Ensure the mcpServerUrl is correct in the Advanced Identity Cloud may_act script that you created in step 2.2.

Test the MCP Server with the MCP Inspector

  1. To validate the deployed MCP server, launch the Inspector:

    npx @modelcontextprotocol/inspector

    The Inspector starts on port 6277, which you allowed in the CORS configuration. To confirm that DCR is operational, the Inspector acts as an unregistered client and establishes a secure connection.

    A screenshot of the MCP Inspector initiating authentication and self-registering with Advanced Identity Cloud.
    Authentication flow
    1. The Inspector attempts an initial, unauthenticated request to the MCP endpoint (<mcp_server>/mcp).

    2. The MCP server responds with the protected resource metadata, triggering the discovery process.

    3. The MCP inspector consumes the metadata, performs discovery of the authorization server, and self-registers with Advanced Identity Cloud as a public OIDC client.

    4. The MCP Inspector initiates the OAuth authorization flow, securing the necessary user consent and establishing a stateful MCP connection.

  2. To validate token exchange, observe how the MCP server acquires a new token audienced for the downstream API. Use the who_am_I and peek_api_token_claims tools to compare the two tokens and confirm the following behavior:

    1. Both tokens share the same sub claim, confirming they identify the same authenticated end user.

    2. The subject token contains the may_act claim, explicitly authorizing the MCP server for exchange. The resulting delegation token contains the act claim, identifying the MCP server as the trusted intermediary acting on the user’s behalf.

    3. The subject token is audienced for the MCP server, while the delegation token is audienced for the downstream API.

    4. The subject token retains the user’s full, broader permissions. The delegation token is limited to only whats necessary for its target audience.

    The original end user token and the MCP server delegation token shown side-by-side.

Access the remote MCP server from Claude Desktop

  1. In Claude Desktop, click your profile icon, and then click Settings.

  2. Go to Connectors and then click Add Custom Connector.

  3. In the Remote MCP server URL field, enter the URL in this format: https://remote-mcp-ping-federate.<ENV>.workers.dev/mcp

    No OAuth Client ID or Secret is required since Claude will perform Dynamic Client Registration.

  4. Click Save.

  5. Connect to the MCP server and authenticate with PingFederate.

  6. Once connected, you can ask Claude: "Can you tell me what is in my Todo list?"

  7. Claude sees its connected tools and calls the appropriate tool after asking for consent.