# Fix /.well-known/ Content-Type with a Cloudflare Transform Rule

> SiteGround and other nginx hosts serve /.well-known/ OAuth metadata as text/plain, breaking strict MCP clients; a free Cloudflare Transform Rule fixes it.

- Canonical: <https://royalplugins.com/support/royal-mcp/siteground-cloudflare-content-type-fix/>
- HTML version: <https://royalplugins.com/support/royal-mcp/siteground-cloudflare-content-type-fix/>

---

If you’ve already placed the static OAuth metadata files in `.well-known/` and they return `HTTP/2 200` — but Claude Desktop still can’t connect — your host is serving them with the wrong Content-Type. On SiteGround (and other nginx hosts), `.htaccess ForceType` directives are silently ignored for these paths. A free Cloudflare Transform Rule rewrites the response Content-Type to `application/json` at the edge in about two minutes.

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**](https://royalplugins.com/support/royal-mcp/troubleshooting-start-here/). If you haven’t run through that yet, do it first — this article assumes the basics are already ruled out.

### Read the static-files article first

This page is the second step of a two-part fix for SiteGround. Before applying the Transform Rule, you need the static OAuth metadata files in place — see [SiteGround Returns 404 for /.well-known/](https://royalplugins.com/support/royal-mcp/siteground-well-known-404/#fix) for that. Once those files return 200, come back here if Claude Desktop still rejects the connection.

📍 New here? Start with the complete SiteGround playbook

This page covers **one layer** of the SiteGround setup. If you’re just getting started, work through the [**Royal MCP on SiteGround playbook**](https://royalplugins.com/support/royal-mcp/royal-mcp-on-siteground/) first, which sequences all three layers in the correct order.

🚀 Simpler fix if you’re on Apache or LiteSpeed (not SiteGround/nginx)

If your host runs Apache or LiteSpeed and honors `.htaccess` inside `/.well-known/`, you can override the Content-Type without Cloudflare. Create `public_html/.well-known/.htaccess` with:

```
<Files "oauth-authorization-server">
    ForceType application/json
</Files>
<Files "oauth-protected-resource">
    ForceType application/json
</Files>
```

Verify with `curl -I https://example.com/.well-known/oauth-authorization-server` — you should see `content-type: application/json`. If the header stays wrong (`text/plain` or `application/octet-stream`), your host is intercepting the entire `/.well-known/` directory and ignoring the `.htaccess` inside it — this is the pattern on some managed hosts using ACME-first SSL handlers. In that case skip to the [root `.htaccess` rewrite fix for ACME-intercept hosts](https://royalplugins.com/support/royal-mcp/acme-first-host-well-known-intercepted/) instead.

**Continue with the Cloudflare fix below only if:** (a) you’re on SiteGround, or (b) you’re on nginx, or (c) you tried the `ForceType` above and the Content-Type didn’t change AND the [ACME-intercept fix](https://royalplugins.com/support/royal-mcp/acme-first-host-well-known-intercepted/) also didn’t apply.

## When You Need This Fix

You’re hitting the Content-Type issue if all of these are true:

#### The Telltale Signs

- You’ve already placed `oauth-authorization-server` and `oauth-protected-resource` in `public_html/.well-known/` per the static-files article
- Both URLs return `HTTP/2 200` with the correct JSON body when you `curl` them
- `curl -I` on either URL shows `content-type: text/plain` (or `application/octet-stream`) — **not** `application/json`
- You added a `.htaccess` file with `ForceType application/json` directives, but the Content-Type didn’t change
- Claude Desktop / mcp-remote shows “Couldn’t reach the MCP server” despite OAuth metadata being reachable

Why `.htaccess` doesn’t work for these paths

SiteGround’s nginx layer serves files in `/.well-known/` directly as static content and never falls through to Apache. Since `.htaccess` directives only apply when Apache handles the response, `ForceType` is silently ignored on these paths. The same is true on most managed hosts that intercept `/.well-known/*` at the nginx layer for ACME SSL renewals.

Pre-flight: confirm Cloudflare is actually in your request path BEFORE you build the rule

The fix below only works if your domain’s DNS record is set to **proxied** (orange cloud) in Cloudflare. If it’s DNS-only (gray cloud), traffic goes straight to your host and Cloudflare’s Transform Rules never fire — you can build, deploy, enable, disable the rule and observe zero change in response headers, because there’s nothing for the rule to act on.

Quick check — from any terminal:

```
curl -sI https://example.com/.well-known/oauth-authorization-server | grep -i cf-ray
```

(Substitute your actual domain.) If you see a `cf-ray: ...` header, you’re proxied through Cloudflare and good to proceed with the fix. If you see nothing back, your record is gray-cloud / DNS-only — flip the relevant A record to proxied in **Cloudflare Dashboard → DNS → Records** (orange cloud icon) before continuing. **Caveat:** adding Cloudflare to the request path can introduce its own connector-breaking issues (Bot Fight Mode and AI Crawl Control are known to block `python-httpx`, Anthropic’s OAuth backend UA, on some setups). If your OAuth flow is currently working with the static-file fix alone and your only remaining concern is the cosmetic Content-Type, the lower-risk path is to leave the record as-is — claude.ai and Claude Desktop both accept `text/plain` in practice.

## Why Cloudflare Is the Fix

RFC 8414 requires OAuth Authorization Server metadata responses to be served as `application/json`. RFC 9728 requires the same for Protected Resource Metadata. Strict MCP clients — Claude Desktop’s `mcp-remote` bridge in particular — reject metadata served with the wrong Content-Type, even if the JSON body is valid.

To fix this we need to override the response Content-Type. There are three places that could happen: in the application (PHP), in the web server (nginx), or at the edge (a CDN in front). On SiteGround, the first two are blocked:

- **PHP can’t intercept these paths.** nginx serves the static file directly and the request never reaches WordPress, so the plugin can’t set the Content-Type.
- **nginx config is host-controlled.** SiteGround migrated to Google Cloud infrastructure in 2026 and confirmed they no longer make per-customer nginx changes. There’s no path to add a MIME-type rule.
- **An edge layer can rewrite headers freely.** Cloudflare sits in front of the origin, sees the response on its way back to the client, and can override response headers per URL pattern with no host involvement.

Cloudflare’s free plan includes Transform Rules with up to 10 Modify Response Header rules — more than enough for this fix. You don’t need a paid plan, you don’t need APO, and you don’t need to ask SiteGround for anything.

## The Fix — Cloudflare Modify Response Header Rule

Prerequisite: your domain must be on Cloudflare (proxied)

Transform Rules only fire on requests that pass through Cloudflare’s edge. If your nameservers don’t point to Cloudflare yet, sign up for the free plan at [dash.cloudflare.com/sign-up](https://dash.cloudflare.com/sign-up) and follow Cloudflare’s nameserver migration prompt before continuing. The DNS A record for your domain must show the orange-cloud (proxied) icon — not gray-cloud (DNS-only). Setup typically takes 15–30 minutes including DNS propagation.

#### Open Transform Rules in the Cloudflare dashboard

Sign in at [dash.cloudflare.com](https://dash.cloudflare.com/), select the zone for your domain, then in the left sidebar click **Rules** → **Transform Rules**.

#### Create a Modify Response Header rule

Click the **Modify Response Header** tab, then **Create rule**. Give it a memorable name like *Force JSON Content-Type on OAuth metadata*.

#### Set the matching expression

Switch to **Edit expression** mode and paste this filter exactly as written. It matches both OAuth metadata paths Royal MCP relies on:

```
(http.request.uri.path eq "/.well-known/oauth-authorization-server") or (http.request.uri.path eq "/.well-known/oauth-protected-resource")
```

Two URI paths, both joined with `or`. Cloudflare will validate the syntax inline; if it shows green, you’re good.

#### Configure the response header override

In the **Then…** section, set:

- **Modify response header:** *Set static*
- **Header name:** `Content-Type`
- **Value:** `application/json`

#### Deploy the rule

Click **Deploy**. Cloudflare propagates Transform Rules globally within seconds — you don’t need to wait for DNS or cache purge.

#### Verify the override is live

From any terminal:

```
curl -sI https://example.com/.well-known/oauth-authorization-server | grep -i content-type
curl -sI https://example.com/.well-known/oauth-protected-resource | grep -i content-type
```

Both should now show `content-type: application/json`. If they still show `text/plain`, double-check (a) the Transform Rule is enabled, (b) the URI path expressions match exactly, and (c) the DNS record is proxied (orange cloud) rather than DNS-only.

#### Reconnect Claude Desktop / Claude.ai

Quit and relaunch Claude Desktop, or click **Reconnect** in Claude.ai → Connectors. If the OAuth flow completes cleanly, you’re done. If you still see “Couldn’t reach the MCP server” and your Royal MCP Activity Log shows no auth activity, continue to [the next section](#ai-bots) — Cloudflare’s default AI bots rule is most likely intercepting Anthropic’s backend before it ever reaches your site.

## How to Confirm the Fix Worked

Three checks confirm the override is in place and Claude is happy:

#### Headers show `application/json`

The `curl -sI` commands above should show the corrected Content-Type. If they still show `text/plain`, the Transform Rule isn’t firing — recheck the proxied DNS record and the expression syntax.

#### Body content is unchanged

Run `curl -s https://example.com/.well-known/oauth-authorization-server | head` and confirm you still see your JSON. Transform Rules only modify headers; they never touch the body. If the body changed, something else is going on.

#### Claude Desktop logs show successful discovery

Inspect `%APPDATA%\Claude\logs\mcp*.log` on Windows, or `~/Library/Logs/Claude/mcp*.log` on macOS, for entries from your reconnection attempt. A successful flow logs the OAuth metadata fetch and a Bearer token issuance. Any earlier “invalid Content-Type” or “couldn’t parse OAuth metadata” errors should be gone.

## After the Transform Rule: Allow Anthropic Through Cloudflare’s AI Bots Rule

The Transform Rule above fixes the Content-Type, but putting Cloudflare in front of your site activates a second blocker by default. Cloudflare’s built-in **Manage AI bots** managed rule (under the “Cloudflare Bot Management rules for all plans” ruleset) blocks Anthropic’s backend IP ranges — including the IPs Claude uses for the server-to-server OAuth token exchange and post-auth tool calls.

The browser side of the OAuth flow originates from your own IP, so consent completes normally. But the moment Anthropic’s backend tries to call `/token` or any MCP tool endpoint, Cloudflare blocks the request at the edge before WordPress ever sees it. Symptom: Claude shows “Couldn’t reach the MCP server” and your Royal MCP Activity Log stays empty for the entire connection-attempt window.

This is a cascade caused by following the previous step

Without Cloudflare in front, this rule never affects you. By proxying your domain through Cloudflare to apply the Transform Rule, you opt into Cloudflare’s default bot management — which actively blocks the IPs needed for OAuth to work. [Background reading on the underlying mechanism](https://royalplugins.com/blog/cloudflare-block-ai-bots-breaks-mcp/).

### The Fix — Skip Managed Rules for Royal MCP Paths

Path-based exclusion is more durable than IP-based: Anthropic’s IP allocations may grow over time, but the Royal MCP URL paths are stable and version-controlled in this plugin. Create a Custom WAF Rule that skips all managed rules — including AI bots and Super Bot Fight Mode — only for traffic destined for your MCP and OAuth endpoints. The rest of your site stays fully protected.

#### Open Custom WAF Rules

Cloudflare dashboard → **Security** → **WAF** → **Custom rules** tab → **Create rule**.

#### Name the rule and set the matching expression

Name it something memorable like *Allow Royal MCP and OAuth paths through managed rules*, then switch to **Edit expression** mode and paste this filter exactly as written:

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

This matches every URL Royal MCP touches: the MCP REST namespace, the two OAuth metadata files, and the three root-rewritten OAuth endpoints (`/authorize`, `/token`, `/register`).

#### Set the action to Skip

In the **Then take action…** section:

- **Action:** *Skip*
- **Skip features:** check *All managed rules* AND *Super Bot Fight Mode rules* if you have it. The latter is the umbrella that contains the “Manage AI bots” rule specifically.

Leave the other Skip options unchecked — you only want to bypass managed rules for these paths, not your custom rules or rate limiting.

#### Deploy the rule

Click **Deploy**. Custom WAF rules propagate within seconds.

#### Reconnect and verify in Cloudflare Events

Try the Claude connection again. In the Cloudflare dashboard → **Security** → **Events**, filter to a 5-minute window covering your reconnect attempt and look for source IPs starting with `160.79.` (Anthropic’s observed range). You should see entries for `/register`, `/token`, or `/wp-json/royal-mcp/v1/mcp` with action *Allow* and your new Custom Rule named as the matched rule. If you see *Block* from a different feature, expand the Skip features in your Custom Rule to cover it — the Events log names the exact rule + ruleset every time.

Why path-based instead of IP-based

An IP-based Skip rule (e.g. `ip.src in {160.79.104.0/22}`) was the first workaround surfaced in this cascade, but it’s brittle: Anthropic’s IP allocations may expand without notice, and the day they add a new range the rule silently stops covering it. The Royal MCP URL paths, by contrast, are stable across plugin versions. Skip by path and the rule keeps working regardless of what Anthropic does with their network footprint.

If the Skip rule isn’t enough

Some accounts have additional security features that intercept at different phases — Browser Integrity Check, Cloudflare Access, or a separate Bot Fight Mode toggle on the dashboard root. The most reliable signal is **Security → Events**: every blocked request names the exact rule + ruleset that caught it, and you can extend your Skip rule to cover that specific feature. If everything in Events shows *Allow* but the connection still fails, the issue is past Cloudflare entirely — head to the Troubleshooting section below.

## If the Rule Doesn’t Stick

#### The DNS record is gray-cloud (DNS-only)

Transform Rules only fire on proxied traffic. If the orange cloud icon next to your A record is gray, requests bypass Cloudflare entirely and hit SiteGround directly.

##### Fix

Cloudflare dashboard → **DNS** → **Records**. Click the gray-cloud icon next to your A record (and AAAA if present) to flip it to orange. Wait 30 seconds for the change to propagate, then re-run the curl test.

#### The expression has a typo and Cloudflare flagged it

Transform Rules require strict expression syntax. A missing parenthesis or quote will block the rule from saving.

##### Fix

Switch the expression editor to **Edit expression** mode and paste the filter from [Step 3](#fix) exactly. Don’t hand-type it — quote characters and the URI paths must match exactly. Cloudflare will show a green checkmark when the syntax is valid.

#### SiteGround Cache or Cloudflare Cache is serving a stale 200 with old headers

Transform Rules fire on the response headers Cloudflare returns to the client. If a cached response from before the rule was deployed is still being served, you’ll see the old Content-Type.

##### Fix

Cloudflare dashboard → **Caching** → **Configuration** → **Purge Everything**. Then on SiteGround: **SG Optimizer** → **Caching** → **Purge SG Cache**. Re-run the curl test.

#### Claude Desktop still rejects after the headers are correct

If the Content-Type is now correct but Claude Desktop still shows “Couldn’t reach the MCP server”, the failure is somewhere else in the OAuth flow — not the metadata Content-Type.

##### Fix

Check the Claude Desktop logs (`%APPDATA%\Claude\logs\mcp*.log`) for the actual error. Common causes: relative URLs in your `resource` field, OAuth token endpoint cached at the edge, WAF blocking POST requests. The [OAuth Fails on Managed Hosts](https://royalplugins.com/support/royal-mcp/oauth-fails-on-managed-host/) article covers cache poisoning, and the static-files article covers the resource-field issue.

## Alternative: Skip OAuth Entirely on Claude Desktop

If you only need Claude Desktop (not Claude.ai web) and you don’t want to set up Cloudflare, Royal MCP supports API-key authentication via `mcp-remote`’s `--header` flag. This bypasses OAuth completely — no `/.well-known/`, no Content-Type concerns, no Transform Rules.

Full walkthrough: [**Connect Claude Desktop via API Key (Skip OAuth)**](https://royalplugins.com/support/royal-mcp/connect-claude-desktop-api-key/).

Use this fallback if Cloudflare isn’t an option for your setup. For Claude.ai web (browser connector), the Transform Rule above is the only route — the API-key bypass works only for Claude Desktop.

## Why We No Longer Recommend Asking SiteGround Support

Through April 2026, the long-term fix for this issue was to open a SiteGround support ticket asking them to add an nginx MIME type mapping for the two metadata paths. SiteGround had granted similar per-customer nginx exceptions in the past.

That changed when SiteGround migrated to Google Cloud infrastructure. As confirmed by SiteGround support to a Royal MCP customer in May 2026, they no longer make per-customer nginx configuration changes on the new platform — the previous escalation path is permanently closed. The Cloudflare Transform Rule documented above is the recommended replacement.

This pattern applies to other managed hosts too

The Cloudflare Transform Rule approach works on any managed host where (a) `/.well-known/*` is served by nginx as static files, (b) `.htaccess ForceType` is silently ignored, and (c) you can’t edit nginx config directly. We’ve seen the same fingerprint on some o2switch, Hostinger, and Pressable configurations — the same Transform Rule fix applies.

## 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

- **Full `curl -i` output** (with body) for both `/.well-known/oauth-authorization-server` and `/.well-known/oauth-protected-resource`
- **Royal MCP version** from WP Admin → Plugins
- **Your domain**
- **Confirmation the DNS record is proxied** (orange cloud) on Cloudflare
- **Claude Desktop / Claude.ai error message** after the failed connection
- **Excerpt from `mcp*.log`** from the failed attempt (last 50 lines is plenty)

[Static Files for /.well-known/ (step 1)](https://royalplugins.com/support/royal-mcp/siteground-well-known-404/)
[Back to Royal MCP Support](https://royalplugins.com/support/royal-mcp/)
