WordPress Plugins
Free Tools
Pricing Blog Case Studies Switch to Royal Plugin Graveyard Support My Account Cart
Support / Royal MCP / ModSecurity Returns 406

ModSecurity Returns 406 — Bot Fingerprinting Blocks MCP Connections

If your Royal MCP Activity Log stays empty no matter what you do on the WordPress side — even after the 4-step basic checklist, even with Wordfence disabled and caching off — and a quick curl from outside returns 406 Not Acceptable with a literal “Mod Security” message in the response body, your shared host’s ModSecurity rules are fingerprinting non-browser HTTP clients and blocking Anthropic’s MCP backend at the edge before it ever reaches your WordPress install. This is common on cPanel-based shared hosting (InMotion, A2 Hosting, Bluehost, HostGator) running Comodo CWAF or OWASP CRS rulesets. The fix is a path-based ModSecurity exclusion that you (or your host) can apply in cPanel.

Before applying these advanced steps

Royal MCP connection issues that look host-specific are usually a plugin conflict, cache layer, or stale OAuth state — about 90% resolve in our 4-step basic checklist. If you haven’t run through that yet, do it first — this article assumes the basics are already ruled out.

Most Common Hosts Affected

cPanel-based shared hosting running ModSecurity with Comodo CWAF or OWASP Core Rule Set defaults — primarily InMotion Hosting, A2 Hosting, Bluehost, HostGator, GreenGeeks, Namecheap shared, and other resellers of the same stack. Most plans serve the WAF response through an nginx reverse proxy in front of Apache + PHP-FPM, which is why the error body is HTML even when your normal site returns JSON. Premium managed WP hosts with dedicated MCP-aware WAF policies (WP Engine, Kinsta, Pressable) are typically not affected.

Before You Apply the Fix — Confirm This Is Your Issue

An empty Activity Log has more than one possible cause — edge cache poisoning, host-side /.well-known/ blocks, and ModSecurity bot fingerprinting all produce the same surface symptom. The 60-second curl test below confirms which one you’re dealing with. If the picture doesn’t match what’s in this article, the basic checklist routes you to the right fix.

This article applies to you if…

  • You’ve already run the 4-step basic checklist (update, conflict test, OAuth state wipe, Activity Log check) without resolving it
  • Your hosting provider is one of: InMotion, A2 Hosting, Bluehost, HostGator, GreenGeeks, Namecheap shared, or any cPanel-based shared / reseller host running ModSecurity
  • Your Royal MCP → Activity Log is empty even after multiple reproduced connection attempts
  • You’ve confirmed it’s not Wordfence (deactivated), not a caching plugin (deactivated), and not Cloudflare AI bot rules (your domain isn’t even on Cloudflare, or you’ve confirmed those settings are off)
  • Running curl from outside your site against any URL on the domain returns 406 Not Acceptable with the literal phrase “Mod Security” in the response body (the diagnostic in the next section)

Have these on hand before you start

You’ll need these to apply the fix below, and they’re what our support team needs if you end up emailing afterward:

  • Your exact hosting provider (specific ModSec control UI varies)
  • cPanel access — or willingness to open a hosting support ticket if cPanel doesn’t expose ModSecurity to you directly
  • Your Royal MCP version from WP Admin → Plugins (should be 1.4.16 or newer for the diagnostic-quality Activity Log entries to be useful afterward)
  • Output of the curl test below — full headers and body, you’ll be sending this verbatim to your host’s support team

Symptoms

You probably have this issue if all of the following are true:

The Telltale Signs

  • Connection from Claude Desktop, Claude Code, claude.ai web, or ChatGPT fails with “Couldn’t reach the MCP server” or similar — sometimes with an ofid_xxxxx reference code
  • Your Royal MCP → Activity Log is empty after every reproduced attempt — not one row, even from clients that should reach PHP
  • You’ve already deactivated Wordfence (or any other security plugin), switched to a default theme, set caching to off, and reset OAuth state from Royal MCP → Settings
  • Visiting the site in a browser works perfectly — homepage loads, wp-admin loads, no errors
  • You’re on a cPanel-based shared / reseller host that runs ModSecurity (any of the providers listed in the alert banner above)
An empty Activity Log on a known-working browser session means edge-layer block

When the browser can reach your site, but every MCP client’s connection attempt produces zero Activity Log entries, the request is being intercepted by something before WordPress’s PHP code runs. That’s the edge layer — ModSecurity, a host-level WAF, or an nginx-side block. The curl test in the next section tells you specifically whether it’s ModSecurity returning 406, which is the case this article fixes.

How to Verify It’s This Specific Issue

One 60-second curl test confirms the diagnosis. Run it from any terminal — macOS, Linux, Windows PowerShell, WSL all work. Don’t run it from inside your hosting account’s SSH; the test is checking what an outside HTTP client sees, so it has to come from outside your host.

Run curl with default headers against any URL on your site

The homepage works fine for this; you don’t need to hit a Royal MCP URL. The point is to see what ModSecurity does to a non-browser HTTP client:

curl -v https://example.com/

If you see HTTP/1.1 406 Not Acceptable in the response headers, AND the response body contains the literal phrase “Generally a 406 error is caused because a request has been blocked by Mod Security”, the diagnosis is confirmed — your host’s ModSecurity is blocking minimal-header HTTP clients. The exact error body looks like:

<html><head><title>Error 406 - Not Acceptable</title><head>
<body><h1>Error 406 - Not Acceptable</h1>
<p>Generally a 406 error is caused because a request has been blocked by Mod Security.
If you believe that your request has been blocked by mistake please contact the
web site owner.</p></body></html>

Confirm with a browser-headers curl that ModSec lets through

This second test rules out the alternative (general site outage, DNS issue, etc.) by sending Mozilla-style headers that look browser-like enough to pass ModSec’s fingerprint check:

curl -v https://example.com/wp-json/royal-mcp/v1/mcp \
  -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" \
  -H "Accept: application/json, text/plain, */*" \
  -H "Accept-Language: en-US,en;q=0.9"

If this returns 401 Unauthorized with a JSON-RPC body (instead of the 406 HTML page), the diagnosis is confirmed end-to-end. Royal MCP itself is healthy — ModSecurity is just blocking everything that doesn’t look like a browser. Anthropic’s MCP backend (Node.js with a minimal fetch / axios client) is exactly what ModSec considers “bot-like.”

Why your Activity Log is empty even though the plugin is fine

ModSecurity sits in nginx, which is in front of Apache + PHP-FPM. When ModSec returns a 406, the request never reaches PHP — Apache doesn’t see it, WordPress doesn’t see it, Royal MCP doesn’t see it. There’s nothing for the Activity Log to write down because no code ran. That’s why server-side debugging (deactivating plugins, switching themes, clearing caches) does nothing here — you’re changing what happens after a gate that’s already closed.

The Fix — Disable or Exclude ModSecurity for the OAuth + MCP Paths

The fix has two paths depending on whether cPanel exposes ModSecurity to you directly. Most InMotion and A2 plans do; most Bluehost plans don’t. Try Option A first; if you can’t find the control, jump to Option B (open a support ticket with your host).

Option A: Disable ModSecurity in cPanel yourself

Log into cPanel and find the ModSecurity tool

It’s usually under the Security section of cPanel. Look for any of: “ModSecurity”, “Mod Security”, “CWAF” (Comodo WAF), or “Web Application Firewall.” Some hosts label it differently — if you can’t find it, use cPanel’s top-bar search and type “ModSecurity.”

Turn ModSecurity off for your specific domain

The ModSecurity page lists each of your cPanel domains with an On / Off toggle. Find your WordPress domain and switch it to Off. Many hosts allow per-directory ModSec rules but not per-path exclusions through this UI — if you only see an On/Off toggle, the cleanest move is to turn ModSec off for the whole domain.

Should I leave the rest of the site without ModSec?

ModSecurity is one of several security layers (along with your WordPress security plugin, your strong passwords, your up-to-date plugins, etc.). Leaving it off is a tradeoff most MCP users accept because the default ModSec ruleset is heavy-handed and breaks legitimate API integrations, not just MCP. If you’d prefer to keep ModSec on for the rest of the site and only disable rules on the OAuth paths, jump to Option B below — that requires a host support ticket.

Test the connection from Claude immediately

Retry the MCP connection from whichever client (Claude Desktop, claude.ai web, Claude Code, ChatGPT). It should work. Re-run the curl test from the Verify section to confirm 200/401 responses instead of 406. Refresh your Royal MCP → Activity Log — you should now see new entries appearing as connections succeed.

Option B: Open a hosting support ticket for path-based exclusion

If your cPanel doesn’t expose ModSecurity, or you want to keep ModSec on for the rest of the site, ask your host’s support team to apply path-based rule exclusions. Send them this exact text:

ModSecurity on my domain is returning 406 Not Acceptable for HTTP clients
with non-browser User-Agents. I need ModSecurity disabled (or rules whitelisted)
for the following paths so my MCP integration can reach my WordPress install:

  /wp-json/royal-mcp/*
  /.well-known/oauth-authorization-server
  /.well-known/oauth-protected-resource
  /register
  /authorize
  /token

Please also check the ModSecurity audit log on this domain for any rule that
has been firing on these paths in the last 24 hours and let me know which
rule IDs triggered.

Most hosts respond within a few hours and apply the exclusion as a per-directory SecRuleRemoveById directive in your .htaccess file or in an httpd-includes config snippet. Some hosts will push back and ask you to whitelist specific User-Agents instead — that won’t work for MCP, because Anthropic’s backend doesn’t expose a stable User-Agent string we can give them. Stay firm on path-based exclusion.

Option C: Per-host quick notes

Specific to the hosts where we’ve confirmed this issue:

InMotion Hosting

cPanel exposes ModSecurity under Security → ModSecurity on most cPanel and Reseller plans. Toggle off per-domain. If the toggle isn’t available (some older shared plans), open a chat ticket from your AMP (Account Management Panel) and use the Option B text above — InMotion typically applies the exclusion within an hour.

A2 Hosting

cPanel exposes ModSecurity under Security → ModSecurity. A2 also offers a separate “Perpetual Security” rule set on managed WordPress plans that can also interfere — if disabling ModSecurity alone doesn’t resolve it, check that Perpetual Security isn’t separately blocking the paths.

Bluehost

Bluehost typically does not expose ModSecurity in cPanel. You’ll need to open a support chat or ticket and use the Option B text. Bluehost ModSec rules are managed centrally, so the support team applies the exclusion server-side rather than via your .htaccess.

HostGator / GreenGeeks / Namecheap shared

All three resell variants of the same cPanel + ModSecurity + Comodo CWAF stack. cPanel exposure varies by plan. Try Option A first; if no UI control, fall back to Option B with the canonical text above.

Why Does This Happen?

ModSecurity is a generic web-application firewall that ships with cPanel and is enabled by default on most shared hosting. The default rulesets (Comodo CWAF, OWASP Core Rule Set, Atomic AtomiCorp) include “bot-detection” rules that fingerprint requests based on their HTTP headers: User-Agent, Accept, Accept-Language, Accept-Encoding, plus newer Sec-Fetch-* headers that real browsers send and most non-browser HTTP clients don’t.

A request with default curl headers (or default Node.js fetch headers, or default Python requests headers) typically has User-Agent: curl/8.4.0 and Accept: */* — that’s it. No Accept-Language, no Sec-Fetch-Dest, nothing else. ModSec’s pattern-matching scoring system adds points for each missing or suspicious header, and once the score crosses a threshold the request is blocked. Different hosts return different status codes — some return 403, some return 503, but the cPanel default is 406 Not Acceptable with an HTML error body identifying ModSec as the source.

This is a generic security policy, not anything specific to Royal MCP. The exact same ModSec rules block GitHub Actions making API calls to your site, IFTTT webhooks, Make.com automations, monitoring services like UptimeRobot — anything where a server-to-server HTTP request doesn’t bother sending the full browser-header shape. Anthropic’s MCP backend uses a minimal Node.js HTTP client and falls into the same bucket.

Can we fix this from the plugin side instead?

No — Royal MCP can’t change what HTTP headers Anthropic’s backend sends. The plugin runs inside WordPress, after the request has already crossed nginx and Apache and reached PHP. ModSec sits in front of all of that. Disabling or excluding it has to happen at the hosting-config layer, which is why the fix above lives there. We do continuously file feedback with Anthropic to improve their MCP backend’s header behavior, which would let it pass more aggressive WAF rules — but the fix on your end is still going to be the same path-based ModSec exclusion in the short term.

Quick Alternative: Skip OAuth on Claude Desktop

If you’re only trying to connect Claude Desktop (not Claude.ai web or Claude Code), and you can’t get your host to fix ModSec on your timeline, Claude Desktop can connect to Royal MCP via a static API key header instead of OAuth. This does not work around the ModSec block by itself (the underlying request still has to go through ModSec), but if your Desktop client happens to send a slightly different header signature, the API-key path is worth trying as a fallback.

Full walkthrough at Connect Claude Desktop via API Key (Skip OAuth). If that also returns 406, you’re unambiguously dealing with header-level fingerprinting and have to fix ModSec at the host layer regardless of which client you use.

The Fix — Per-Host Cache Exclusions

Royal MCP 1.4.13 sends Cache-Control: no-store, no-cache, must-revalidate on every OAuth response, which is correct per RFC 7234. Some host edge caches still ignore those headers on URLs that match their cacheable-path regex, so we need to exclude the OAuth endpoints explicitly. Pick the section that matches your host.

Option A: SiteGround (SG Optimizer)

Open SG Optimizer’s Dynamic Cache settings

WP Admin → SG OptimizerCachingDynamic Cache. Scroll down to the Exclude URLs from Caching field.

Add the Royal MCP OAuth paths

Paste these patterns, one per line:

/wp-json/royal-mcp/*
/.well-known/oauth-authorization-server
/.well-known/oauth-protected-resource
/authorize
/token
/register

Click Save Changes. The last three paths (/authorize, /token, /register) are the actual OAuth handshake endpoints — missing them is the most common reason a cache-exclusion fix appears to do nothing.

Purge the cache and retry the connector

Click Purge SG Cache in the SG Optimizer toolbar (or under Caching → Dynamic Cache). Then retry the Claude.ai authorization. Re-run the curl headers test from the Verify section to confirm cache-hit headers are gone on the OAuth endpoints.

SiteGround also has server-side NGINX caching

If exclusions in SG Optimizer aren’t enough, also check Site Tools → Speed → Caching → Dynamic Cache at the hosting-panel level (separate from the WordPress plugin), and toggle Dynamic Cache off temporarily to confirm the diagnosis. You can re-enable it once exclusions are in place.

Still failing after cache exclusions and a cache purge?

SiteGround has two separate issues on top of caching that often need to be addressed together:

  • nginx blocks /.well-known/*. Their nginx layer reserves the path for ACME SSL renewals and returns a static 404 for everything else — before WordPress sees the request. Fix: drop two static OAuth metadata files in webroot. See SiteGround Returns 404 for /.well-known/.
  • nginx serves the static files as the wrong Content-Type. Strict MCP clients (Claude Desktop’s mcp-remote) reject metadata served as text/plain. Since SiteGround migrated to Google Cloud, they no longer make per-customer nginx changes, and .htaccess ForceType is silently ignored on these paths. Fix: a free Cloudflare Transform Rule rewrites the response Content-Type at the edge. See Fix /.well-known/ Content-Type with a Cloudflare Transform Rule.

Option B: LiteSpeed Cache plugin

Open the LiteSpeed Cache exclusions panel

WP Admin → LiteSpeed CacheCacheExcludes tab.

Add the OAuth paths to “Do Not Cache URIs”

Paste these into the Do Not Cache URIs field, one per line:

/wp-json/royal-mcp/
/.well-known/oauth-authorization-server
/.well-known/oauth-protected-resource
/authorize
/token
/register

Click Save Changes, then LiteSpeed Cache → Toolbox → Purge All.

Option C: Cloudflare (APO or aggressive page rules)

Add a Cache Rule that bypasses cache for the OAuth paths

In the Cloudflare dashboard for your domain: CachingCache RulesCreate rule. Set the rule to match URI Path containing any of the OAuth paths and set Cache eligibility to Bypass cache.

(starts_with(http.request.uri.path, "/wp-json/royal-mcp/")) or
(starts_with(http.request.uri.path, "/.well-known/oauth-authorization-server")) or
(starts_with(http.request.uri.path, "/.well-known/oauth-protected-resource")) or
(http.request.uri.path eq "/authorize") or
(http.request.uri.path eq "/token") or
(http.request.uri.path eq "/register")

Purge everything and retry

CachingConfigurationPurge Everything. Then retry the Claude.ai authorization.

Cloudflare adds a second blocker by default — the AI bots rule

If your domain is proxied through Cloudflare, fixing the cache isn’t enough on its own. Cloudflare’s built-in Manage AI bots managed rule blocks Anthropic’s backend IP ranges by default, which prevents the server-to-server token exchange even after your cache rules are correct. Symptom: Claude reports “Couldn’t reach the MCP server” and your Royal MCP Activity Log stays empty. Fix: a Custom WAF Rule that skips managed rules for the Royal MCP and OAuth paths only. Full walkthrough at Allow Anthropic Through Cloudflare’s AI Bots Rule. Background on the underlying mechanism.

Option D: o2switch (PowerBoost)

PowerBoost is configured at the hosting-account level and most users can’t edit its rules directly. Open a ticket with o2switch support and ask them to exclude the following paths from PowerBoost edge caching:

/wp-json/royal-mcp/*
/.well-known/oauth-authorization-server
/.well-known/oauth-protected-resource
/authorize
/token
/register

If they push back, the alternative is to disable PowerBoost on the affected domain entirely. Both options have resolved this for previous customers.

Option E: Other (Varnish, NGINX FastCGI, Squid, etc.)

If you run your own reverse-proxy cache, add a bypass rule for the same path patterns above. The general shape is “never cache anything matching /wp-json/royal-mcp/* or /.well-known/oauth-*, regardless of HTTP method.” Once that rule is in place and the cache is purged, OAuth flows will start hitting WordPress correctly.

Why Does This Happen?

OAuth 2.0 endpoints handle a mix of GET (discovery, redirect handoff) and POST (token exchange, dynamic client registration) requests on the same URL. RFC 7234 says caches should only store responses where the origin server has explicitly marked them cacheable, and they should key cache entries by URL and by request method.

Some aggressive edge caches don’t do that. When a security scanner, browser preview, or stale Claude.ai discovery probe sends a GET to /register and receives a 405 Method Not Allowed, the cache treats that 405 as “the response for this URL” and stores it — ignoring the method. Every subsequent POST to the same URL gets the cached 405 served back to it instantly, never reaching PHP. That’s why the cache-busted POST in the Verify section returns a clean 201 Created while the connector flow keeps failing — the OAuth handshake is colliding with a cached error response.

Royal MCP 1.4.13 added Cache-Control: no-store, no-cache, must-revalidate + Pragma: no-cache headers on every OAuth response specifically to defeat this behavior. That fix works on most hosts, but a small number of edge cache configurations (notably SiteGround’s NGINX Dynamic Cache and o2switch PowerBoost) ignore origin-supplied Cache-Control on URLs matched by their internal cacheable-path regex. For those, the customer-side URL exclusion above is the only reliable fix.

This is a host-level issue, not a plugin bug

If you’ve confirmed the diagnosis with the curl test in the “Verify” section above and the OAuth endpoints are clearly being cached despite the no-store headers, the fix has to live at the cache-configuration layer. Royal MCP can’t override an upstream cache that decides to ignore origin headers — only excluding the URL from caching, or disabling the cache, will work.

Quick Alternative: Skip OAuth on Claude Desktop

If you’re only trying to connect Claude Desktop (not Claude.ai web) and the cache exclusions aren’t getting you over the line, you can bypass OAuth entirely. Royal MCP supports API key authentication via mcp-remote’s --header flag, which doesn’t use /.well-known/ at all and is unaffected by edge-cache or WAF behavior on the OAuth endpoints.

It’s a two-line change to your Claude Desktop config — full walkthrough at Connect Claude Desktop via API Key (Skip OAuth).

OAuth is still required if you need Claude.ai web (browser-based) connector support, but for desktop usage the API key path is the most reliable fallback when host configuration is fighting you.

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 — so we can confirm what ModSec stack you were on and what fix worked
  • Royal MCP version from WP Admin → Plugins (1.4.16 or newer ideally, for diagnostic Activity Log entries)
  • Output of the verify curl test after ModSec was disabled — we want to confirm the 406 is actually gone
  • Screenshot of the most recent Activity Log row with View Details expanded — that’s the next layer of diagnostic for whatever is failing now
  • Which Claude client you’re using (Claude Desktop with mcp-remote, claude.ai web, Claude Code, ChatGPT, etc.)
  • Exact error message in the client, plus any ofid_xxxxx reference code if claude.ai web shows one
Related host-side OAuth failures

If the curl test does not show a 406 with “Mod Security” in the body, then ModSec isn’t your problem — check the adjacent failure modes: