Troubleshooting MCP Connections
Most MCP connection failures don’t originate in Total CMS — they happen in front of it, at a CDN or a web server that strips or blocks something before the request ever reaches PHP. That makes them invisible from inside the admin, and it’s why the symptoms operators report (“Claude just won’t connect”, “the agent can’t see anything”) often don’t match the actual cause.
This page covers the three failure patterns behind nearly every real-world MCP support case, plus a reading guide to the Test connection panel (Admin → Settings → MCP Server → Test connection) that probes for them automatically.
”Claude authenticates, then cannot connect”
Section titled “”Claude authenticates, then cannot connect””Symptom: The browser OAuth flow works fine — the user logs in, sees the consent screen, clicks Allow — and then the connector fails anyway with a generic error like “Couldn’t connect to the server.” Nothing in the T3 OAuth activity log shows a problem; the grant was issued.
Cause: A CDN or WAF in front of your site is blocking the user-agent AI clients use for their actual tool traffic. The most common case is Cloudflare’s AI-bot blocking rejecting the Claude-User user-agent with a 403 (Cloudflare error 1010). The browser step that just succeeded uses a normal browser user-agent, so it sails through — but claude.ai’s backend performs the OAuth token exchange and the subsequent MCP requests (initialize, tool calls) as Claude-User, and that traffic dies at the CDN edge before it ever reaches Total CMS. From the operator’s side this looks like nothing is wrong: the consent screen worked, the grant exists, and the failure reads as a vague client-side error because the client genuinely never got a response from your server.
The signature to watch for: browser OAuth login/consent succeeds, then the connector fails immediately after with a generic connection error. That gap — auth worked, everything after it didn’t — is the tell.
Diagnose it yourself with one curl command:
curl -A "Claude-User" -X POST https://your-site.com/mcpCompare that to the same request with curl’s default user-agent (drop the -A flag). If the Claude-User request comes back 403 (often with a Cloudflare error page, “error code: 1010”) while the default-UA request gets a normal response, you’ve confirmed a user-agent block.
Fix:
- Cloudflare: go to Security → Bots and allow AI bots / crawlers.
- Other WAFs: add a skip rule for verified bots, or specifically allow the
Claude-UserandChatGPT-Useruser-agents. - At minimum, exempt the MCP endpoint,
/oauth/*, and/.well-known/*from user-agent filtering — those are the paths every MCP client touches during connection setup.
The Test connection panel’s AI clients allowed probe checks for exactly this and will fail with the same diagnosis if it finds a UA-based block — see The Test connection panel below.
”Connected but only sees public content”
Section titled “”Connected but only sees public content””Symptom: An OAuth-authenticated MCP client connects without errors, but the agent behaves as if it’s anonymous — it can only see content that’s already publicly exposed, admin or write tools are missing, and nothing about the failure looks like an auth error.
Cause: Apache running PHP as CGI or FastCGI does not pass the Authorization header through to PHP by default. The client is sending Authorization: Bearer <token> exactly as it should, but the web server drops the header before Total CMS ever sees it — so every Bearer-authenticated request silently degrades to an anonymous request. There’s no error, no 401, nothing that looks broken; the request just quietly loses its identity.
Fix: Total CMS ships the fix for this out of the box on affected hosts. The shipped public/.htaccess includes a passthrough idiom:
RewriteCond %{HTTP:Authorization} .RewriteRule ^ - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]This exports the header as REDIRECT_HTTP_AUTHORIZATION (Apache’s rewrite-environment naming), and AuthorizationHeaderMiddleware restores it onto the request before any auth middleware runs. If you’re on a host where this still isn’t working — a custom vhost that bypasses .htaccess, or a non-Apache CGI setup — add the equivalent directive to your vhost config instead:
CGIPassAuth On(Apache 2.4.13+.)
Verify it’s working: open Admin → Settings → MCP Server, click Test connection, and check the Bearer auth reaches PHP result. It sends a deliberately invalid Bearer token and expects a 401 back — if it gets a 200 instead, the header isn’t reaching PHP and this is your fix.
Subfolder installs (Stacks)
Section titled “Subfolder installs (Stacks)”Symptom: An install that lives under a subpath — most commonly a Stacks site, where Total CMS runs at something like /rw_common/plugins/stacks/tcms rather than the domain root — has MCP clients failing to connect, or connecting inconsistently depending on which URL was used to discover the server.
Cause and fix, two separate issues:
1. The endpoint isn’t at /mcp on the bare domain. On a subpath install, the real MCP endpoint includes the install’s base path — something like https://your-site.com/rw_common/plugins/stacks/tcms/mcp, not https://your-site.com/mcp. Don’t guess or assume the short form. Copy the exact endpoint URL from Admin → Settings → MCP Server and paste it into the client’s configuration verbatim.
2. Two discovery authorities can disagree. The setup wizard’s Server Config step offers an optional root catch-all rewrite for subpath installs — adding it lets short root-relative URLs and the RFC well-known discovery locations (/.well-known/mcp.json, /.well-known/oauth-authorization-server, etc.) resolve at the domain root in addition to the install’s real subpath. That’s convenient, but it means the site can now answer discovery requests two different ways — once from the root, once from the actual subpath — and if those two answers ever disagree on which endpoint to use, a client that discovers through one shape and connects through the other can fail in confusing, hard-to-reproduce ways.
If the Test connection panel reports a Single discovery authority warning (meaning the root and subpath discovery documents advertise different endpoints), pin the canonical base explicitly by setting api in config/tcms.php:
'api' => '/rw_common/plugins/stacks/tcms',(Use an empty string '' if you want the root shape to be canonical instead.) Pinning api also ensures that RFC discovery URLs (/.well-known/oauth-authorization-server, /.well-known/mcp.json, etc.) advertise a consistent issuer, which strict OAuth clients require — until it’s pinned, root-shaped requests advertise a bare-host issuer that differs from subpath-shaped requests. This resolves the ambiguity without changing how the install actually serves requests — it just tells Total CMS which base path is the one true answer when it reports discovery metadata.
The Test connection panel
Section titled “The Test connection panel”Admin → Settings → MCP Server → Test connection runs a set of outbound self-probes — Total CMS calling its own public URLs the way an external AI client would — and reports what it finds. Each probe below can show pass, warn, fail, skip (not applicable to this install), or couldn’t test.
Endpoint — Fetches the /.well-known/mcp.json discovery document and performs a JSON-RPC initialize call against the advertised MCP endpoint. A fail here means the MCP server itself isn’t reachable or answering correctly — check that it’s enabled and that nothing (maintenance mode, a WAF) is intercepting the endpoint before digging into anything more specific.
AI clients allowed — Repeats the initialize call using the Claude-User and ChatGPT-User user-agents. A fail here is the Cloudflare/WAF blocking case above — a 403, 429, or 503 on an AI-client user-agent while the default user-agent passes. A pass means no user-agent-based blocking was detected — it does not rule out IP- or ASN-based blocking, which a self-probe run from your own server can never see, since your server isn’t the one being blocked.
Bearer auth reaches PHP — Sends a deliberately invalid Bearer token and expects a 401 back. A fail (getting 200 instead) is the stripped-Authorization-header case above.
Root URL rewrite — Subpath installs only. Checks whether /.well-known/mcp.json is reachable at the domain root, which only works if the setup wizard’s optional root catch-all rewrite rules were added. A warn here doesn’t mean the MCP server is broken — it still works fine at its full subpath URL — it means the shorter root-relative discovery path isn’t available, which some MCP clients try first.
Single discovery authority — Subpath installs with the root rewrite in place. Compares the endpoint advertised by the root-shape discovery document against the one advertised at the real subpath. A warn means the two disagree — see Subfolder installs (Stacks) above for the fix.
OAuth discovery — Checks that the JWKS endpoint (/.well-known/jwks.json) answers when OAuth is enabled. A fail usually means OAuth signing keys were never generated — run tcms oauth:setup.
“Couldn’t test” is not a failure. Every probe is wrapped so that a transport error — DNS failure, connection refused, timeout — reports as unreachable (“couldn’t test”), never as a fail. This happens when the server can’t reach its own public URL, which is common on shared hosts that block loopback requests to their own domain. It says nothing about whether external clients can reach your site; if you see this, test the same URLs from another machine instead of treating it as confirmation of a problem.