Skip to content

Troubleshooting

Most Gateway problems are one of three things: the Gateway’s contract with the Britive platform, a backend that cannot be reached, or a tool list that isn’t what someone expected. This page works through each.

Diagnosing a Tenant

Start here. When the console is empty or a caller sees no tools, the cause is almost always the Gateway’s contract with the platform rather than the Gateway itself. The Gateway ships a read-only probe of that contract which prints what to fix. Run it inside a Gateway container:

docker exec <container> python -m gateway.doctor

It takes no arguments: it reads TENANT and GATEWAY_POOL_TOKEN from the container’s environment, exactly as the Gateway does. It deliberately does not fetch the pool’s settings first, because reaching the platform is the thing it is diagnosing.

It checks:

  • Whether the tenant is reachable.
  • Whether the pool token is accepted on the platform routes the Gateway needs.
  • Whether MCP server records carry a usable endpoint.
  • That per-caller routes reject the pool token.

Every check is a GET: nothing is created, mounted, or written.

That last check is the subtle one. A per-caller route that accepts the pool token means policy is being evaluated as the pool identity rather than as the calling identity - which surfaces as an empty tool catalogue rather than as an error. If you are chasing “no tools” and everything else looks healthy, this is the check that finds it.

A Caller Sees No Tools

Work through these in order.

Confirm the identity has policy

The catalogue is built from Britive policy alone. An identity with no MCP tool access sees an empty list, and that is correct behaviour. Check their policy on the tenant first.

Run the doctor probe

If policy evaluation is happening as the wrong identity, this reports it. See above.

Check that backends are mounted

Open the admin console, or:

curl -s https://mcp-gateway.example.internal/admin/backends

No backends means nothing has synced. Register an MCP server on the tenant and use Sync Now.

Check backend status

A backend in issue state has no tools to offer. A backend marked Refused was rejected by destination policy and never contacted - the reason is shown.

Confirm the client isn’t holding a stale list

MCP clients bind a tool list and hold it, and the Gateway publishes no refresh tool - a real call re-asks the platform, so it heals itself. Have the caller re-list, or call the tool it expects and read the error. Clients that honour list-changed notifications pick up a synced backend on their own.

A Backend Won’t Mount

SymptomCauseFix
Status Refused, reason mentions HTTPSThe endpoint is http://Use HTTPS. Only turn off backendRequireHttps on the pool for a local development stack.
Status Refused, reason mentions the addressThe endpoint resolves to loopback, link-local, multicast, or a reserved addressThese are always refused and cannot be allowlisted. Give the backend a routable address.
Status Refused, host not allowedbackendAllowedHosts is set on the pool and does not name this hostAdd the exact host, or clear the allowlist.
Status Refused, reason mentions a private addressbackendAllowPrivateNetworks is off on the poolTurn it back on, or move the backend to a routable address.
Status stuck on issueUnreachable, or no tools published on the tenantCheck the network path and DNS from the Gateway container. It re-probes every 15 minutes; Sync Now retries immediately.
Registered on the tenant but never appearsThe record has no endpoint the Gateway can readRun the doctor probe - it reports records it had to skip and why.

A Tool Call Fails

The error code tells you which stage stopped it.

CodeMeaningWhere to look
AUTH_REQUIREDReal, allowed tool; the backend needs authorizingThe caller opens the connect URL carried in the error, then retries the same call.
POLICY_DENIEDPolicy does not allow this toolBritive policy on the tenant.
BACKEND_UNAVAILABLEBackend unreachable or erroredBackend status in the console; the backend’s own logs.
RATE_LIMITEDPer-identity limit exceededtoolCallRateLimitBurst and toolCallRateLimitPerSecond on the pool. Check for a runaway agent loop first.
INSPECTION_BLOCKEDEnforced inspection blocked the callThe error names the phase, detector, category, severity, and finding label. The matched content is in the audit row, not the error.
TOOL_UNKNOWNName genuinely absent from this caller’s catalogueThe name, and the caller’s policy.

Every code except TOOL_UNKNOWN asserts the tool exists and the call was well-formed. A downstream_oauth backend has no live inventory until a caller has a token, so its tools come from the platform’s published catalogue - a call against one before authorizing answers AUTH_REQUIRED naming the backend, not TOOL_UNKNOWN.

A Tool Schema Is Missing Parameters

A classic: a tool’s schema shows only the injected audit parameters and none of the ones the backend actually requires, so calls fail validation.

Determine where the schema came from

curl -s https://mcp-gateway.example.internal/admin/backends

Platform-published metadata and live discovery are different sources. Platform metadata often lacks the input schema; live discovery from the backend is the source of truth.

Diff against the backend’s own tool list

Call the backend’s tools/list directly and compare it with what the Gateway surfaces. The difference is the gap.

Pass the parameters anyway

Parameters absent from a surfaced schema are still accepted by the Gateway. Pass them explicitly in the request body while the schema gap is being fixed.

Republish the tools

Once the backend’s schemas are correct, publish them so the platform catalogue matches what the backend offers.

_user_prompt, _llm_intent, and _llm_session_id are audit fields the Gateway injects. They are stripped before the call reaches the backend and are never shown in the console’s tool list. If you see them presented as ordinary callable parameters somewhere, that is the bug - not a missing backend parameter.

Sign-In Problems

SymptomCauseFix
Signed in, but the console is refusedIdentity lacks mcp.server.manageGrant it on the tenant, then sign in again.
OAuth flow fails at the callbackThe pool’s Public base URL does not match the URL the browser actually usesSet it to the load balancer’s URL, not the container’s. A startup warning about defaulting to localhost means it is unset entirely.
OAuth fails before the Gateway sees anythingTenant WAF blocking the callback, or the client’s redirect URI not registeredRegister the callback; raise WAF rules with your Britive administrator.
Signed out when hitting a different replicaThe replicas are not in the same gateway pool, or not on the same databaseEvery replica needs a pool token for one pool and the same DATABASE_URL. The pool supplies the key that lets them read each other’s sessions.
Registration rejected with 400 naming a fieldA client registration exceeded a payload limitThese bounds are fixed and sized far above what a real client sends - fix the client.
Registration rejected with 503oauthRegistrationMaxClients reachedRaise the ceiling on the pool; registrations expire with the refresh token lifetime.
Repeated 429s on sign-inFailure lockout tripped for that IPWait out authRateLimitLockoutSeconds. A successful exchange clears it.

Agent and Workload Callers

SymptomCauseFix
“gateway session required” for a token that works elsewhereallowAgentTokens is off - it is off by defaultTurn on Accept agent tokens on the pool; only then can an agent present a Britive token directly.
A settings save is rejected mentioning delegationallowOnBehalfOf set without allowAgentTokensEnable both, or neither.
Delegated call rejectedThe agent identity lacks a Delegate relationship to that personThe platform authorizes delegation, not the Gateway. Grant it on the tenant.

Calls Time Out Waiting for Approval

A checkout that requires human approval can take longer than a default 60-second timeout. The connection is cut before the approval lands, and the tool call fails.

This is an ingress timeout to raise on the MCP path, at your CDN, load balancer, or ingress controller. It is not a Gateway setting and not a Gateway defect - the Gateway is still waiting when the connection is closed underneath it.

Related: if calls appear to hang rather than fail, your proxy is probably buffering the MCP response stream. Turn buffering off on that path.

Startup Failures

The Gateway fetches its settings from the tenant before it serves anything, and a failure there is fatal by design: every catalogue, checkout, and policy decision is a platform call, so a container that started on guessed settings would look healthy while being configured by accident.

MessageCauseFix
DATABASE_URL is requiredNo database configuredSet DATABASE_URL. It is always required.
TENANT is not setThe container has no tenant to askSet TENANT - a bare subdomain or a full https:// URL.
GATEWAY_POOL_TOKEN is not setNothing to authenticate the settings fetch withSet it. The token also decides which gateway pool’s settings the container runs.
could not derive a platform URL from TENANT=…TENANT is neither a bare subdomain nor an absolute URLGive one or the other.
the platform would not serve this gateway's configuration (403…)Either the tenant predates gateway pool settings, or the pool token is not current and activeThe status code cannot tell these apart, which is why the message names both. Run the doctor probe.
could not reach the platform at …DNS, egress, or NAT from the containerCheck outbound access to the tenant from inside the container.
the platform answered with settings shape NThe tenant is newer than this imageUpdate the Gateway image to one that understands this tenant.
bootstrap response has no encryptionKeyAn incomplete bootstrap payloadRaise it with Britive - a Gateway without the pool key cannot read what its peers wrote.
A warning about a defaulted public base URLThe pool’s Public base URL is unsetSet it in the tenant portal. Startup succeeds, so this warning is the only signal before clients start failing OAuth.

A settings re-fetch failing later is not fatal. The settings already in force stay in force, and the console’s Settings tab flags them as stale. A transient platform outage must not reconfigure a working Gateway.

Settings the platform sends that this image does not recognise are logged and ignored, not rejected - the platform is upgraded before the gateways are, so an older image is routinely handed a setting it has never heard of.

Getting Help

When raising an issue, include:

  • The output of the doctor probe.
  • GET /admin/backends output, with the backend in question.
  • The audit event for the failing call - filter by session_id to get the whole episode.
  • Gateway startup logs.
  • GET /admin/settings/resolved, or a screenshot of the console’s Settings tab, so the settings actually in force are on the record.

Never paste tokens, checked-out credentials, or prompt content into a support ticket. The audit trail and the console’s Settings tab give the same diagnostic picture without them - secrets there show only as (set) or (not set).

Last updated on