Enterprise·
advanced
·15 min read·Sep 12, 2026
By Rad Tome·Lead AI Systems Architect

Dynamic Client Registration and OAuth 2.0 for Remote MCP

Implementing RFC 7591 Dynamic Client Registration (DCR) and OAuth 2.0 Token Exchange for authenticating autonomous AI agents connecting to remote MCP servers.

oauth2dcrsecurityauthenticationmcprfc7591
Interactive Tool
1-Click Export

Generate & Validate Multi-Client MCP Config

One-click export with environment variables & path locators for Claude Desktop, Cursor, Windsurf, and OpenAI Codex CLI.

Open in Generator

Dynamic Client Registration and OAuth 2.0 for Remote MCP

As Model Context Protocol (MCP) servers transition to remote cloud environments accessible via Streamable HTTP and Server-Sent Events (SSE), static API keys become an acute enterprise vulnerability. Autonomous agents, ephemeral developer IDE containers, and multi-tenant agent frameworks cannot securely hardcode static credentials without risking credential sprawl, permission over-provisioning, and severe compliance violations under SOC 2 and ISO 27001.

Securing remote MCP infrastructure at scale requires standardized Dynamic Client Registration (DCR, RFC 7591) paired with OAuth 2.0 Token Exchange (RFC 8693). This enables AI clients to register ephemeral credentials on-the-fly, negotiate cryptographically scoped tool execution permissions, and automatically rotate access tokens without human intervention.

This guide provides the complete architectural pattern, wire-level protocol flows, and concrete implementation for establishing zero-trust OAuth 2.0 and DCR authentication for remote MCP servers.


1. Authentication Architecture: Ephemeral AI Clients

In an enterprise DCR architecture, an AI client host (e.g., Cursor, Claude Code, or a cloud-hosted LangGraph runner) first contacts the enterprise Identity Provider (IdP) or MCP Authorization Server. It registers its instance, obtains an ephemeral client_id and client_secret, exchanges an employee or workload identity token for an scoped MCP Access Token, and connects to the remote server.

mermaid
sequenceDiagram
    autonumber
    participant Agent as AI Host Client (e.g. Cursor / Agent)
    participant AuthServer as OAuth 2.0 / DCR Server (Okta/Keycloak)
    participant MCP as Remote MCP Server (Streamable HTTP)

    Agent->>AuthServer: POST /register (RFC 7591 DCR Request)
    AuthServer-->>Agent: 201 Created (client_id, client_secret, scopes)
    
    Agent->>AuthServer: POST /token (Grant: client_credentials / token-exchange)
    AuthServer-->>Agent: 200 OK (access_token: mcp:db:read mcp:git:pr)
    
    Agent->>MCP: GET /sse (Authorization: Bearer <access_token>)
    MCP-->>Agent: 200 OK (Event Stream Established)
    
    Agent->>MCP: POST /messages [tools/call: query_records]
    MCP-->>Agent: 200 OK (Execution Result)

2. Dynamic Client Registration (RFC 7591) Wire Flow

When a developer spins up a new ephemeral development sandbox or an autonomous agent initializes a task, the client programmatically registers itself using RFC 7591:

Client Registration Request (POST /oauth/register)

http
POST /oauth/register HTTP/1.1
Host: auth.enterprise.internal
Content-Type: application/json
Authorization: Bearer <INITIAL_REGISTRATION_TOKEN>

{
  "client_name": "Cursor-DevInstance-user-8921",
  "grant_types": ["client_credentials", "urn:ietf:params:oauth:grant-type:token-exchange"],
  "response_types": ["token"],
  "scope": "mcp:tools:read mcp:tools:call:db_query mcp:tools:call:k8s_read",
  "token_endpoint_auth_method": "private_key_jwt",
  "software_id": "4c9d8a-cursor-agent",
  "software_version": "2026.3.1"
}

Authorization Server Registration Response

The server validates the policy, provisions the client, bounds its maximum permissions, and sets an aggressive registration expiration:

http
HTTP/1.1 201 Created
Content-Type: application/json
Cache-Control: no-store

{
  "client_id": "mcp-client-881b29a-4c22",
  "client_secret": "sec_77f81a0e8d91c2b3e4f5a6b7c8d9e0f1",
  "client_id_issued_at": 1773446400,
  "client_secret_expires_at": 1773532800,
  "registration_client_uri": "https://auth.enterprise.internal/oauth/register/mcp-client-881b29a-4c22",
  "scope": "mcp:tools:read mcp:tools:call:db_query"
}

[!NOTE] In production environments, client credentials registered via DCR should be granted a maximum Time-To-Live (TTL) of 24 hours. Ephemeral agent workers terminate their credentials upon task completion.


3. Scoped Token Minting & JSON-RPC Access Control

Once registered, the AI host exchanges its identity for an MCP-scoped JWT access token:

Token Request (POST /oauth/token)

http
POST /oauth/token HTTP/1.1
Host: auth.enterprise.internal
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
&client_id=mcp-client-881b29a-4c22
&client_secret=sec_77f81a0e8d91c2b3e4f5a6b7c8d9e0f1
&scope=mcp:tools:call:db_query

Resulting JWT Payload (Decoded)

json
{
  "iss": "https://auth.enterprise.internal",
  "sub": "mcp-client-881b29a-4c22",
  "aud": "https://mcp-server.enterprise.internal",
  "exp": 1773450000,
  "nbf": 1773446400,
  "scope": "mcp:tools:read mcp:tools:call:db_query",
  "enterprise": {
    "org_id": "engineering_core",
    "user_email": "jane.dev@enterprise.internal",
    "clearance_level": "l4"
  }
}

4. MCP Server Authorization Middleware Implementation (TypeScript)

The remote MCP server verifies the JWT token on both the initial SSE connection and on individual tools/call POST invocations, enforcing granular scope checks per tool.

typescript
import Fastify, { FastifyRequest, FastifyReply } from 'fastify';
import fastifyJwt from '@fastify/jwt';
import jwksRsa from 'jwks-rsa';

const app = Fastify({ logger: true });

// 1. Configure JWKS validation for enterprise IdP (Okta / Keycloak)
const jwksClient = jwksRsa({
  jwksUri: 'https://auth.enterprise.internal/.well-known/jwks.json',
  cache: true,
  rateLimit: true,
  jwksRequestsPerMinute: 10
});

// Helper: Custom JWT Secret Provider for Remote Key Sets
const getPublicKey = async (req: FastifyRequest, token: any) => {
  const decodedToken = token as { header: { kid: string } };
  const key = await jwksClient.getSigningKey(decodedToken.header.kid);
  return key.getPublicKey();
};

app.register(fastifyJwt, {
  secret: getPublicKey,
  algorithms: ['RS256']
});

// 2. Scope Verification Decorator
function enforceToolScope(requiredScope: string) {
  return async (request: FastifyRequest, reply: FastifyReply) => {
    try {
      await request.jwtVerify();
      const user = request.user as { scope?: string };
      const scopes = (user.scope || '').split(' ');

      if (!scopes.includes(requiredScope) && !scopes.includes('mcp:admin')) {
        reply.status(403).send({
          jsonrpc: '2.0',
          id: (request.body as any)?.id || null,
          error: {
            code: -32003,
            message: `Forbidden: Missing required OAuth 2.0 scope '${requiredScope}'`,
            data: { requiredScope, currentScopes: scopes }
          }
        });
      }
    } catch (err) {
      reply.status(401).send({
        jsonrpc: '2.0',
        id: (request.body as any)?.id || null,
        error: {
          code: -32002,
          message: 'Unauthorized: Invalid or expired OAuth 2.0 Bearer token'
        }
      });
    }
  };
}

// 3. Authenticated Streamable SSE Endpoint
app.get('/sse', { preHandler: [app.authenticate] }, async (request, reply) => {
  reply.raw.setHeader('Content-Type', 'text/event-stream');
  reply.raw.setHeader('Cache-Control', 'no-cache');
  reply.raw.setHeader('Connection', 'keep-alive');
  reply.raw.flushHeaders();

  const sessionId = generateSessionId();
  reply.raw.write(`event: endpoint\ndata: /messages?sessionId=${sessionId}\n\n`);
});

// 4. Scoped Tool Execution Route
app.post('/messages', async (request: FastifyRequest, reply: FastifyReply) => {
  const body = request.body as { jsonrpc: string; id: any; method: string; params: any };

  if (body.method === 'tools/call') {
    const toolName = body.params?.name;
    const requiredScope = `mcp:tools:call:${toolName}`;

    // Execute dynamic scope evaluation
    await enforceToolScope(requiredScope)(request, reply);
    if (reply.sent) return;

    // Proceed to tool execution
    const result = await dispatchToolExecution(toolName, body.params?.arguments);
    return reply.send({
      jsonrpc: '2.0',
      id: body.id,
      result: { content: [{ type: 'text', text: JSON.stringify(result) }] }
    });
  }

  // Handle standard lifecycle methods
  return reply.send({ jsonrpc: '2.0', id: body.id, result: { status: 'acknowledged' } });
});

async function dispatchToolExecution(name: string, args: any) {
  return { status: 'success', data: 'Protected enterprise data payload' };
}

function generateSessionId() {
  return 'sess_' + Math.random().toString(36).substring(2, 15);
}

app.listen({ port: 8080, host: '0.0.0.0' });

5. Token Exchange (RFC 8693) for Multi-Agent Workflows

In agentic chains where an orchestrator agent delegates subtasks to specialized worker agents, passing the orchestrator's broad administrative token violates least privilege. Instead, the orchestrator performs an OAuth 2.0 Token Exchange (RFC 8693) to downscope permissions for downstream subagents.

http
POST /oauth/token HTTP/1.1
Host: auth.enterprise.internal
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&subject_token=eyJhbGciOiJSUzI1Ni... (Orchestrator Token)
&subject_token_type=urn:ietf:params:oauth:token-type:access_token
&audience=https://mcp-analytics.internal
&scope=mcp:tools:call:aggregate_metrics

The authorization server returns an access token restricted solely to aggregate_metrics, guaranteeing that if the subagent experiences a prompt injection attack, it cannot invoke broader tools like delete_database or update_user_roles.


6. Standard Client Configuration with Dynamic Tokens

Modern MCP hosts support dynamic bearer authentication via environment variables or local token helper binaries.

Cursor / Claude Desktop Setup (mcp.json)

json
{
  "mcpServers": {
    "enterprise-oauth-mcp": {
      "url": "https://mcp.ai.enterprise.com/sse",
      "headers": {
        "Authorization": "Bearer ${ENTERPRISE_OAUTH_TOKEN}"
      }
    }
  }
}

Token Helper Automated Refresh Daemon

To automate token acquisition, enterprise IT teams deploy an OS-level token daemon that handles DCR and refreshes the local token cache in ~/.mcp/tokens.json:

bash
# Developer initial bootstrap
mcp-auth-cli login --dcr-endpoint=https://auth.enterprise.internal/oauth/register

# Token helper automatically writes active JWT:
export ENTERPRISE_OAUTH_TOKEN=$(cat ~/.mcp/active_token.jwt)

Related Security & Architecture Guides

Ready to Deploy?

Build your full agent toolstack in the Visual Generator

Combine Dynamic Client Registration and OAuth 2.0 for Remote MCP with databases, search APIs, and memory graphs in a single configuration file.

Customize in Generator

Dynamic Client Registration and OAuth 2.0 for Remote MCP FAQ

What is the Dynamic Client Registration and OAuth 2.0 for Remote MCP?

Implementing RFC 7591 Dynamic Client Registration (DCR) and OAuth 2.0 Token Exchange for authenticating autonomous AI agents connecting to remote MCP servers.

How do I configure Dynamic Client Registration and OAuth 2.0 for Remote MCP in Claude Desktop or Cursor?

You can copy the configuration JSON from our guide or launch the interactive MCP Codex Config Generator at https://mcp-codex.com/generator to export valid configs in 1 click.

Can I use Dynamic Client Registration and OAuth 2.0 for Remote MCP with the OpenAI Codex CLI?

Yes, OpenAI Codex CLI supports Model Context Protocol. You can add it directly to ~/.codex/config.toml or pass arguments to codex mcp add.

RT

Written by Rad Tome

Lead AI Systems Architect & Founder, MCP Codex

@RadTome

Specializing in Model Context Protocol (MCP) integrations, autonomous AI agent orchestration, and distributed developer toolchains. Researches and benchmarks production MCP client-server architectures across OpenAI Codex, Claude, and Cursor.

Editorial Integrity: All configurations, schemas, and commands verified against live GitHub repositories and tested in local sandbox runtimes.

Related Guides