Settings Reference
Every setting a gateway pool holds, in the order the tenant portal presents it. Treat this as a catalogue rather than a checklist: only the values you change are stored on the pool, and every default here is a value chosen to be left alone.
Each setting is shown by its API name (publicBaseUrl) and, in the same row, the
label the portal gives it. The Gateway’s own admin console shows the same settings in
snake_case (public_base_url); they are the same setting under two spellings.
Nothing on this page requires a restart. A change made in the portal reaches every
gateway in the pool within backendSyncIntervalSeconds (five minutes by default).
Settings the container reads from its environment instead - TENANT,
GATEWAY_POOL_TOKEN, DATABASE_URL, PORT, LOG_LEVEL - are covered in
Configuration.
General
| Setting | Portal label | Default | Accepts |
|---|---|---|---|
publicBaseUrl | Public base URL | (none) | An absolute http(s):// URL |
logLevel | Log level | info | debug, info, warning, error |
toolDiscoveryMode | Tool discovery | full | full, search |
toolDiscoveryMaxResults | Search results | 8 | Integer, 1-100 |
publicBaseUrl is the address MCP clients actually use. The OAuth issuer, the
redirect URI, and the protected-resource URLs are all built from it, so it must match
how clients connect - the load balancer’s address where TLS terminates there, not the
container’s port.
Left unset, each gateway falls back to http://localhost:<port> and logs a warning.
Startup succeeds, which is the trap: the deployment advertises OAuth URLs that only
resolve on the container itself, and the symptom appears later as clients unable to
finish signing in. Set this anywhere but local development.
toolDiscoveryMode decides the shape of the catalogue served to clients. full
offers every tool the caller’s policy allows. search offers two meta-tools instead,
so the model describes the capability it needs in natural language and only matching
tools enter its context - worth reaching for when a large catalogue would otherwise
consume the model’s context window. Policy, inspection, and credential handling are
identical in both: a tool invoked through search re-enters the same per-call path.
toolDiscoveryMaxResults caps how many tools one search returns. Ignored in
full mode.
Client Access
| Setting | Portal label | Default | Accepts |
|---|---|---|---|
browserCorsAllowedOrigins | Browser origins | [] | List of origins |
oauthRegistrationAllowedRedirectHosts | Extra redirect hosts | [] | List of hostnames |
oauthRegistrationMaxClients | Maximum registered clients | 5000 | Integer, 0 disables |
allowAgentTokens | Accept agent tokens | false | true, false |
allowOnBehalfOf | Allow delegation | false | true, false |
trustRequestId | Trust inbound request IDs | false | true, false |
Browser origins
Required only by browser-based MCP clients calling the Gateway cross-origin, such as MCP Inspector. An empty list disables CORS entirely - there is no separate on/off switch, because two fields that can disagree about whether CORS is running is one field too many.
The permitted headers and methods are fixed at what the MCP specification requires and are not settable. Leave the list empty in production unless a browser-based client genuinely needs it.
Extra redirect hosts
MCP clients register themselves, which is what lets someone connect a client you did
not provision. Loopback (127.0.0.1, ::1, localhost) is always accepted and is
what every real client uses - the desktop relay listens on 127.0.0.1, Inspector on
localhost.
Registration is anonymous and the redirect URI is where an authorization code is delivered. Keeping it on loopback means a code obtained by phishing someone lands on that person’s own machine rather than a host an attacker controls. Add an entry only for a client that genuinely cannot use loopback, and name the exact host.
Maximum registered clients
A ceiling on live registrations for the tenant, and the backstop against a
distributed caller that never trips a per-caller rate limit. Registrations expire
with the refresh token lifetime, so this bounds concurrent clients rather than
registrations ever made. Beyond it the endpoint returns 503. 0 removes the limit.
Agent tokens and delegation
allowAgentTokens accepts a Britive-issued credential presented directly as
Authorization: <scheme> <token> with no OAuth flow - an AI identity token, workload
token, or API token. People continue to use the OAuth flow; this is for agents that
cannot perform one. Off by default because it widens what the MCP endpoint accepts;
with it off, an opaque token is refused with “gateway session required”.
The Gateway does not validate these credentials itself. It forwards them, and the platform is the authority: an invalid token fails the first per-user call with 401 or 403 and no tool is served.
allowOnBehalfOf honours an inbound X-On-Behalf-Of: <username> header so an agent
identity can act for a named person. Britive authorises the delegation - the agent
must hold a Delegate relationship to that user, directly or through one of the user’s
tags - and audit events record the person as the actor while additionally
carrying the agent identity and a delegated flag.
allowOnBehalfOf requires allowAgentTokens. Enabling it alone is rejected: the
platform’s delegate check keys off the agent identity, so without an agent credential
the header would be an unauthenticated claim about who the caller is.
Trust inbound request IDs
Use an inbound X-Request-Id for audit correlation instead of generating one. Enable
only behind a proxy you trust to set it - any caller can otherwise choose its own
correlation id.
Sessions & Tokens
| Setting | Portal label | Default | Accepts |
|---|---|---|---|
gatewayAccessTokenTtlSeconds | Access token lifetime | 28800 (8h) | Integer seconds, min 60 |
gatewayRefreshTokenTtlSeconds | Refresh token lifetime | 604800 (7d) | Integer seconds, min 60 |
sessionAuthorizationTtlSeconds | Session re-check interval | 28800 (8h) | Integer seconds, min 60 |
An access token is additionally capped at the expiry of the underlying Britive session when it is minted, then extended on refresh. The refresh lifetime is how long a client can renew without a fresh browser sign-in.
sessionAuthorizationTtlSeconds is how long a validated session is reused before
being re-checked against the platform. Shorter means more platform calls; longer
widens the window in which a session revoked in Britive still works at the Gateway.
Backend Policy
Where the Gateway is willing to send a user’s credentials. A backend’s URL arrives from the platform, and every tool call forwards the caller’s checked-out credential or downstream OAuth bearer to it - so the URL is a destination for other people’s secrets, not just a server address. These settings are applied when a backend is mounted, covering both synced and admin-created backends. A refused backend is never contacted, appears as Refused with a reason, and is recorded in the audit trail.
| Setting | Portal label | Default | Accepts |
|---|---|---|---|
backendRequireHttps | Require HTTPS | true | true, false |
backendAllowPrivateNetworks | Allow private networks | true | true, false |
backendAllowedHosts | Allowed backend hosts | [] | List of hostnames |
backendRequireHttps refuses a backend reachable only over http://, because the
credential crosses that connection in a header. Turn it off for a development stack of
plain-HTTP containers, not for a deployment.
backendAllowPrivateNetworks is on by default, deliberately: reaching backends
inside a network the platform cannot see is a primary purpose of the Gateway, and a
self-hosted database MCP server on an internal address is the normal case. Set it to
false in a deployment whose backends are all public SaaS, where nothing should ever
be pointed at an internal address.
backendAllowedHosts, when non-empty, is an allowlist: any host not named is
refused. Matching is exact or by suffix on a label boundary, so example.com permits
mcp.example.com and never notexample.com. Naming a host also settles the
address-range question for it.
Regardless of these settings, and not configurable: loopback, link-local (including
cloud instance metadata addresses such as 169.254.169.254), multicast, and reserved
ranges are always refused. Allowlisting such a host does not override this, nor
does allowlisting a name that resolves into one of those ranges.
Hostnames are resolved and checked when a backend is mounted and re-verified. A name that resolves to a permitted address now and an internal one later is not caught here
- use outbound network policy for that.
Inspection
Detectors, modes, and rollout are covered in Payload Inspection.
| Setting | Portal label | Default | Accepts |
|---|---|---|---|
inspection.mode | Mode | audit | off, audit, enforce |
inspection.detectors | Detectors | all three | promptInjection, secretScanner, obfuscation |
inspection.urlBlocklistDomains | Blocked domains | [] | List of domains |
inspection.urlBlocklistFeedUrl | Blocklist feed | "" | A URL |
inspection.silentRedact | Redact silently | false | true, false |
inspection.debugCaptureResponses | Capture blocked responses | false | true, false |
inspection.debugResponseMaxChars | (API only) | 10000 | Integer, min 1 |
mode - audit records a verdict and lets the call proceed unchanged; enforce
blocks or redacts matching content; off disables inspection entirely. There is no
separate enabled flag: off is what that flag used to mean.
detectors is the list of built-in detectors that run. Naming a detector turns it
on; an unrecognised name is rejected rather than silently disabling inspection. All
run locally, with no model call and no outbound request.
The URL blocklist detector runs whenever urlBlocklistDomains or
urlBlocklistFeedUrl is non-empty - there is nothing else to enable. The feed is
either a JSON array of strings or one domain per line where # starts a comment. Each
gateway fetches it at startup and again whenever the address changes, not on every
save, so editing other settings does not call out to the feed’s host. A failed fetch
keeps the domains last retrieved and logs a warning, so an outage there cannot quietly
shrink the blocklist.
debugResponseMaxChars caps the captured body when debugCaptureResponses is on.
It is accepted by the settings API but is not shown in the portal form; the default is
suitable for diagnosis.
Audit
What is recorded, retention, and the two optional sinks are covered in Audit & SIEM.
| Setting | Portal label | Default | Accepts |
|---|---|---|---|
audit.categories | Recorded categories | access, auth, admin, catalog | Any of access, auth, admin, catalog, system |
injectUserPrompt | Record the user’s prompt | true | true, false |
injectLlmIntent | Record the model’s reasoning | true | true, false |
injectLlmSessionId | Record the model’s session id | true | true, false |
audit.stdoutFull | Full events to standard output | false | true, false |
audit.retentionDays | Retention | 90 | Integer days, 0 disables the sweep |
audit.platform.enabled | Send events to Britive | true | true, false |
audit.platform.events | Events to forward | see Audit & SIEM | Action names the Gateway emits |
audit.platform.includeContent | Include call content | false | true, false |
The three capture settings
Each of injectUserPrompt, injectLlmIntent, and injectLlmSessionId adds a
required parameter to the tool schema the model sees, so the model supplies the value
on every call. The value is stripped before the call reaches the backend and recorded
in the audit trail. None of them appear in the admin console’s tool list.
| Setting | Parameter | What it captures |
|---|---|---|
injectUserPrompt | _user_prompt | What the person actually asked. |
injectLlmIntent | _llm_intent | Why the model chose this tool and these arguments. |
injectLlmSessionId | _llm_session_id | An identifier the model holds constant for one chat or coding session. |
_llm_session_id is what turns a flat stream of tool calls into chains: every call an
agent made while working on one thing carries the same id, so an investigation can
pull the whole episode rather than one row at a time. The Gateway cannot derive it -
the transport is stateless and one conversation spans many requests. Being a
correlation handle rather than content, it reaches your tenant whether or not content
forwarding is on.
These three are grouped with Audit in the portal but stored at the top level of the settings object, which is where the Gateway reads them. Which group a field is displayed in and which path it is stored at are separate concerns.
Integrations
| Setting | Portal label | Default | Accepts |
|---|---|---|---|
audit.webhook.url | SIEM webhook | "" | A URL. Empty disables the sink |
audit.webhook.headers | SIEM webhook headers | {} | Map of header name to value. Stored encrypted |
audit.webhook.transform | SIEM payload transform | "" | A JMESPath expression |
audit.webhook.verifyTls | Verify the SIEM’s certificate | true | true, false |
metricsEnabled | Serve Prometheus metrics | false | true, false |
metricsToken | Metrics token | "" | A bearer token. Stored encrypted |
audit.webhook.headers and metricsToken are sensitive: they are stored encrypted
and never returned to an administrative reader. The portal shows whether a value is
set, and submitting the masked placeholder leaves the stored value alone - so reading
the settings and writing them back cannot overwrite a secret you were never shown.
The Gateway itself does receive them, because it is the one reader that has to.
metricsEnabled serves Prometheus metrics at GET /metrics. When off, the route
returns 404 rather than 403, so a disabled endpoint does not advertise itself. Labels
are low-cardinality and non-identifying - backend, decision, outcome - never user ids
or tool arguments, which stay in the access-controlled audit log. With metricsToken
set, the endpoint requires it as a bearer token, compared in constant time; leave it
empty only where the port is reachable solely by your collector.
Rate Limits
Two limiters, keyed differently because the abuse shapes differ.
| Setting | Portal label | Default | Accepts |
|---|---|---|---|
toolCallRateLimitBurst | Tool call burst | 30 | Integer, 0 disables |
toolCallRateLimitPerSecond | Tool calls per second | 2.0 | Number |
authRateLimitMaxFailures | Sign-in failures allowed | 10 | Integer, 0 disables |
authRateLimitWindowSeconds | Failure window | 60.0 | Number of seconds |
authRateLimitLockoutSeconds | Lockout | 300.0 | Number of seconds |
oauthRegistrationRateLimitBurst | Registration burst | 5 | Integer, 0 disables |
oauthRegistrationRateLimitPerSecond | Registrations per second | 0.2 | Number |
Tool calls are limited per user, not per address, because many users share an
egress address. Burst is the back-to-back allowance from idle; the rate is sustained
throughput. A rejected call is audited as decision=deny outcome=rate_limited and
returns an error instructing the model to stop retrying.
Sign-in limits count only failures, keyed by client IP, so a caller that is succeeding is never throttled. A successful exchange clears the record.
Client registration is unauthenticated by necessity, and the failure lockout does
not bound it - a well-formed registration is a success, and succeeding is exactly what
someone filling the table would be doing. These two settings bound it instead, with
oauthRegistrationMaxClients as the tenant-wide ceiling.
X-Forwarded-For is not trusted for the sign-in limits, since a caller can set
it. Behind a proxy, apply address-based limits at the proxy.
Counters are held per gateway process, so a pool of N gateways has an effective ceiling of N times these rates. They exist to stop runaway repetition, not to meter usage precisely. For an exact global limit, enforce it at your ingress.
Advanced
Cache lifetimes and timeouts. Each has a correct default and rarely a reason to change, which is why the portal collapses this group.
Synchronisation and verification
| Setting | Portal label | Default | Accepts |
|---|---|---|---|
backendSyncIntervalSeconds | Sync interval | 300 | Integer seconds, min 30 |
backendVerifyTimeoutSeconds | Backend probe timeout | 10.0 | Number of seconds, min 0.1 |
backendVerifyRetryIntervalSeconds | Backend retry interval | 900 | Integer seconds, 0 disables retries |
backendSyncIntervalSeconds governs both backends and these settings: it is the
upper bound on how long any change made on the tenant takes to reach a running
gateway. Sync Now in the admin console does the same pass on demand.
Mounting a backend is an in-memory registration, so a tenant with many MCP servers
becomes serviceable immediately and each backend is probed in the background. The
probe timeout stops one unroutable host from holding a slot until the OS TCP timeout.
A backend left in issue is re-probed after the retry interval, because an issue is a
snapshot of one moment and usually gets fixed; Sync Now ignores the spacing.
Caches
Shorter values mean more platform calls; longer values widen the window in which a change upstream has not yet taken effect here. Each pair is a fresh-hit window and a grace period for serving a stale answer when the platform is unreachable.
| Setting | Portal label | Default | Accepts |
|---|---|---|---|
adminCheckCacheTtlSeconds | Administrator check cache | 300 | Integer seconds |
adminCheckStaleIfErrorSeconds | Administrator check grace | 600 | Integer seconds |
identityCacheTtlSeconds | Identity cache | 300 | Integer seconds |
identityCacheStaleIfErrorSeconds | Identity cache grace | 600 | Integer seconds |
allowedToolsCacheTtlSeconds | Permitted tools cache | 60 | Integer seconds |
allowedToolsCacheStaleIfErrorSeconds | Permitted tools grace | 300 | Integer seconds |
oauthMetadataCacheTtlSeconds | Sign-in metadata cache | 3600 | Integer seconds |
Administrator access is decided by your Britive tenant: a caller holding
mcp.server.manage is an administrator, and there is no local administrator list and
no way to disable the check. Once the grace period lapses on an unreachable platform,
administrator access is refused rather than assumed. Use of a stale answer is recorded
in the audit trail and shown in the admin console.
Tokens and headers
| Setting | Portal label | Default | Accepts |
|---|---|---|---|
gatewayRefreshReuseGraceSeconds | Refresh reuse grace | 30.0 | Number of seconds, 0 revokes on any repeat |
checkoutHeaderMaxValueLength | Maximum credential length | 2048 | Integer characters, min 1 |
hstsMaxAgeSeconds | HSTS max-age | 0 | Integer seconds, 0 disables |
Refresh tokens are single-use: presenting one returns a replacement and retires it,
and presenting a retired one is treated as a compromise, revoking that session and
every token issued from it. The grace window covers a client that legitimately
presents the same token twice - two refreshes in flight, or a retry after a lost
response - by returning the same replacement instead of revoking. 0 revokes on any
repeat, at the cost of occasionally signing users out for their own concurrency.
checkoutHeaderMaxValueLength bounds a single credential value forwarded to a
backend. A longer credential is refused rather than truncated, because a truncated
secret fails at the backend with a misleading error. Raise it for a backend that
legitimately needs larger values, such as a long session token or a PEM-encoded key.
hstsMaxAgeSeconds is sent only on HTTPS responses. It is off by default because the
header is durable in a way that is hard to undo: a browser that receives it refuses
plain HTTP to that host for the full period, so a development stack that emits it by
accident makes its own origin unusable. Set it here or at your TLS edge once the
deployment is permanently HTTPS-only. 31536000 is one year.
All other security headers - Content-Security-Policy, frame and MIME-type
protections, and no-store on responses carrying credentials - are sent
unconditionally and need no configuration.
Audit and inspection timers
| Setting | Portal label | Default | Accepts |
|---|---|---|---|
audit.toolsListHeartbeatSeconds | Catalogue heartbeat | 86400 (24h) | Integer seconds |
audit.retentionSweepIntervalMinutes | Retention sweep | 1440 (24h) | Integer minutes, min 1 |
audit.webhook.timeoutSeconds | SIEM timeout | 5.0 | Number of seconds, min 0.1 |
audit.webhook.maxRetries | SIEM retries | 3 | Integer |
inspection.requestTimeoutMs | Inspection request budget | 250 | Integer milliseconds, min 1 |
inspection.responseTimeoutMs | Inspection response budget | 500 | Integer milliseconds, min 1 |
The catalogue heartbeat is how long an unchanged tool catalogue stays silent before re-affirming itself once, so that no events means nothing changed rather than that recording stopped.
What Is No Longer a Setting
Several values that a configuration file once carried are now fixed. Each either has exactly one correct answer, or bounds an attacker-supplied payload, or sizes an internal batch that no administrator can hold an informed opinion about.
| Fixed value | Why it is not a setting |
|---|---|
| Master secret | Replaced by the pool encryption key (encryptionKey), minted by the platform, delivered only to a gateway, and never shown in any interface. It has no read endpoint: a person who could copy it could forge gateway sessions. Nothing to generate, distribute, or keep in sync across replicas. |
| Platform API base URL | Derived from TENANT, which accepts a full URL. A second way to say where Britive is could only ever contradict the first. |
| Supported auth strategies | Both strategies that exist are the only ones there are. Removing one would silently unmount backends configured that way. |
| CORS allowed headers, methods, and credentials | The header and method lists are exactly what the MCP specification requires. The Gateway authenticates with bearer tokens and never cookies, so credentialed CORS is never needed. |
| Client-registration payload bounds | Body 16 KB, 8 redirect URIs, 512 characters per URI, 16 items per metadata list, 256 characters per metadata string - all far above what a real client sends. They bound an unauthenticated payload rather than expressing a preference. Exceeding one returns 400 naming the field; values are never silently truncated. |
| Per-backend inspection trust | A “trusted” backend meant enforce mode quietly did not enforce. Inspection either runs or it does not, and inspection.mode says which. Every backend is inspected the same way. |
| Custom inspection plugins | Import paths set remotely and run with full gateway privileges is remote code execution by configuration. If custom inspectors return, they will arrive as a signed artifact. |
| Audit JSONL file path | A path inside a container nobody mounts. Postgres, stdout, the webhook, and platform forwarding cover every real destination. |
| Queue sizes, batch sizes, probe concurrency, blocklist fetch timeout | Each sink buffers 10,000 events; retention deletes 10,000 rows per statement; the platform sink batches 50 events with a 5-second flush and a 10-second timeout; 12 backends are probed at once; the blocklist feed fetch times out at 10 seconds. Internal sizing - and both ends of the platform audit sink are Britive’s, so there is nobody for whom a different flush interval is the right answer. |
| Entitlement checkout cache lifetimes | Fixed at 60 seconds for a positive entitlement and 3600 for “no credential providers are bound to this tool”. A positive entitlement is a live decision, so it is re-asked often; the negative one only changes when an administrator binds something, which is a deliberate act rather than something that lapses. Neither is a preference. |