Cloudflare Zero Trust Is Hijacking Your Royal MCP OAuth 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.
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 → ERRORrow withinvalid_client: Unknown client_id at /authorize— OR it shows no recent activity at all
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 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:
- Claude fetches
/.well-known/oauth-authorization-server— gets Cloudflare’s OIDC metadata instead of Royal MCP’s - Claude POSTs to the endpoint Cloudflare advertised as
registration_endpoint— Cloudflare issues a CF-format client_id (often prefixedwp_*, not Royal MCP’srmcp_*) - 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
- User clicks Authorize. Cloudflare Access issues an authorization code
- Claude POSTs to Cloudflare’s token endpoint with the code. Cloudflare returns an access token
- 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 - 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, 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:
- In Claude (web or Desktop), go to Settings → Connectors and delete the existing Royal MCP connector entirely
- In WordPress, go to Royal MCP → Settings and click Reset OAuth State (button available in Royal MCP 1.4.17+)
- Wait 30 seconds. Claude.ai caches its own discovery state briefly
- 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 - Complete the OAuth flow when prompted
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 or open a paid 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.
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/. 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 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