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.doctorIt 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/backendsNo 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
| Symptom | Cause | Fix |
|---|---|---|
Status Refused, reason mentions HTTPS | The endpoint is http:// | Use HTTPS. Only turn off backendRequireHttps on the pool for a local development stack. |
Status Refused, reason mentions the address | The endpoint resolves to loopback, link-local, multicast, or a reserved address | These are always refused and cannot be allowlisted. Give the backend a routable address. |
Status Refused, host not allowed | backendAllowedHosts is set on the pool and does not name this host | Add the exact host, or clear the allowlist. |
Status Refused, reason mentions a private address | backendAllowPrivateNetworks is off on the pool | Turn it back on, or move the backend to a routable address. |
Status stuck on issue | Unreachable, or no tools published on the tenant | Check 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 appears | The record has no endpoint the Gateway can read | Run 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.
| Code | Meaning | Where to look |
|---|---|---|
AUTH_REQUIRED | Real, allowed tool; the backend needs authorizing | The caller opens the connect URL carried in the error, then retries the same call. |
POLICY_DENIED | Policy does not allow this tool | Britive policy on the tenant. |
BACKEND_UNAVAILABLE | Backend unreachable or errored | Backend status in the console; the backend’s own logs. |
RATE_LIMITED | Per-identity limit exceeded | toolCallRateLimitBurst and toolCallRateLimitPerSecond on the pool. Check for a runaway agent loop first. |
INSPECTION_BLOCKED | Enforced inspection blocked the call | The error names the phase, detector, category, severity, and finding label. The matched content is in the audit row, not the error. |
TOOL_UNKNOWN | Name genuinely absent from this caller’s catalogue | The 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/backendsPlatform-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
| Symptom | Cause | Fix |
|---|---|---|
| Signed in, but the console is refused | Identity lacks mcp.server.manage | Grant it on the tenant, then sign in again. |
| OAuth flow fails at the callback | The pool’s Public base URL does not match the URL the browser actually uses | Set 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 anything | Tenant WAF blocking the callback, or the client’s redirect URI not registered | Register the callback; raise WAF rules with your Britive administrator. |
| Signed out when hitting a different replica | The replicas are not in the same gateway pool, or not on the same database | Every 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 field | A client registration exceeded a payload limit | These bounds are fixed and sized far above what a real client sends - fix the client. |
| Registration rejected with 503 | oauthRegistrationMaxClients reached | Raise the ceiling on the pool; registrations expire with the refresh token lifetime. |
| Repeated 429s on sign-in | Failure lockout tripped for that IP | Wait out authRateLimitLockoutSeconds. A successful exchange clears it. |
Agent and Workload Callers
| Symptom | Cause | Fix |
|---|---|---|
| “gateway session required” for a token that works elsewhere | allowAgentTokens is off - it is off by default | Turn on Accept agent tokens on the pool; only then can an agent present a Britive token directly. |
| A settings save is rejected mentioning delegation | allowOnBehalfOf set without allowAgentTokens | Enable both, or neither. |
| Delegated call rejected | The agent identity lacks a Delegate relationship to that person | The 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.
| Message | Cause | Fix |
|---|---|---|
DATABASE_URL is required | No database configured | Set DATABASE_URL. It is always required. |
TENANT is not set | The container has no tenant to ask | Set TENANT - a bare subdomain or a full https:// URL. |
GATEWAY_POOL_TOKEN is not set | Nothing to authenticate the settings fetch with | Set 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 URL | Give 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 active | The 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 container | Check outbound access to the tenant from inside the container. |
the platform answered with settings shape N | The tenant is newer than this image | Update the Gateway image to one that understands this tenant. |
bootstrap response has no encryptionKey | An incomplete bootstrap payload | Raise it with Britive - a Gateway without the pool key cannot read what its peers wrote. |
| A warning about a defaulted public base URL | The pool’s Public base URL is unset | Set 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/backendsoutput, with the backend in question.- The audit event for the failing call - filter by
session_idto 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).