# Cloudflare Zero Trust Is Hijacking Your Royal MCP OAuth Discovery

> Royal MCP OAuth passes consent but Claude says 'Couldn't reach the MCP server': Cloudflare Zero Trust Access is hijacking /.well-known/ discovery. Remove it.

- Canonical: <https://royalplugins.com/support/royal-mcp/cloudflare-zero-trust-shadows-mcp-discovery/>
- HTML version: <https://royalplugins.com/support/royal-mcp/cloudflare-zero-trust-shadows-mcp-discovery/>

---

If your Royal MCP OAuth flow completes the consent screen successfully but then Claude returns “Couldn’t reach the MCP server” with an `ofid_*` reference code, Cloudflare Zero Trust Access is most likely intercepting your `/.well-known/oauth-authorization-server` endpoint and serving its own OIDC discovery metadata instead of Royal MCP’s. This page covers how to confirm the hijack, why it happens (often by accident), and how to remove it.

### The fix is removal, not configuration.

Cloudflare Zero Trust Access and Royal MCP are two independent OAuth providers. They cannot coexist for the same domain because they both claim ownership of `/.well-known/oauth-authorization-server`. The fix below removes the Cloudflare side — you cannot configure your way out of this.

## Is this your issue? (60-second symptom check)

This pattern fits a very specific symptom signature. All of the following should be true:

- You have **Cloudflare in front of your site** (DNS proxied through CF, or your site uses CF for any service)
- **Royal MCP plugin is installed**, enabled, and visible in WP Admin
- OAuth flow appears to **complete in the browser** — the consent screen renders, you click “Authorize”, and the browser tab redirects normally
- Claude Desktop or claude.ai then shows **“Couldn’t reach the MCP server”** + an `ofid_*` code (e.g. `ofid_93c524b3e137b8fd`)
- Royal MCP’s Activity Log shows an `oauth:authorize → ERROR` row with `invalid_client: Unknown client_id at /authorize` — OR it shows no recent activity at all

If you’ve been told to set up “Cloudflare MCP Portals”...

The Cloudflare AI assistant (the chat product in the CF dashboard) actively suggests “set up MCP Portals in Zero Trust → AI Controls” when customers ask for help with MCP connection failures. If you (or anyone on your team, or someone in IT) followed that suggestion during recent debugging, **you almost certainly have this issue**. Cloudflare’s “MCP Portal” is CF’s own competing product to Royal MCP — setting it up against your domain creates exactly the discovery hijack this page describes.

## Confirm the hijack with one curl command

From your local machine (not your WordPress server), run:

**Mac Terminal, Linux, Windows Command Prompt, Git Bash:**

```
curl -sS https://example.com/.well-known/oauth-authorization-server
```

**Windows PowerShell:** (PowerShell aliases `curl` to `Invoke-WebRequest` with different syntax — use `curl.exe` to bypass the alias)

```
curl.exe -sS https://example.com/.well-known/oauth-authorization-server
```

### Healthy Royal MCP response (what you SHOULD see)

```
{
  "issuer": "https://example.com",
  "authorization_endpoint": "https://example.com/authorize",
  "token_endpoint": "https://example.com/token",
  "registration_endpoint": "https://example.com/register",
  "scopes_supported": ["mcp:full"],
  "code_challenge_methods_supported": ["S256"]
}
```

### Hijacked response (what indicates this issue)

```
{
  "issuer": "https://auth.example.com",
  "authorization_endpoint": "https://auth.example.com/oauth/authorize",
  "token_endpoint": "https://auth.example.com/oauth/token",
  "registration_endpoint": "https://auth.example.com/oauth/register",
  "scopes_supported": ["openid","profile","email","offline_access"],
  "token_endpoint_auth_methods_supported": ["client_secret_basic","client_secret_post"]
}
```

### Four tells that confirm hijacking

Any single one of these confirms it. You don’t need all four.

| Field | Royal MCP value | Hijack signature |
| --- | --- | --- |
| `issuer` | Your site root (`https://your-site.com`) | An `auth.` subdomain (`https://auth.your-site.com`) |
| Endpoint paths | At site root (`/authorize`, `/token`, `/register`) | Under `/oauth/` prefix (`/oauth/authorize`, `/oauth/token`) |
| `scopes_supported` | `["mcp:full"]` | OIDC scopes: `openid`, `profile`, `email`, `offline_access` |
| `token_endpoint_auth_methods_supported` | `["none", "client_secret_post"]` | Includes `client_secret_basic` |

If your response matches the hijack signature, continue to the fix below. If your discovery returns Royal MCP’s correct shape, this isn’t your issue — see the [troubleshooting start-here page](https://royalplugins.com/support/royal-mcp/troubleshooting-start-here/) for other diagnostic paths.

## Why this breaks your OAuth flow

Cloudflare Zero Trust Access and Royal MCP are two independent OAuth providers. When Cloudflare Zero Trust is configured against your domain, it serves its own OIDC discovery metadata at `/.well-known/oauth-authorization-server` instead of letting requests pass through to Royal MCP. Here’s what happens during the OAuth flow:

1. Claude fetches `/.well-known/oauth-authorization-server` — gets Cloudflare’s OIDC metadata instead of Royal MCP’s
2. Claude POSTs to the endpoint Cloudflare advertised as `registration_endpoint` — **Cloudflare** issues a CF-format client\_id (often prefixed `wp_*`, not Royal MCP’s `rmcp_*`)
3. Claude redirects the user to Cloudflare’s authorize endpoint — user sees **Cloudflare Access’s OIDC consent screen**, not Royal MCP’s. The page looks fine to the user — they don’t know it’s the wrong provider
4. User clicks Authorize. Cloudflare Access issues an authorization code
5. Claude POSTs to Cloudflare’s token endpoint with the code. Cloudflare returns an access token
6. Claude tries to use that token against `https://your-site.com/wp-json/royal-mcp/v1/mcp`. **Royal MCP rejects the token because Cloudflare issued it, not Royal MCP.** The tokens are not interchangeable
7. Claude surfaces the rejection as “Couldn’t reach the MCP server” + `ofid_*`

The fundamental problem: Cloudflare Access and Royal MCP both issue OAuth tokens, but they’re completely different token formats backed by different validation logic. Tokens from one are not valid at the other. They can’t both serve OAuth for the same domain.

## The Fix: 5 Steps (15 minutes)

Removal is required — configuration won’t resolve this. The fix below walks through clicking each piece out of your Cloudflare account.

#### Open Cloudflare Dashboard → Zero Trust

Log into [dash.cloudflare.com](https://dash.cloudflare.com), select the account with the affected domain, then click **Zero Trust** in the left sidebar. This opens the Zero Trust dashboard at `one.dash.cloudflare.com`.

#### Delete the MCP Portal (if present)

In Zero Trust, navigate to **Access → AI Controls → MCP Portals**. If anything is listed here for your domain, delete it. This is the most common form the hijack takes — Cloudflare’s AI assistant suggests setting this up to customers debugging MCP, and customers often agree without realizing it conflicts with Royal MCP.

#### Delete the Access Application (if present)

In Zero Trust, navigate to **Access → Applications**. Look for any application that covers `your-domain.com`, `auth.your-domain.com`, or related paths. Delete it. (If you have legitimate Access applications for other parts of your infrastructure, leave those alone — only delete the one covering the OAuth discovery path or auth subdomain.)

#### Delete the `auth.*` CNAME record

In the main Cloudflare Dashboard (not Zero Trust), navigate to **DNS → Records**. Look for a CNAME record for `auth.your-domain.com`. Delete it. Zero Trust creates this CNAME automatically when an Access app is set up; removing the Access app does NOT automatically remove the DNS record.

#### Wait ~2 minutes, then verify

Wait for Cloudflare’s edge to drop the cached hijack response. Then re-run the curl probe from earlier:

```
curl -sS https://example.com/.well-known/oauth-authorization-server
```

The response should now match Royal MCP’s healthy shape — `issuer` at site root, endpoints at site root, `scopes_supported: ["mcp:full"]`. If it still returns the hijacked shape, see “If the hijack persists” below.

## Reconnect Claude with fresh OAuth state

Once curl confirms healthy discovery, Claude still has stale credentials cached from the hijacked attempts. Wipe them and reconnect fresh:

1. In Claude (web or Desktop), go to **Settings → Connectors** and **delete the existing Royal MCP connector entirely**
2. In WordPress, go to **Royal MCP → Settings** and click **Reset OAuth State** (button available in Royal MCP 1.4.17+)
3. Wait **30 seconds**. Claude.ai caches its own discovery state briefly
4. Re-add the connector in Claude using **only the server URL** (e.g. `https://your-site.com/wp-json/royal-mcp/v1/mcp`). **Leave Advanced Settings completely empty** — do NOT paste any client\_id, client\_secret, or redirect\_uri values you may have copied during earlier debugging
5. Complete the OAuth flow when prompted

Important: leave Advanced Settings empty

Customers who’ve been debugging MCP for days sometimes paste manually-copied values (`wp_*` client IDs, custom redirect URIs, etc.) into Claude’s Advanced Settings during earlier troubleshooting attempts. These stale values cause `invalid_client` errors on the next OAuth attempt even after the CF hijack is removed. Leaving Advanced Settings empty forces Claude to use Dynamic Client Registration, which generates fresh valid Royal MCP credentials.

## Want Cloudflare’s own AI to do the cleanup?

If you’d rather not click through the dashboard yourself, Cloudflare’s dashboard chat (the AI assistant) can audit your config and execute the removal. **This prompt has been tested as effective** — we used it on a customer’s account to find and remove the hijacking Worker, its routes, and verify no other interception layers existed:

```
Audit my Cloudflare configuration and REMOVE any Workers, Routes, or
Rules that intercept or modify the path:

    /.well-known/oauth-authorization-server

Specifically:
- Delete any Cloudflare Workers created recently that return OAuth
  discovery metadata
- Remove any Worker Routes applied to <DOMAIN>/* or /.well-known/*
- Disable any Zero Trust Access or authentication policies that
  inject OAuth responses at this path
- Ensure no Cloudflare layer is responding with:
  - issuer: auth.<DOMAIN>
  - authorization_endpoint or token_endpoint pointing to auth.<DOMAIN>

Goal: Return this path to origin control only, with zero Cloudflare
interception.

DO NOT create any new Workers, Routes, Rules, or Access policies. The
fix is removal only. If you cannot resolve this through removal alone,
stop and report what is preventing complete removal — do not work
around the issue by adding new configuration.

After removal, confirm by running this curl from a separate machine
(NOT through the Cloudflare dashboard):

    curl https://<DOMAIN>/.well-known/oauth-authorization-server

Expected: JSON with "issuer": "https://<DOMAIN>" (NOT auth.<DOMAIN>)
and "scopes_supported" including "mcp:full".
```

Replace `<DOMAIN>` with your actual domain everywhere it appears in the prompt. Paste the whole thing into the Cloudflare dashboard’s AI chat. The DO-NOT-create-new-configuration language is important — without it, the CF AI sometimes “helpfully” offers to recreate the MCP Portal in a different shape, which doesn’t solve the problem.

After the CF AI reports completion, still run the verification curl from your own machine. The CF AI’s verification (from inside the dashboard) sometimes sees a different response than what external clients see.

## If the hijack persists after the fix

Rare, but possible. Cloudflare offers several mechanisms that can intercept HTTP responses, and Zero Trust Access is only the most common one. If curl still shows the hijacked response after deleting the Access app and CNAME, check these additional locations in order:

#### Workers & Pages → Workers

A Cloudflare Worker can return OAuth discovery metadata for any path it’s bound to. This is sometimes the actual hijack location — the Zero Trust Access app may have created (or been replaced by) a Worker that handles the discovery response.

##### How to check

Cloudflare Dashboard → **Workers & Pages → Workers**. Look for any Worker created recently (often named `oauth-discovery`, `auth-server`, `mcp-auth`, or similar). Check each Worker’s Routes tab for routes matching `your-domain.com/.well-known/*` or `/.well-known/oauth-authorization-server`. **Delete the entire Worker** — that removes all its routes atomically.

#### Rules → Transform Rules

A Transform Rule can rewrite the response body or modify the request before it reaches origin. If someone configured a rule to rewrite `/.well-known/oauth-*`, that rule overrides whatever WordPress serves.

##### How to check

Cloudflare Dashboard → **Rules → Transform Rules**. Look for any rule whose match condition involves `.well-known`, `oauth`, or your auth subdomain. Disable or delete it.

#### Rules → Redirect Rules

A Redirect Rule can 3xx-route the path to a different URL (e.g. to `auth.your-domain.com` via 301), which then serves the OIDC metadata from there.

##### How to check

Cloudflare Dashboard → **Rules → Redirect Rules**. Look for any rule whose source URL involves `/.well-known/oauth-*` or whose target involves an `auth.` subdomain. Disable or delete it.

#### DNS → `auth.*` records (verify deletion)

If you deleted the CNAME in step 4 above but curl still hijacks, the DNS deletion may not have propagated or there may be a second record (CNAME flattening, AAAA record, etc.).

##### How to check

Re-verify **DNS → Records** in the Cloudflare Dashboard. Search for “auth” in the records list — nothing should appear. Also check **Spectrum** applications if you use Cloudflare Spectrum — OAuth subdomains can be routed at the transport layer there.

If after checking all four you still see the hijacked response, post the full curl output (including all response headers, captured with `curl -i`) in the [Royal MCP wp.org support forum](https://wordpress.org/support/plugin/royal-mcp/) or open a paid [Premium Support](https://royalplugins.com/premium-support/) ticket.

## How this happens (it’s usually accidental)

Most customers who hit this issue didn’t intentionally set up an OAuth server competing with Royal MCP. Three common paths lead here:

### Path 1: Cloudflare’s AI assistant suggested it

The CF dashboard chat product actively pushes “set up MCP Portals in Zero Trust → AI Controls” as a solution when customers ask about MCP connection failures. The CF AI has no idea what plugin the customer is using — it suggests Cloudflare’s own competing product regardless. Customers who follow this suggestion (often during a frustrating debugging session) inadvertently create the hijack.

### Path 2: An IT team member set it up

If your organization has shared Cloudflare access, someone on the IT or security team may have set up Zero Trust Access for legitimate reasons (employee access controls, internal-only sections of the site, etc.). The configuration affects MCP discovery as a side effect, even though that wasn’t the goal.

### Path 3: Migrating from another MCP product

Customers moving from a different MCP solution that uses Cloudflare Workers (Cloudflare’s own MCP Portal product, or similar third-party setups) sometimes leave the previous configuration in place when installing Royal MCP. The old Worker/Access setup keeps serving discovery, blocking Royal MCP from responding.

Can I use Cloudflare Zero Trust for OTHER purposes on the same domain?

Yes. Zero Trust Access is a legitimate, useful product — it’s only the OAuth-discovery interception piece that conflicts with Royal MCP. If you have CF Access apps protecting `/wp-admin/`, `/admin/`, or other paths, those are fine to keep. The issue is specifically when CF Access is configured to cover (or to issue OAuth metadata for) the root domain or `/.well-known/oauth-authorization-server`. After the fix, you can keep CF Access protecting other paths.

## Still Stuck? Two Support Paths

If you’ve worked through the steps above and your connection still fails:

### Community Support (free) — wp.org Plugin Forum

Post a new thread at [wordpress.org/support/plugin/royal-mcp/](https://wordpress.org/support/plugin/royal-mcp/). The Royal Plugins team monitors the forum regularly and community members often help disambiguate issues faster than email could. Include the diagnostic info listed below in your post.

### Premium Support (paid) — direct one-on-one help

For priority response (24-hour SLA) and hands-on diagnostic help, our [Premium Support tier](https://royalplugins.com/premium-support/) is $149/year. Includes a 30-day “if it breaks again” follow-up window on every resolved ticket.

#### Information to include in your post or ticket

- **Your hosting provider** and whether Cloudflare or another CDN is in front of the site
- **Royal MCP version** from WP Admin → Plugins (1.4.22 or newer recommended for the auto-diagnostic notices)
- **Output of the curl probe(s) from this page** — full response including headers (use `curl -i`)
- **Which Claude client** you’re using (Claude Desktop, claude.ai web custom connector, Claude Code CLI, ChatGPT MCP, etc.)
- **The exact error message** and any `ofid_*` reference code shown by the client
- **Screenshot of the most recent `oauth:` row in Royal MCP → Activity Logs** (View Details expanded) — or confirmation the log is empty after a reproduced failure

## Related host-side OAuth failures

If the curl probe shows your discovery response is healthy (Royal MCP shape, correct issuer), this isn’t your issue. Try these adjacent failure modes:

[Cloudflare blocking AI bots / OAuth

Discovery shape is fine but Claude still can’t reach the OAuth endpoints. Symptom: 403 + `cf-ray` on `python-httpx` User-Agent, 200 on browser UA.](https://royalplugins.com/blog/cloudflare-block-ai-bots-breaks-mcp/)
[cPanel ModSecurity returns 406

Host-side WAF blocking before requests reach WordPress. Symptom: 406 + literal “Mod Security” phrase in response body.](https://royalplugins.com/support/royal-mcp/modsecurity-406-bot-fingerprint/)
[Apache + Imunify360 blocking python-httpx

Apache shared host returning 429 specifically for python-httpx User-Agent. Same UA-discrimination as CF Bot Fight but on Apache stacks.](https://royalplugins.com/support/royal-mcp/apache-python-httpx-429-token-exchange/)
[Discovery returns HTML instead of JSON

A membership plugin, security plugin, or theme is intercepting `/.well-known/`. Different from CF Zero Trust because the response is HTML, not JSON-shaped OIDC metadata.](https://royalplugins.com/support/royal-mcp/well-known-served-as-html/)
[Full curl diagnostic walkthrough

The complete 4-probe sequence with dual-UA testing, response header reading, and a paste-ready bash + PowerShell diagnostic script.](https://royalplugins.com/support/royal-mcp/diagnose-mcp-with-curl/)

[Back to Troubleshooting Start Here](https://royalplugins.com/support/royal-mcp/troubleshooting-start-here/)
[Royal MCP Support Hub](https://royalplugins.com/support/royal-mcp/)
