WordPress Plugins
Free Tools
Pricing Blog Case Studies Switch to Royal Plugin Graveyard Support My Account Cart
Support / Royal MCP / Apache 429 / python-httpx Block Fix

Fix: mcp_token_exchange_failed on Apache Shared Hosts

If your Apache shared host returns HTTP 429 Too Many Requests specifically for the python-httpx User-Agent — while browser User-Agents pass through fine — you’ve hit a UA-targeted block from Imunify360, mod_security with UA-fingerprint rules, BitNinja, or a similar managed security platform. The block kills Claude’s OAuth /token exchange because Anthropic’s OAuth backend uses python-httpx. This page covers how to confirm it, why it happens, and how to get your host to fix it.

You cannot fix this from inside WordPress.

This block lives at your hosting provider’s edge security stack, before requests reach WordPress. No plugin setting, theme change, or cache clear will help. The fix is a configuration change at your host — you’ll need to open a hosting support ticket. The text you’ll need is in the “Paste-Ready Hosting Ticket” section below.

Is this your issue?

This pattern fits a specific signature. The more of these that match, the more likely it’s the right diagnosis:

Different from Cloudflare-based blocks

If you have Cloudflare in front of your site (curl responses show server: cloudflare + a cf-ray header), the right doc is Cloudflare blocking Claude MCP OAuth instead. CF’s block returns 403 with branded HTML; the Apache shared-host block returns 429 with a plain “Too Many Requests” body. Same general failure family, different vendor & fix path.

Confirm the block with a dual-UA curl probe

The diagnostic test: hit your OAuth discovery endpoint twice from your local machine — once with a python-httpx User-Agent (which Anthropic’s backend uses), once with a browser User-Agent. If python-httpx returns 429 while browser returns 200, this gotcha applies.

Test with python-httpx UA

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

curl -sS -i -A "python-httpx/0.27.0" https://example.com/.well-known/oauth-authorization-server | head -10

Windows PowerShell:

curl.exe -sS -i -A "python-httpx/0.27.0" https://example.com/.well-known/oauth-authorization-server | Select-Object -First 10

Test with browser UA

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

curl -sS -i -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0" https://example.com/.well-known/oauth-authorization-server | head -10

Windows PowerShell:

curl.exe -sS -i -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0" https://example.com/.well-known/oauth-authorization-server | Select-Object -First 10

Interpret the results

python-httpx browser UA Diagnosis
429 Too Many Requests 200 OK This gotcha applies. UA-targeted block from Apache managed security. Continue to the persistence check below.
200 OK 200 OK This block isn’t affecting you (at least not right now). If Claude still fails, check Step 0 of the troubleshooting checklist for other patterns.
403 Forbidden + cf-ray Either This is a Cloudflare block, not an Apache host block. See Cloudflare blocking Claude MCP OAuth instead.
406 Not Acceptable + “Mod Security” 406 Not Acceptable + “Mod Security” cPanel ModSecurity blocking all non-trusted UAs. See ModSecurity 406 fix instead.

The 429 response body looks like this

Plain text body, ISO-8859-1 encoding, no JSON. The minimal “Too Many Requests” response is the signature of Apache’s default 429 handler when the managed security layer issues the block.

HTTP/1.1 429 Too Many Requests
Server: Apache
Content-Type: text/html; charset=iso-8859-1

Too Many Requests

Persistence check — rules out burst rate limiting

One 429 might just mean “you sent too many requests too fast.” A persistent 429 specifically on python-httpx, while browser UA returns 200, is a different thing entirely — it’s a UA-targeted rule that fires regardless of request rate.

To confirm it’s UA-targeted (not rate-based):

  1. Run the python-httpx probe above. Note the result (likely 429)
  2. Wait 60+ seconds (give any burst rate limiter time to reset)
  3. Run the python-httpx probe again
  4. If it’s still 429 on the second attempt while the browser UA still returns 200 on a fresh run, the block is UA-targeted and persistent
Why this distinction matters for the hosting ticket

If you tell your host “I’m being rate limited,” they may just raise your request quota, which won’t help (the block isn’t rate-based). If you tell them “a UA-fingerprint rule is blocking python-httpx specifically,” that points them to the actual mechanism (Imunify360 / mod_security UA rule / BitNinja) and the correct fix (whitelist or path exclusion). The paste-ready ticket text below uses the right framing.

Why this breaks the OAuth /token exchange

Royal MCP’s OAuth flow uses different participants at different stages. Specifically, Anthropic’s OAuth backend uses the python-httpx HTTP client for the server-to-server /register and /token POSTs. When the host blocks that User-Agent, the OAuth flow dies at the final step:

  1. Claude initiates OAuth. The user’s browser handles the redirect to /authorize — this is a browser request with a browser UA, so it passes through the host’s filter without issue
  2. The user sees Royal MCP’s consent screen, clicks Authorize. Browser stuff still works. The browser redirects back to Claude with an authorization code
  3. Now Claude’s backend takes over. It POSTs to /token using python-httpx to exchange the authorization code for an access token
  4. Your Apache host’s managed security stack sees the python-httpx UA and returns 429 Too Many Requests — the request never reaches WordPress, never reaches Royal MCP’s /token handler
  5. Claude receives 429, surfaces it to you as mcp_token_exchange_failed or “Authorization with the MCP server failed”

From your perspective, the OAuth flow looked like it was working — the consent screen rendered correctly, you clicked Authorize, the page redirected — right up until the very last step where Claude needed to actually obtain the token. That mismatch (browser stuff works, backend stuff fails) is the diagnostic fingerprint of UA-targeted blocking.

Which security stack is your host running?

You don’t need to know this to open the hosting ticket — the ticket text below works regardless. But if you want to give the host more context, here’s how the three most common Apache-shared-host security stacks identify themselves:

Imunify360

Default 429 response is plain text “Too Many Requests” with Content-Type: text/html; charset=iso-8859-1 and no specific Imunify branding. Most common stack on Apache shared hosting in Europe, Africa, and Asia-Pacific regions. The fix on the host side is adding a Network Rule or Firewall Exclusion scoped to your domain + the python-httpx UA pattern.

mod_security (with custom UA rules)

Default 429 may include a brief reference number (e.g. Reference #18.abcd...) but often is identical to Apache’s default. Sometimes returns 403 instead of 429 depending on rule configuration. The fix on the host side is adding a SecRule with allow,nolog action scoped to your vhost + the User-Agent pattern.

BitNinja / other managed bot defense

Default response varies by vendor. May include vendor-specific HTML if the customer ever installed the vendor’s “branded” error page templates. Usually identifiable by your hosting brand — some smaller managed-WordPress hosts in Europe and Asia bundle BitNinja, Patchstack, or similar managed defense products. The fix on the host side is vendor-specific allow rules.

Don’t spend time trying to identify the exact stack yourself. The ticket text below describes the symptom in vendor-neutral language, and your host’s support team will know which specific tool they use. They’ll apply the right kind of allow rule.

Paste-ready hosting support ticket

Copy this and submit it to your hosting provider’s support team. Replace example.com with your actual domain.

Subject: User-Agent "python-httpx" receiving 429 Too Many Requests on
my domain — need allowlist or path exclusion

Hi support,

My WordPress site at example.com runs a plugin (Royal MCP) that needs
to accept OAuth requests from Anthropic's backend service. Anthropic's
OAuth client identifies itself with User-Agent "python-httpx".

Your managed security stack is currently returning 429 Too Many Requests
for any request with that User-Agent, even single, non-bursty requests
made 60+ seconds apart. Browser User-Agents pass through normally.

This is reproducible from any external IP:

    curl -A "python-httpx/0.27.0" \
        https://example.com/.well-known/oauth-authorization-server

returns 429 Too Many Requests, while

    curl -A "Mozilla/5.0" \
        https://example.com/.well-known/oauth-authorization-server

returns 200 OK with the expected JSON.

The 429 persists across multiple requests minutes apart, so this is
NOT a burst rate limit — it's a User-Agent-targeted block (likely
an Imunify360 rule, a mod_security UA rule, or a similar managed
bot-defense rule).

Could you please either:

1. Whitelist User-Agent matching python-httpx/* on this domain, OR

2. Exclude these specific paths from User-Agent-based filtering:
   - /register
   - /authorize
   - /token
   - /.well-known/oauth-authorization-server
   - /.well-known/oauth-protected-resource
   - /wp-json/royal-mcp/v1/mcp

Option 1 is preferable as it allows the plugin's authentication to
work site-wide. Option 2 is an acceptable fallback if a domain-wide
UA allowlist isn't available.

After you apply the change, I'll re-run the curl test to confirm
the block is cleared. Happy to provide any additional logs or
diagnostic info you need.

Thanks!
A note about how to phrase this for the host

This text is intentionally vendor-neutral (it doesn’t name “Imunify360” or “mod_security” in the subject line) because some host support staff get defensive if you tell them which tool you think the problem is. The body mentions the likely tools in passing, which gives them context without sounding accusatory. Most host support teams will quickly identify the right rule once they have the curl repro.

Verify the fix worked

After your host confirms the change has been applied, re-run the dual-UA probe:

curl -sS -i -A "python-httpx/0.27.0" https://example.com/.well-known/oauth-authorization-server | head -5

Expected output:

HTTP/1.1 200 OK
Server: Apache
Content-Type: application/json; charset=utf-8

If you now see 200 OK on both browser UA and python-httpx UA, the block is cleared. Continue to the reconnection step below.

If the python-httpx probe still returns 429:

Reconnect Claude with fresh OAuth state

Once curl confirms python-httpx now returns 200, Claude still has stale connection state from the failed attempts. Wipe and reconnect:

  1. In Claude (web or Desktop), 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 for any caching layers to settle
  4. Re-add the connector in Claude with only the server URL (e.g. https://example.com/wp-json/royal-mcp/v1/mcp). Leave Advanced Settings completely empty
  5. Complete the OAuth flow when prompted — should succeed this time

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

This is part of a family of UA-targeted blocks

The Apache 429 block is one of three closely related patterns. All three break Royal MCP OAuth in the same way (UA-discrimination at the edge, OAuth dies at /token) but on different host families with different status codes. Worth knowing in case you migrate hosts and hit a sibling pattern later, or in case your initial diagnosis is wrong:

Host family Edge layer Status code Body signature Fix doc
Cloudflare in front CF Bot Fight Mode / AI Bots 403 CF-branded HTML, cf-ray header CF AI bots fix
cPanel shared hosting ModSecurity bot-fingerprint 406 “Generally a 406 error is caused because a request has been blocked by Mod Security” ModSec 406 fix
Apache + managed security Imunify360 / mod_security UA rule / BitNinja 429 Plain text “Too Many Requests”, no vendor branding (this page)

All three resolve the same way: open a hosting support ticket asking for a UA whitelist or path-based exclusion for the OAuth endpoints. Only the status code, host family, and which support team you contact differ.