ZuploZuplo
LoginSign Up
  • Documentation
  • API Reference
Introduction
Getting Started
    Develop using the Portal
      1 - Setup Your Gateway2 - Rate Limiting3 - API Key Auth4 - Deploy5 - Dynamic Rate LimitingMCP - Quick start
    Develop Locally
      1 - Setup Your Gateway2 - Rate Limiting3 - API Key Auth
Concepts
Development
Policies
Handlers
API Keys
MCP Server
MCP Gateway
    IntroductionBetaQuickstartQuickstart (Local Dev)How it works
    Connect MCP clients
    Authentication
    Configuration
    Observability
    ReferenceTroubleshooting
AI Gateway
Developer Portal
Monetization
Deploying & Source Control
Observability
Networking & Infrastructure
Account Management
Programming API
Build with AI
Zuplo CLI
Migration Guides
Platform LimitsSecuritySupportTrust & ComplianceChangelog
powered by Zudoku
MCP Gateway

Troubleshooting

This page covers the issues people hit most often with the MCP Gateway, organized by symptom. Each entry lists what you'll see, the likely cause, and the fix. Jump straight to a symptom:

SymptomMost likely cause
401 with no WWW-Authenticate headerRoute missing an MCP OAuth policy
403 with error="insufficient_scope"Token issued for the wrong scope
Connect-required errorsExpected — upstream OAuth not yet completed
Reconsent promptsUpstream credential revoked or expired
Compatibility date too oldcompatibilityDate older than 2026-03-01
Auth0 policy rejects domain with https:// prefixauth0Domain set to a URL, not a hostname
Custom domain → wrong issuer in AS metadataEdge proxy not forwarding Host
Dev server needs a restart after first connectLocal zuplo dev quirk
GET on an MCP route returns 405By design — Streamable HTTP is POST-only
Tool name doesn't match in capability filterFilter matches case-sensitively and exactly

401 with no WWW-Authenticate header

Symptom. The MCP client gets a 401 Unauthorized response when it first tries POST /mcp/<name>, but the response has no WWW-Authenticate: Bearer header. The client never discovers the authorization server.

Likely cause. The route in routes.oas.json doesn't have an MCP OAuth policy in its inbound chain. Without one of the MCP OAuth policies, the route returns a plain 401 from another policy or handler that doesn't speak the MCP authorization spec.

Fix. Add the OAuth policy to the route's policies.inbound array:

Code
"policies": { "inbound": ["auth0-managed-oauth", "mcp-token-exchange-linear"] }

A single MCP OAuth policy can (and should) be shared across every MCP route in the project.

403 with error="insufficient_scope"

Symptom. An authenticated request to an MCP route returns 403 with WWW-Authenticate: Bearer error="insufficient_scope", scope="mcp:tools", resource_metadata=....

Likely cause. The access token was issued for a different scope set than the gateway expects. The only scope the gateway issues is mcp:tools. A DCR client registered with a different scope, or a stale token from a previous configuration, will fail this check.

Fix. Re-register the client and let it discover the scope through the AS metadata document, or use step-up authorization to re-issue the token with scope=mcp:tools. For MCP clients that support incremental scope consent, the gateway's 403 response includes the required scope in WWW-Authenticate for the client to re-request.

Connect-required errors

Symptom. The first tool call after authentication returns a JSON-RPC error with code: -32042 and a payload containing "state": "authenticating" and an authUrl. The MCP client either opens a browser tab (modern clients) or surfaces the URL for the user to copy (older clients).

Likely cause. This is expected behavior. The gateway requires each user to complete the upstream OAuth flow once per upstream. After the first connection, requests skip this step entirely.

Fix. Open the authUrl in a browser and complete the upstream provider's login. The gateway stores the encrypted tokens, and the next tool call succeeds.

Reconsent prompts

Symptom. A user who connected an upstream weeks ago suddenly sees a connect-required error again, this time with "state": "reconsent_required" and a message like "Linear authorization must be renewed."

Likely cause. The upstream provider revoked the gateway's OAuth client, or the user revoked the connection from the upstream's dashboard, or the refresh token expired according to the upstream's policy. The stored connection record still exists, but its tokens are no longer usable.

Fix. Follow the same flow as a first-time connect — the user re-authorizes the upstream and the gateway replaces the stored tokens. No admin action is required.

Compatibility date too old

Symptom. Calls work most of the time, but a request that triggers an upstream 401 (for example, an upstream token expired mid-session) returns the raw 401 to the client instead of refreshing and retrying.

Likely cause. compatibilityDate in zuplo.jsonc is older than 2026-03-01. MCP Gateway features require 2026-03-01 or later.

Fix. Bump the compatibility date:

Code
{ "compatibilityDate": "2026-03-01", }

Then redeploy. See the reference page for context.

Auth0 policy rejects domain with "https://" prefix

Symptom. The runtime rejects the project's MCP Auth0 policy with a configuration error that mentions an invalid auth0Domain value.

Likely cause. McpAuth0OAuthInboundPolicy requires a bare hostname — not a URL. Passing https://my-tenant.us.auth0.com/ fails validation.

Fix. Set auth0Domain to just the hostname:

Code
{ "options": { "auth0Domain": "my-tenant.us.auth0.com", "clientId": "$env(AUTH0_CLIENT_ID)", "clientSecret": "$env(AUTH0_CLIENT_SECRET)", }, }

Custom domain → wrong issuer in AS metadata

Symptom. The Authorization Server metadata document advertises an issuer like https://my-project.zuplosite.com instead of your custom domain (https://api.example.com). MCP clients fail audience validation because the token's iss doesn't match the URL they expect.

Likely cause. The reverse proxy or CDN in front of the gateway isn't propagating the original Host header, and isn't setting X-Forwarded-Host either. The gateway derives its issuer from the incoming request's effective host.

Fix. Configure your edge proxy to forward one of these:

  • Preserve the original Host header end-to-end.
  • Or set X-Forwarded-Host: api.example.com on requests to the gateway.

The gateway uses X-Forwarded-Host when present, falling back to Host. After updating the proxy, fetch https://api.example.com/.well-known/oauth-authorization-server and confirm the issuer field matches.

Dev server needs a restart after the first MCP client connects

Symptom. When running zuplo dev locally, the first MCP client connection succeeds, but subsequent requests hang or return unexpected errors. Restarting zuplo dev makes it work again.

Fix. When in doubt, restart zuplo dev. This is a local development quirk only — the production runtime is unaffected.

GET on an MCP route returns 405

Symptom. A client (or a browser, or a probe) sends GET /mcp/linear-v1 and gets back 405 Method Not Allowed with Allow: POST and a message about Streamable HTTP.

Likely cause. This is by design. The gateway implements the Streamable HTTP transport as POST-only and doesn't open SSE streams for server-initiated messages.

Fix. Use POST for all MCP requests. Browser-based health checks or uptime monitors should hit a different endpoint — pointing them at the well-known PRM URL (/.well-known/oauth-protected-resource/<path>) is a good lightweight option.

Tool name doesn't match in capability filter

Symptom. A capability-filter policy lists a tool by name, but the upstream still appears to return everything (or returns nothing).

Likely cause. The filter matches case-sensitively and exactly. A typo, a stray space, or a capitalization difference makes the entry not match, and the policy treats the tool as if it weren't on the allow-list.

Fix. Run tools/list against the unfiltered upstream first and copy the name exactly. For example:

TerminalCode
curl -X POST https://my-gateway.zuplo.dev/mcp/linear-v1 \ -H 'Authorization: Bearer <token>' \ -H 'Accept: application/json, text/event-stream' \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","id":"1","method":"tools/list"}'

Then paste the exact name values into the policy's tools array.

Browser session expires after 8 hours

Symptom. A long-running MCP client that's been idle for most of a day suddenly redirects the user to the identity provider's login page the next time it makes a request.

Likely cause. The zuplo_mcp_session cookie has an 8-hour default lifetime. Once it expires, the next interaction that hits the consent or login flow has to re-authenticate the user against the IdP.

Fix. Either accept the re-login (this matches a typical workday) or extend the session by setting browserLogin.sessionTtlSeconds on the OAuth policy to a longer value. The trade-off is the usual one: longer sessions mean a longer window where a stolen cookie is useful.

Where to look when none of the above match

  • Open the project's Logs in the Portal and filter on the request's zuplo-request-id. Gateway log entries include the relevant operationId and subjectId.
  • Open the Analytics dashboard and switch to the MCP tab. The "Top Reason Codes" and "Failure Origins" panels surface the highest-cardinality failure modes for the current time window.
  • For OAuth-specific issues, the MCP Inspector reproduces the full discovery and authorization flow against any gateway URL and gives a step-by-step view of where it breaks.
Edit this page
Last modified on May 27, 2026
ReferenceIntroduction
On this page
  • 401 with no WWW-Authenticate header
  • 403 with error="insufficient_scope"
  • Connect-required errors
  • Reconsent prompts
  • Compatibility date too old
  • Auth0 policy rejects domain with "https://" prefix
  • Custom domain → wrong issuer in AS metadata
  • Dev server needs a restart after the first MCP client connects
  • GET on an MCP route returns 405
  • Tool name doesn't match in capability filter
  • Browser session expires after 8 hours
  • Where to look when none of the above match
JSON
JSON
JSON