Plugin Support
rpteam
(@rpteam)
Hey @vasok71
Ran some quick tests against mushwell.com — endpoints are reachable, discovery correctly advertises PKCE S256, and the response headers show Cloudflare + SiteGround edge caches BOTH correctly bypassing cache on /authorize (query-string bypass is active). So the edge layer isn’t the obvious suspect for stripping params.
Before I recommend a fix, I need to distinguish between two possibilities:
A. ChatGPT actually isn’t sending PKCE — some clients/versions skip it
B. Something between HTTP → PHP is stripping code_challenge specifically — rare but real
Can you check Royal MCP → Activity Log → the oauth:authorize row from your most recent failed attempt? If it captures the raw query params received, share what’s in there. That tells us whether ChatGPT sent PKCE and it got stripped, OR ChatGPT never sent it.
If the log doesn’t capture raw params in detail, one more test that would help: try connecting from Claude.ai instead of ChatGPT (same account/site). Claude.ai definitely sends PKCE. If Claude.ai connects
successfully, that isolates the issue to ChatGPT specifically (either their client isn’t sending PKCE, or their infrastructure is stripping it before it hits your host).
Meanwhile, common host-layer patterns for OAuth flows: https://royalplugins.com/support/royal-mcp/troubleshooting-start-here.html
RP Team
Hello, thank you for checking this.
Here is the exact Royal MCP Activity Log entry from the latest failed ChatGPT connection attempt. I have redacted only the IP address, client ID, state value and callback identifier:
Request Data:
{
"method": "GET",
"uri": "/authorize?response_type=code&client_id=[REDACTED]&redirect_uri=https://chatgpt.com/connector/oauth/[REDACTED]&scope=mcp:full&resource=https://mushwell.com/wp-json/royal-mcp/v1&state=[REDACTED]&ui_locales=es-ES",
"ip": "[REDACTED]",
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36",
"client_id": "[REDACTED]",
"grant_type": "",
"response_type": "code"
}
Response Data:
{
"http_status": 302,
"code": "invalid_request",
"description": "PKCE with code_challenge_method=S256 is required."
}
As shown in the URI captured by Royal MCP, neither code_challenge nor code_challenge_method is present.
The same result occurred in two consecutive ChatGPT connection attempts. Immediately before this, the real ChatGPT DCR request reached oauth:register successfully and Royal MCP returned HTTP 201 with a valid client ID.
Does the uri field in Royal MCP Activity Logs represent the complete query string received by PHP? If so, it appears that the PKCE parameters were already absent when the request reached Royal MCP.
Please let me know if you need any additional diagnostic information.
Thank you.
Plugin Support
rpteam
(@rpteam)
Hi @vasok71 ,
Thanks for the Activity Log dump, that’s the exact data we needed.
What your log confirms: code_challenge and code_challenge_method were both absent from the request URI when it reached PHP. The uri field in Activity Log is the raw $_SERVER[‘REQUEST_URI’] WordPress
receives from the web server ( not reconstructed from parsed $_GET) so we can rule out Royal MCP as the source of the missing PKCE fields. Discovery correctly advertises S256, DCR registered your client
as public (which mandates PKCE), and /authorize reads code_challenge directly from the raw query string. Nothing in that path strips or transforms it. If it’s absent in the log, it was absent when the request hit PHP.
The delta has to be somewhere between ChatGPT and your web server. Two directions:
- Ask your host what request-inspection or request-rewriting layers sit in front of PHP on
mushwell.com. Managed WordPress hosts often run application firewalls, request-normalizers, or
security-optimization middleware between Cloudflare and Apache/nginx. code_challenge and
code_challenge_method are unusual parameter names — a poorly-configured allowlist that drops
non-standard query params would produce exactly this pattern.
- Try the same connection from Claude.ai as a control. Claude.ai’s MCP connector sends PKCE
identically. If Claude.ai’s /authorize row in your Activity Log shows the same PKCE-missing shape, the stripping is definitively at the host layer. If Claude.ai succeeds while ChatGPT fails, that narrows
things further.
For context on point 1: We ran the same end-to-end today on our own demo site (hadn’t used it since
May, needed a Settings → Permalinks → Save Changes to re-flush WordPress rewrites before discovery came back live). After that, ChatGPT completed the full flow — DCR register → /authorize with PKCE → /token → connected — and Activity Log recorded code_challenge intact. That’s not evidence your site should just work; it’s evidence the code path handles a clean request as designed, so the delta on your end is environmental. (Aside: ChatGPT renamed “Connectors” to “Plugins” at some point — our support docs are being updated to match the new UI.)
What host is mushwell.com on? That’s the fastest signal on where to look next.
RP Team
Hello, thank you for the detailed explanation.
mushwell.com is hosted on SiteGround Managed WordPress, with Cloudflare in front of it.
There is one relevant detail: SiteGround previously identified an anti-bot/WAF rule that was blocking ChatGPT’s POST request to /register based on the python-httpx/0.27.0 User-Agent, before the request reached PHP/WordPress.
SiteGround disabled only that specific rule for mushwell.com. After that change:
- POST
/register started returning HTTP 201
- DCR began working correctly
oauth:register appeared as SUCCESS in Royal MCP
Cloudflare Bot Fight Mode is disabled, and a custom Skip rule is configured for the Royal MCP OAuth and MCP routes.
The current failing /authorize request is a GET request opened in Chrome from my own IP address. It reaches Royal MCP, but the raw REQUEST_URI does not contain code_challenge or code_challenge_method.
I have not tested the connection from Claude.ai yet. I will first ask SiteGround whether any request-inspection, request-normalization, ModSecurity or URL-rewriting layer could be dropping those two query parameters before the request reaches PHP.
Please let me know if there is any specific SiteGround log, rule or diagnostic test you would recommend requesting from them.
Thank you.
Plugin Support
rpteam
(@rpteam)
Hi @vasok71,
That’s a very useful signal, SiteGround + prior /register python-httpx UA fix by their support = clear
direction and a working precedent for SG being cooperative on this account. Two things matter for the
next round:
1. This is a different SG layer than the /register fix. The prior fix was UA-based (blocked python-httpx). This failure is a GET request from Chrome on your own IP — so a UA/bot rule can’t be the source. That means the layer stripping code_challenge / code_challenge_method is operating at the query-argument level, not the UA level. Different rule, different fix.
2. Concrete asks to hand SG support:
a. Full ModSecurity audit log for the failing /authorize request. Ask for the audit log entries corresponding to GET /authorize?… from your IP, matching the timestamps of your two failed attempts. That log will show every rule that inspected/matched/transformed the request. If a rule stripped or sanitized specific query args, it will appear there.
b. Check for OWASP CRS rules or custom rules that inspect argument names. SG typically runs ModSecurity with OWASP CRS at some paranoia level. Some CRS rules match against ARGS_NAMES (parameter names, not values) and can strip or log based on patterns. Ask SG to identify any active rule that inspects arg names on paths matching /authorize* or query strings containing code_*.
c. Ask specifically about HTTP method-override protection rules. Some ModSecurity setups strip or sanitize any parameter named _method or ending in _method because Rails, Symfony, and other frameworks use _method as an HTTP verb override, and unfiltered _method params are a common attack vector. Your missing code_challenge_method ends in _method — worth ruling out. If such a rule is active and matches on .*_method$, it could strip code_challenge_method, and a companion rule cascading on the same arg-name prefix could take code_challenge with it.
d. Have SG test-reproduce internally. Ask their tech to curl from an SG server directly to mushwell.com/authorize?response_type=code&client_id=test&redirect_uri=https://example.com&code_challenge=abc123&code_challenge_method=S256&state=x&scope=mcp:full and verify whether all query params traverse their proxy layer to PHP intact. If SG’s own internal probe shows args being stripped, they can identify the responsible rule immediately.
Reference for SG’s tech (so they know what the params are for): The two missing parameters are required by RFC 7636 — Proof Key for Code Exchange (PKCE) (https://datatracker.ietf.org/doc/html/rfc7636), a mandatory security extension for OAuth 2.1 authorization code flows. Modern OAuth-based AI/MCP clients (ChatGPT, Claude.ai, Claude Desktop) send code_challenge and code_challenge_method on /authorize requests. Blocking them = blocking the entire OAuth 2.1 flow.
In parallel — 5-minute control test. While SG works on their end, connect the same site from Claude.ai (custom connector, same URL). Same OAuth 2.1 + PKCE mechanic. If Claude.ai completes cleanly, the SG rule is somehow ChatGPT-request-shaped (possibly triggered by ui_locales, resource, or the specific redirect_uri host pattern) and we’ve narrowed the rule further. If Claude.ai also loses PKCE at
/authorize, it’s a general OAuth 2.1 filter and SG’s fix will resolve both clients at once.
RP Team