# How the MCP OAuth Provider Handles Dynamic Client Registration in OpenSEO

> Learn how the OpenSEO MCP OAuth provider handles dynamic client registration by intercepting requests normalizing metadata and delegating to Cloudflare workers-oauth-provider.

- Repository: [Every App/open-seo](https://github.com/every-app/open-seo)
- Tags: how-to-guide
- Published: 2026-07-27

---

**The OpenSEO MCP OAuth provider handles dynamic client registration by intercepting requests to `/api/auth/oauth2/register`, normalizing the client metadata to enforce confidential client authentication, and delegating actual client creation to Cloudflare's workers-oauth-provider.**

The `every-app/open-seo` repository implements a Machine-Client-Powered (MCP) OAuth flow that supports **dynamic client registration** (DCR) for third-party applications. This implementation bridges the gap between public client registration requests and Cloudflare's confidential client requirements through a lightweight normalization shim.

## Dynamic Client Registration Endpoint

The provider exposes a dedicated registration path that acts as the entry point for all DCR requests. In [`src/server/mcp/oauth-provider.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/oauth-provider.ts), the constant `OAUTH_REGISTER_PATH` defines this endpoint:

```ts
const OAUTH_REGISTER_PATH = "/api/auth/oauth2/register";

```

When `createOpenSeoOAuthProvider` receives a request matching this path, it intercepts the payload before forwarding it to the underlying Cloudflare OAuth provider. The handler checks the URL pathname and triggers the normalization process:

```ts
if (url.pathname === OAUTH_REGISTER_PATH) {
  // Cloudflare's provider can reject public DCR clients, but Perplexity
  // does not appear to retry as confidential and instead expects a
  // client_secret. Normalize before handing the request to Cloudflare so
  // it still owns client creation, secret hashing, and token storage.
  request = await normalizeClientRegistrationRequest(request);
}
return provider.fetch(request, env, ctx);

```

This interception point ensures that OpenSEO can modify incoming registration metadata while allowing Cloudflare to retain responsibility for secure client creation and token management.

## Normalizing Client Registration Requests

The core transformation logic resides in [`src/server/mcp/oauth-registration.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/oauth-registration.ts) within the `normalizeClientRegistrationRequest` function. This shim preprocesses the request body to ensure compatibility with MCP authentication requirements.

### Payload Size Limits and Parsing

The shim first protects against oversized payloads by rejecting bodies larger than 1 MiB:

```ts
if (request.body && contentLength > 1024 * 1024) {
  // Let Cloudflare handle the error for oversized bodies
  return request;
}

```

If the body parses successfully as JSON, the function proceeds with metadata transformation. If parsing fails, the original request returns unchanged, allowing Cloudflare to handle malformed JSON errors natively.

### Enforcing Confidential Authentication

The critical transformation occurs when the shim inspects the `token_endpoint_auth_method` field. To ensure the MCP can use client secrets during subsequent token exchanges, the function rewrites public client registrations to use `client_secret_post`:

```ts
if (
  metadata.token_endpoint_auth_method === undefined ||
  metadata.token_endpoint_auth_method === "none"
) {
  // Perplexity registers as a public client but then rejects DCR responses
  // without a client_secret. Use client_secret_post because its validator
  // accepts that method but rejects client_secret_basic.
  metadata.token_endpoint_auth_method = CONFIDENTIAL_CLIENT_AUTH_METHOD;
}

```

After modifying the metadata, the shim constructs a fresh `Request` object with updated headers to ensure Cloudflare processes the normalized payload correctly:

```ts
const headers = new Headers(request.headers);
headers.set("Content-Type", "application/json");
headers.delete("Content-Length");

return new Request(request.url, {
  method: request.method,
  headers,
  body: JSON.stringify(metadata),
});

```

Stripping the `Content-Length` header prevents request mismatch errors since the JSON stringification may alter the body size from the original request.

## Delegation to Cloudflare OAuth Provider

After normalization, the sanitized request flows to `new OAuthProvider(options)` from Cloudflare's `workers-oauth-provider` library. Cloudflare handles the security-critical operations:

- **Client creation** – Generates and stores unique client identifiers
- **Secret hashing** – Cryptographically hashes the client secret before persistence
- **Token storage** – Manages refresh and access tokens in Cloudflare's built-in storage layer

This architecture allows OpenSEO to maintain minimal custom code for DCR while leveraging Cloudflare's hardened OAuth implementation for sensitive cryptographic operations.

## MCP Scope Validation

Beyond registration normalization, the MCP OAuth provider enforces scope requirements during the authorization flow. When clients omit requested scopes, the provider automatically grants the full MCP scope list defined in `MCP_OAUTH_SCOPES`:

```ts
if (requestedScopes.length === 0) {
  return [...MCP_OAUTH_SCOPES];
}

```

The provider strictly validates that all authorized clients possess the essential `MCP_SCOPE`. If this scope is missing, the authorization aborts immediately, ensuring that registered clients can access required MCP resources.

## Practical Implementation Examples

### Registering a New MCP Client

Submit a dynamic client registration request without specifying an authentication method:

```bash
curl -X POST https://your-openseo.example.com/api/auth/oauth2/register \
  -H "Content-Type: application/json" \
  -d '{
    "client_name": "My MCP App",
    "redirect_uris": ["https://myapp.example.com/callback"],
    "grant_types": ["authorization_code"],
    "response_types": ["code"]
  }'

```

The OpenSEO shim automatically injects `"client_secret_post"` as the `token_endpoint_auth_method` before Cloudflare processes the request, ensuring the response includes a `client_secret` even for clients that initially register as public.

### Obtaining an Access Token

Use the registered client credentials to complete the authorization flow. First, initiate user authorization:

```bash
curl -G https://your-openseo.example.com/api/auth/oauth2/authorize \
  --data-urlencode "response_type=code" \
  --data-urlencode "client_id=YOUR_CLIENT_ID" \
  --data-urlencode "redirect_uri=https://myapp.example.com/callback" \
  --data-urlencode "scope=mcp" \
  --data-urlencode "state=xyz"

```

Then exchange the authorization code for tokens:

```bash
curl -X POST https://your-openseo.example.com/api/auth/oauth2/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code&code=AUTH_CODE&redirect_uri=https://myapp.example.com/callback&client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET"

```

Because the registration process enforced `client_secret_post`, the client secret must be included in the request body rather than the Authorization header.

### Accessing MCP Resources

Use the obtained token to call MCP-protected endpoints:

```bash
curl -H "Authorization: Bearer ACCESS_TOKEN" \
     https://your-openseo.example.com/mcp/keyword-research?keyword=example

```

The MCP transport layer validates the token against Cloudflare's storage and extracts the granted scopes before processing the request.

## Summary

- **Endpoint interception**: The MCP OAuth provider captures DCR requests at `/api/auth/oauth2/register` in [`src/server/mcp/oauth-provider.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/oauth-provider.ts) before Cloudflare processes them.
- **Metadata normalization**: The `normalizeClientRegistrationRequest` function in [`src/server/mcp/oauth-registration.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/oauth-registration.ts) enforces `client_secret_post` authentication for all clients to ensure MCP compatibility.
- **Security delegation**: Cloudflare's `workers-oauth-provider` handles actual client creation, secret hashing, and token storage after OpenSEO normalizes the request.
- **Scope enforcement**: The provider automatically grants `MCP_OAUTH_SCOPES` when omitted and validates the presence of the essential `MCP_SCOPE` during authorization.
- **Request reconstruction**: The normalization shim rebuilds the request with updated JSON bodies and stripped `Content-Length` headers to prevent downstream parsing errors.

## Frequently Asked Questions

### What happens if a client tries to register with token_endpoint_auth_method set to "none"?

The OpenSEO normalization shim automatically rewrites the `token_endpoint_auth_method` to `"client_secret_post"` before forwarding the request to Cloudflare. This ensures the client receives a `client_secret` in the registration response, which is required for subsequent MCP token exchanges despite the client initially requesting public client status.

### Why does the OpenSEO provider strip the Content-Length header during normalization?

The shim reconstructs the request body using `JSON.stringify(metadata)`, which may produce a different byte length than the original payload due to formatting changes or added fields. Removing the `Content-Length` header prevents HTTP request mismatch errors and allows Cloudflare to calculate the correct content length for the modified body.

### Does OpenSEO store client secrets or tokens locally?

No. According to the source code in [`src/server/mcp/oauth-provider.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/oauth-provider.ts), OpenSEO delegates all client creation, secret hashing, and token storage to Cloudflare's `workers-oauth-provider` library. The OpenSEO code only modifies the incoming registration metadata; all cryptographic operations and persistence remain within Cloudflare's secure infrastructure.

### What is the maximum payload size for dynamic client registration requests?

The normalization shim in [`src/server/mcp/oauth-registration.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/oauth-registration.ts) allows request bodies up to 1 MiB (1024 × 1024 bytes). Requests exceeding this size are passed through unchanged to Cloudflare, which handles the oversized body error response according to its own validation logic.