Configuration reference
Configure only the integrations you use. Missing integration settings are reported when one of that integration’s tools is called and do not prevent the server from starting.
Configuration sources
Section titled “Configuration sources”Integration, HTTP-cache, request-timeout, and streaming settings use this priority order (highest first):
- Process environment variables
.envin~/.mcp, beside the global config- Vendor sections in
~/.mcp/configs.json
The .env file is read from ~/.mcp/.env by default. MCP_DOTENV_PATH=<file>
in the process environment reads another file instead, and MCP_DOTENV_CWD=true
reads .env from the server’s working directory. The working directory is not
consulted otherwise: the MCP client chooses it, and a checked-out repository’s
.env could redirect a self-hosted *_URL to a host of its choosing and receive
the operator’s token. Migration: before 0.18.0 the server read .env from
the working directory; set MCP_DOTENV_CWD=true in the client’s launch
environment to keep that, or move the file to ~/.mcp/.env.
The supported global filename is configs.json; ~/.mcp/mcp.json is not read. The global file is watched and reloaded while the server is running. Values in process environment variables and .env are shared by all integrations. Values in the global file are isolated to their vendor section.
The global file is read from ~/.mcp/configs.json by default. MCP_GLOBAL_CONFIG_PATH=<file>
in the process environment reads another file instead — set it when the
server runs under an account with no real $HOME (a systemd DynamicUser, a
nobody-style container user), or to point at a mounted Kubernetes
Secret/ConfigMap at a fixed path rather than relying on HOME to line up
with the mount.
A complete copy-ready example is available at examples/configs.json. Its secret values use the "keychain" sentinel; either store those credentials with mcp-devtools creds set, run mcp-devtools creds migrate on an existing plaintext config, or replace the sentinel with the credential value.
The global file has this shape:
{ "jira": { "environments": { "ATLASSIAN_USER_EMAIL": "you@example.com", "ATLASSIAN_API_TOKEN": "keychain", "ATLASSIAN_SITE_NAME": "mycompany" } }}Recognized section names are bitbucket, jira, confluence, zoom, circleci, slack, postman, edx, newrelic, grafana, sonarqube, teamcity, splunk, ninjaone, wrds, github, gitlab, figma, cloudflare, vercel, sentry, artifactory, snyk, and mend.
Secret references
Section titled “Secret references”Any configuration value, in any of the three sources, may be a reference to a secret instead of the secret itself (enterprise product plan §3.8, retired Enterprise repository):
| Form | Meaning |
|---|---|
keychain or keychain:// |
The OS keychain slot for this key (mcp-devtools creds set). Unchanged from before; read on the request path as it always was. |
file:///path |
The whole file, less one trailing line break. The path is absolute and taken verbatim (no percent-decoding, no file://host/…). |
file:///path#key |
The string under key in a JSON object held in the file. Several references into one file are served from one read, so an email and a token in the same document always change together. |
vault://<mount>/<path>#<key> |
The string under key in the KV v2 secret at <mount>/data/<path> on the HashiCorp Vault / OpenBao server named by MCP_VAULT_ADDR (feature secrets-vault, on by default; the MCP_VAULT_* settings below). The first path segment is the mount; the version is Vault’s own version number. #key is required — a KV secret is a document of keys. Several references into one secret are served from one read. Vault-side setup is in secret-sources-runbook.md. |
awssm://<name-or-arn>[#<key>] |
The SecretString of the AWS Secrets Manager secret (feature secrets-aws, on by default; enabled by MCP_AWS_AUTH and the MCP_AWS_* settings below) — the whole string, or the string under key when it is a JSON object. Only the AWSCURRENT stage is read; the version is its VersionId. A binary secret is refused. Several references into one secret are served from one read. |
azkv://<vault>/<secret-name>[#<key>] |
The current version of the Azure Key Vault secret (feature secrets-azure, on by default; enabled by MCP_AZURE_AUTH and the MCP_AZURE_* settings below). <vault> is the vault’s name, read at https://<vault>.vault.azure.net (MCP_AZURE_KEYVAULT_DNS_SUFFIX for the sovereign clouds); a <vault> containing a dot is used as the host name. The secret name follows Key Vault’s rule (letters, digits, hyphens), which is why it is spelled out rather than derived from the configuration key. The version is Key Vault’s version id. |
A vault://, awssm://, or azkv:// reference whose adapter is compiled in but not configured is refused at startup naming the setting that enables it; one whose adapter is not compiled in (--no-default-features without the feature) is refused as “no adapter compiled into this binary”. Provider-side setup for all three is in secret-sources-runbook.md; the CSI Secrets Store volume plus file:// (deploy/k8s/secrets-csi.yaml) remains supported on every cloud.
On Unix, file:// refuses to read a file that is group- or other-writable (another identity could tamper with the bytes before they are read) and logs one warning per path — not a refusal — for a file that is world-readable, since a plain (non-CSI) Kubernetes Secret volume defaults to mode 0644 unless defaultMode is set. Tighten defaultMode to 0640 or narrower where the platform allows it.
Anything else is a literal, byte for byte, so a configuration with no references behaves exactly as it did.
The MCP server attempts secret resolution before it starts serving. Missing, malformed, empty, or unavailable references in registered outbound vendor credential slots (including their account fields), credential-selection bundles, and NINJAONE_SERVERS / NINJAONE_DB_ENVIRONMENTS degrade secret health without stopping the server. Calls requiring an unresolved credential fail locally; other integrations remain usable. Required server settings, MCP_* provider credentials, and unrecognized reference-bearing settings still refuse startup if they cannot resolve. Invalid provider configuration, policy, inbound authentication, and credential-selection rules remain startup errors.
References are re-read every MCP_SECRET_REFRESH_INTERVAL_SECONDS (default 30, clamped to 1–3600); an upstream 401 triggers one early re-read, at most every 10 s. Each document resolves atomically: a missing field prevents publication of all credential fields from that document. Healthy vendor documents can refresh while another vendor is unavailable. Failed documents keep any last good values; missing documents recover automatically when restored. The health banner reports secret degradation without exposing values or references; details go to the operator log. A document shared with a required server setting remains required. A global-config reload introducing an unresolved reference is still refused, preserving the previous configuration.
Where a referenced value acts as a credential, the audit record and the enterprise-mode result metadata (_meta["mcp-devtools/upstream"]) carry its source (the scheme) and version (the provider’s version, or sha256: plus the first 16 hex characters of the file’s content hash) alongside the credential label; a change of version between two records is the evidence of a rotation. The value itself never appears anywhere.
The one-shot CLI subcommands (mcp-devtools jira|bb|conf …) run the same resolution step before their call: unresolved required server settings refuse startup, and unresolved vendor credentials refuse calls that require them before contacting that vendor. Legacy password and totpSecret fields nested inside NINJAONE_SERVERS entries accept literals, keychain, and secret references resolved into the same startup/refresh snapshot; URLs, prefixes, emails, and commands remain literal administrator configuration. targeted credential bundles support vault references for login and database account fields.
Per-user credential selection
Section titled “Per-user credential selection”MCP_CREDENTIAL_SELECTION contains administrator-managed JSON rules selecting
credentials by verified issuer, tenant, subject, vendor, and environment. Modes
are shared_only, user_then_shared, and user_required. Existing deployments
without rules retain their shared credential behavior. See
per-user credential selection for configuration,
fallback semantics, supported credential sets, and audit evidence.
| Setting | Default | Purpose |
|---|---|---|
MCP_CREDENTIAL_SELECTION |
unset (shared credentials only) | Administrator-managed JSON rules that select a credential by verified issuer, tenant, subject, vendor, and environment, in shared_only, user_then_shared, or user_required mode. See per-user credential selection. |
Native REST integrations
Section titled “Native REST integrations”The eight native adapters are included in both default and headless builds. They
provide read workflows; Artifactory AQL and Mend project summaries use POST for
read queries. No AWS SDK is included; the Playwright runtime is an operator-installed
Node package behind the optional playwright feature (see Playwright browser tools).
| Section | Required when called | Optional settings and defaults |
|---|---|---|
github |
GITHUB_TOKEN |
GITHUB_API_BASE=https://api.github.com; GITHUB_API_VERSION=2022-11-28. For Enterprise Server include /api/v3 in the base. |
gitlab |
GITLAB_TOKEN |
GITLAB_API_BASE=https://gitlab.com/api/v4; self-managed bases preserve context paths and normalize the /api/v4 suffix. |
figma |
FIGMA_TOKEN |
File key or Figma URL comes from tool arguments. API origin is https://api.figma.com. |
cloudflare |
CLOUDFLARE_API_TOKEN |
Scoped bearer token; account and zone IDs are explicit in endpoint paths. |
vercel |
VERCEL_TOKEN |
VERCEL_TEAM_ID; a tool’s teamId overrides it, otherwise personal-account scope. |
sentry |
SENTRY_TOKEN |
SENTRY_URL=https://sentry.io; SENTRY_ORG supplies the default organization slug. |
artifactory |
ARTIFACTORY_URL, ARTIFACTORY_TOKEN |
Set the Artifactory base including any reverse-proxy context. /artifactory is normalized by the adapter. |
snyk |
SNYK_TOKEN |
SNYK_API_BASE=https://api.snyk.io, SNYK_API_VERSION=2024-10-15, SNYK_ORG_ID. Regional API hosts are supported. |
mend |
MEND_API_BASE, MEND_EMAIL, MEND_USER_KEY, MEND_ORG_UUID |
Explicit Platform API 3.0 base for your tenant; no inferred region or legacy API fallback. |
Tokens use the existing secret store (keychain, file://, vault://) and
never become tool arguments. See each tool description for operation-specific
scopes: GitHub repository metadata/contents/PRs/issues/actions read permissions,
GitLab read_api, Figma granular file content/metadata/comments read scopes,
Vercel team access, Sentry organization/project/event read access, JFrog
repository/build read permissions, and Snyk/Mend organization and project access.
Upstream permissions still determine which resources are visible.
Mend logs in with the configured email, user key and organization, then exchanges
the refresh token at POST /api/v3.0/login/accessToken with wss-refresh-token
and orgUuid. The access JWT expires according to tokenTTL (milliseconds,
10-minute fallback). Refresh tokens are renewed conservatively after 25 minutes;
rotated refresh tokens are retained. The cache is isolated by base, email,
organization and credential fingerprint. Auth responses are bounded, excluded
from response caches/artifacts, and never echoed in errors. Single-project and
application-scoped project queries use the organization projects/summaries
endpoint. Optional search/severity/status filters apply locally to each page;
continue using additionalData.cursor even when a filtered page is empty.
GitHub/GitLab paginated tools return {data, nextPage}; jq filters the native
body before wrapping it, so continuation survives projection. GitHub PR diff
pages additionally expose the boolean patchesUnavailable. GitLab preserves
upstream too_large/collapsed flags. Sentry exposes its cursor from Link
headers. Snyk and Mend preserve their native pagination envelopes; use the
cursor arguments rather than asking the server to follow arbitrary next URLs.
Tool selection and session reloads
Section titled “Tool selection and session reloads”MCP_ENABLED_VENDORS accepts auto (default), all, or comma-separated canonical
vendor names/aliases, for example github,gitlab,artifactory. Put this global
setting in the process environment, .env, or the package-wide environment
section. In auto, a nonblank vendor section, vendor-prefixed setting or registered credential setting enables its vendor;
secret references and the keychain sentinel count as configured without
performing authentication during discovery. A configured base URL also counts. Use explicit selection for centrally supplied credentials. Unknown names
do not enable tools. artifact_read always remains available and owner-checked.
The selection is snapshotted at session creation and enforced for both discovery
and calls. Global configuration/credential refresh continues for calls, but
changing the advertised set requires reconnecting. HTTP sessions have separate
inventories and advertise a private, zero-TTL listing cache. all restores the
pre-filter discovery behavior; it does not bypass authorization.
| Setting | Default | Purpose |
|---|---|---|
MCP_ENABLED_VENDORS |
auto |
Which vendors’ tools are advertised and callable: auto (vendors with configuration or credentials), all, or comma-separated canonical vendor names or aliases. Global: set it in the process environment, .env, or the package-wide section. Snapshotted per session; changing it requires reconnecting. |
Download origins, bounds and binary handling
Section titled “Download origins, bounds and binary handling”Ordinary successful API responses default to a 10 MiB encoded and decoded limit.
MCP_API_MAX_RESPONSE_BYTES accepts an integer from 1 through 67108864 (64 MiB).
Set it inside a vendor’s environments object to change only that vendor; a
process environment value takes precedence for all vendors. For example, large
CircleCI test-result pages can use:
{ "circleci": { "environments": { "MCP_API_MAX_RESPONSE_BYTES": "33554432", "MCP_DOWNLOAD_ALLOWED_ORIGINS": "https://circleci.com" } }}Merge these fields into the existing section alongside its credentials. Invalid
limits fail before an upstream request. The limit applies after decompression
as well as to encoded bytes; lowering it prevents reuse of cached responses
fetched under the higher limit. Filtering with jq happens after body retrieval.
Error responses remain capped at 10 MiB. Dedicated streaming log/artifact
downloads use their own limits and are unaffected by this setting.
MCP_DOWNLOAD_ALLOWED_ORIGINS is a comma-separated list of exact HTTP(S) origins,
optionally isolated in a vendor section. For example:
{ "artifactory": { "environments": { "ARTIFACTORY_URL": "https://packages.example.com/artifactory", "ARTIFACTORY_TOKEN": "keychain", "MCP_DOWNLOAD_ALLOWED_ORIGINS": "https://downloads.example.com" } }}Allow the actual trusted object-storage/CDN origin used by your deployment. Wildcards, paths, queries and user information are not origin entries. Absolute signed downloads require an entry even when no redirect occurs. Same-origin API GET redirects need no additional entry; cross-origin redirects do. Each hop is authorized, at most five are followed, loops and HTTPS downgrades fail, and API credentials/custom headers are removed after crossing origins. Non-GET redirects are refused. Signed query values are excluded from download audit records.
This also applies to existing CircleCI signed-log downloads. Configure their trusted storage origins before upgrading. Existing same-origin GET workflows, including Jira attachments, continue through explicit redirects; uploads retain the existing multipart implementation and are never automatically replayed.
When the allowlist blocks a download or redirect, the MCP error and process log
name the exact blocked origin and explain how to append it to the affected
vendor’s environments object in ~/.mcp/configs.json or the process environment.
Verify that you trust that destination before adding it; signed paths and query
strings are omitted from the diagnostic. Preserve existing entries. For CircleCI
log URLs on https://circleci.com, the CircleCI example above enables downloads;
other storage/CDN origins need their own verified entries. The browser console’s
Server configuration page provides the same troubleshooting guidance beside
MCP_DOWNLOAD_ALLOWED_ORIGINS. Environment and .env overrides take priority;
restart/reconnect the MCP server after changing configuration.
| Setting | Default | Purpose |
|---|---|---|
MCP_API_MAX_RESPONSE_BYTES |
10485760 (10 MiB); 1–67108864 |
Encoded and decoded size limit for ordinary successful API responses. Set it in a vendor section to change one vendor; a process environment value applies to all vendors. Invalid values fail before an upstream request. |
MCP_DOWNLOAD_ALLOWED_ORIGINS |
unset (no extra origins) | Comma-separated exact HTTP(S) origins that downloads and cross-origin redirects may reach, optionally isolated in a vendor section. No wildcards, paths, queries, or user information. |
Explicit downloads default to 32 MiB and accept maxBytes up to 512 MiB. The
existing encoded/decoded byte limits, aggregate export budget, idle/total
deadlines, disk reservations, atomic commit, checksum, retention and owner checks
apply. Artifactory validates an upstream X-Checksum-Sha256 when supplied.
Failures produce no complete handle. Successful metadata includes artifactId,
filename, media type, bytes, SHA-256, complete and expiry seconds. Retrieve bytes
with artifact_read or the authenticated HTTP artifact endpoint. No arbitrary
binary body is decoded into text, inline base64, or a preview. Figma exports PNG
only, at most ten nodes, with bounded header/dimension checks (32 million pixels,
32,768 per dimension); missing renders remain explicit entries with complete: false and an error.
GitLab large traces provide bounded text head/tail previews; small traces remain
plain text. The existing streaming storage and retention settings below apply.
Streaming retries honor Retry-After seconds or HTTP dates for supported retryable
statuses, within the same cancellation and total deadline. Missing/invalid values
use bounded equal jitter. Shared non-retrying requests surface parsed timing
metadata without automatically replaying mutations. Error bodies are read with
actual byte/decompression caps rather than trusting Content-Length.
In enforcing deployments the new tools use read_vendor_resource and resource
type vendor_resource, conservatively unscoped. Grant the tool intent (canonical
path /) separately from its outbound paths/origins. match.destination_origin
accepts exact serialized origins (one string or a list). An origin-constrained
rule matches egress, not the origin-less tool intent. Existing rules without the
field keep their semantics. Unknown native API paths remain denied; this release
does not infer repository/project resource IDs for resource-level policy rules.
See implementation status for fixture coverage, compatibility limits and release validation.
Integration settings
Section titled “Integration settings”| Section | Setting | Required/default | Purpose |
|---|---|---|---|
bitbucket |
ATLASSIAN_USER_EMAIL |
Required with API token | Atlassian account email. |
bitbucket |
ATLASSIAN_API_TOKEN |
Required with account email | Atlassian API token; preferred when both Bitbucket credential forms are configured. |
bitbucket |
ATLASSIAN_BITBUCKET_USERNAME |
Alternative | Bitbucket username for app-password authentication. |
bitbucket |
ATLASSIAN_BITBUCKET_APP_PASSWORD |
Alternative | Bitbucket app password. |
bitbucket |
BITBUCKET_DEFAULT_WORKSPACE |
Optional | Default workspace used when a tool does not receive one. |
bitbucket |
BITBUCKET_CLONE_ROOT |
Optional | Absolute directory local stdio or direct CLI bb_clone lands under; HTTP does not expose cloning. A relative target_path resolves against it; an absolute one must lie inside it, checked before anything is created and again on the real path so a symlink cannot lead out. Unset, the tool writes wherever the process user can — the workstation posture; set it wherever a model-driven call could name a path the operator did not choose. |
jira, confluence |
ATLASSIAN_USER_EMAIL |
Required | Atlassian account email. |
jira, confluence |
ATLASSIAN_API_TOKEN |
Required | Atlassian API token. |
jira, confluence |
ATLASSIAN_SITE_NAME |
Required for classic tokens | Site prefix, for example mycompany for mycompany.atlassian.net. Jira and Confluence may share this value. |
jira, confluence |
ATLASSIAN_API_TOKEN_MODE |
classic |
classic uses the site URL; scoped uses https://api.atlassian.com/ex/{product}/{cloudId}. Case-insensitive. Set per vendor. |
jira, confluence |
ATLASSIAN_CLOUD_ID |
Required for scoped tokens | Cloud ID for that vendor. Scoped mode and Cloud ID do not fall back to the other vendor’s section. |
zoom |
ZOOM_ACCOUNT_ID |
Required | Server-to-Server OAuth account ID. |
zoom |
ZOOM_CLIENT_ID |
Required | Server-to-Server OAuth client ID. |
zoom |
ZOOM_CLIENT_SECRET |
Required | Server-to-Server OAuth client secret. |
teamcity |
TEAMCITY_URL |
Required | Server URL including any deployment context path, without /app/rest (e.g. https://ci.example.com/teamcity). |
teamcity |
TEAMCITY_TOKEN |
Required | Personal access token, sent as Authorization: Bearer. Supports the registered-secret/keychain mechanisms. |
circleci |
CIRCLECI_TOKEN |
Required | Personal API token. |
slack |
SLACK_TOKEN |
Required | Bot or user OAuth token. |
postman |
POSTMAN_API_KEY |
Required | Postman API key. |
edx |
EDX_ACCESS_TOKEN |
Required | edX/Open edX bearer token. |
edx |
EDX_API_BASE |
https://courses.edx.org |
LMS base URL for an Open edX instance. |
newrelic |
NEW_RELIC_API_KEY |
Required | New Relic User API key. |
newrelic |
NEW_RELIC_REGION |
us |
Set to eu for EU accounts; other values select the US endpoint. |
newrelic |
NEW_RELIC_API_BASE |
Optional | Explicit NerdGraph base URL; overrides the region. |
grafana |
GRAFANA_URL |
Required | Grafana Cloud or self-hosted base URL. |
grafana |
GRAFANA_TOKEN |
Required | Grafana service-account token. |
sonarqube |
SONARQUBE_URL |
Required | SonarQube base URL, or https://sonarcloud.io. |
sonarqube |
SONARQUBE_TOKEN |
Required | SonarQube/SonarCloud user token. |
splunk |
SPLUNK_URL |
Required | Splunk management API base URL. |
splunk |
SPLUNK_TOKEN |
Required | Splunk authentication token. |
splunk |
SPLUNK_AUTH_SCHEME |
Bearer |
Set to splunk for a legacy Splunk session key. |
ninjaone |
NINJAONE_URL |
Required without server aliases | Base URL for a single server. |
ninjaone |
NINJAONE_SERVERS |
Alternative | JSON object of named server URLs or server objects; see below. |
ninjaone |
NINJAONE_ACCESS_TOKEN |
One authentication form | Public v2 API bearer token. |
ninjaone |
NINJAONE_SESSION_KEY |
One authentication form | Legacy console session-key header. |
ninjaone |
NINJAONE_SESSION_COOKIE |
One authentication form | Console sessionKey cookie. |
ninjaone |
NINJAONE_EMAIL |
Required for login flow | Console account email. |
ninjaone |
NINJAONE_PASSWORD |
Required for login flow | Console account password. |
ninjaone |
NINJAONE_TOTP_COMMAND |
Optional | Command that returns a current MFA code. |
ninjaone |
NINJAONE_TOTP_SECRET |
Optional | Base32 seed or otpauth:// URI used to generate MFA codes. |
wrds |
WRDS_USERNAME |
Required | WRDS username. |
wrds |
WRDS_PASSWORD |
Required | WRDS password. |
wrds |
WRDS_HOST |
wrds-pgdata.wharton.upenn.edu |
PostgreSQL host. |
wrds |
WRDS_PORT |
9737 |
PostgreSQL port. |
wrds |
WRDS_DBNAME |
wrds |
PostgreSQL database. |
wrds |
WRDS_SSLMODE |
require |
require, prefer, or disable. |
playwright |
PLAYWRIGHT_ENABLED |
false |
Runtime opt-in for the playwright_* browser tools (optional playwright Cargo feature). Without it every call refuses and names this setting. With it, the runtime is probed at startup: a missing Node binary or cli.js withdraws the tools and logs why. |
playwright |
PLAYWRIGHT_HTTP_ENABLED |
false |
Advertise and dispatch the browser tools over the HTTP transport. Refuses to turn on, with the reason in the startup log and the health banner, unless PLAYWRIGHT_CONTAINMENT declares how the browser’s network is contained (see Playwright over HTTP). |
playwright |
PLAYWRIGHT_CONTAINMENT |
Unset | proxy: every browser request, loopback included, goes through PLAYWRIGHT_MCP_PROXY_SERVER, which holds the allowlist. The only accepted value; network-policy is refused because a pod network policy cannot block the browser from the pod’s own loopback. Required for PLAYWRIGHT_HTTP_ENABLED. |
playwright |
PLAYWRIGHT_MCP_PROXY_SERVER |
Unset | Forward proxy for the browser, http://host:port, https://host:port or socks5://host:port; no credentials, no path. Passed to Chromium as --proxy-server with the loopback bypass removed. Required when PLAYWRIGHT_CONTAINMENT=proxy; honoured on stdio too when set. |
playwright |
PLAYWRIGHT_HTTP_IDLE_TTL_SECS |
60 |
Seconds a browser opened over HTTP may idle before it is closed (30–3600). Measured from the handle’s last use, never from the HTTP session. |
playwright |
PLAYWRIGHT_CHILD_MEMORY_MAX_BYTES |
Unset | Memory ceiling per browser child (64 MiB–64 GiB; 0 or unset for none). Applied as a cgroup v2 leaf with memory.oom.group where Linux lets the gateway write one (systemd Delegate=memory, a rootless user session, or a container with a writable cgroup namespace); otherwise logged once and the container or sidecar limit is the ceiling. A browser the kernel kills is a dropped handle. |
playwright |
PLAYWRIGHT_CGROUP_ROOT |
The gateway’s own cgroup | Absolute cgroup v2 directory to create the per-child leaves under, when it is not the gateway’s own. |
playwright |
PLAYWRIGHT_MCP_CLI |
Required when enabled | Absolute path to the pinned @playwright/mcp package’s cli.js. The gateway spawns it under Node per session; nothing is downloaded at runtime. |
playwright |
PLAYWRIGHT_NODE_BIN |
node |
Node executable: a bare name resolved on the child’s PATH, or a path. |
playwright |
PLAYWRIGHT_BROWSERS_PATH |
Unset | Browser cache directory passed to the child as PLAYWRIGHT_BROWSERS_PATH. |
playwright |
PLAYWRIGHT_BROWSER |
chromium |
Engine for --browser: chromium, chrome, firefox, webkit, msedge, or chromium-headless-shell. With PLAYWRIGHT_MCP_PROXY_SERVER set (required over HTTP) only the Chromium-based engines are accepted: the loopback and WebRTC containment switches are Chromium’s. |
playwright |
PLAYWRIGHT_NO_SANDBOX |
false |
Pass --no-sandbox for containers without user namespaces. Leave off wherever the sandbox works. |
playwright |
PLAYWRIGHT_OUTPUT_DIR |
System temp directory | Base directory for the per-session output directory the child may write automatically named files under; created owner-only and removed on close. |
playwright |
PLAYWRIGHT_MAX_SESSIONS |
4 |
Open or reserved browsers across the process (1–64). Admission runs before a process is spawned. |
playwright |
PLAYWRIGHT_MAX_SESSIONS_PER_OWNER |
2 |
Open or reserved browsers per principal (1–64). |
playwright |
PLAYWRIGHT_IDLE_TTL_SECS |
300 |
Seconds a browser opened over stdio may idle before it is closed (30–3600). |
playwright |
PLAYWRIGHT_CALL_TIMEOUT_SECS |
60 |
Deadline for one request to the child (5–600); a hung child is killed and its handle dropped. |
playwright |
PLAYWRIGHT_MAX_MESSAGE_BYTES |
16777216 |
Largest line the child may write (64 KiB–256 MiB). Refused at framing, before parsing, and the session is closed. |
playwright |
PLAYWRIGHT_SNAPSHOT_MAX_BYTES |
262144 |
Total bytes of one snapshot or action result returned to the model (4 KiB–16 MiB). A caller’s maxBytes can only lower it. |
playwright |
PLAYWRIGHT_SNAPSHOT_MAX_NODES |
4000 |
Nodes (lines) kept in one snapshot (16–100000). |
playwright |
PLAYWRIGHT_SNAPSHOT_MAX_DEPTH |
32 |
Deepest snapshot level kept (1–256). |
playwright |
PLAYWRIGHT_SCREENSHOT_MAX_BYTES |
8388608 |
Largest screenshot stored as an artifact (64 KiB–64 MiB); checked on the encoded length before decoding. |
aws |
MCP_AWS_AUTH, MCP_AWS_REGION, and the mode’s inputs |
Required | The identity the aws_* tools act as: the same MCP_AWS_* settings as the awssm:// secret source (listed under Secret references below), read from this section or the environment on every call, so a rotated key pair or role is used without a restart. MCP_AWS_SECRET_ACCESS_KEY supports the registered-secret/keychain mechanisms here (principal: the access key id). |
aws |
MCP_AWS_ALLOWED_REGIONS |
Optional | Comma-separated regions a tool’s region argument may select besides MCP_AWS_REGION. Absent means the configured region only; a request for any other region is refused before a request is made. |
aws |
MCP_AWS_ENDPOINT_URL |
Optional (AWS_ENDPOINT_URL accepted) |
One endpoint for every service in place of the regional defaults (LocalStack, a single VPC endpoint). The service root only; https, or http on loopback. |
NINJAONE_SERVERS is itself a JSON string when placed in .env or the environments object. Each alias can be a URL or an object with required url and optional prefix, email, password, totpCommand, and totpSecret fields:
NINJAONE_SERVERS={"production":{"url":"https://app.ninjarmm.com","email":"you@example.com","password":"keychain","totpSecret":"keychain"},"sandbox":"https://sandbox.example.com"}Shared request, cache, and streaming settings
Section titled “Shared request, cache, and streaming settings”These settings use the same three-source cascade. When placed in configs.json, define a setting in only one vendor section or give it the same value in every section that defines it; conflicting values are intentionally treated as ambiguous.
| Setting | Default/valid range | Purpose |
|---|---|---|
ATLASSIAN_REQUEST_TIMEOUT |
30000 ms; positive integer |
Shared upstream HTTP request timeout. Despite the historical name, it applies to the shared HTTP transport. |
HTTP_CACHE_ENABLED |
false |
Enables the bounded in-process cache for successful upstream reads. |
HTTP_CACHE_EXPLORATION_ENABLED |
false |
Samples normal TTL (75%), half TTL (12.5%), or reject (12.5%) for eligible admissions. Can increase upstream traffic; see cache training data. |
HTTP_CACHE_DEFAULT_TTL_SECONDS |
60; positive integer |
TTL used when the upstream response provides no usable cache lifetime. |
HTTP_CACHE_MAX_TTL_SECONDS |
3600; positive integer |
Maximum admitted TTL. |
HTTP_CACHE_MAX_ENTRIES |
512; positive integer |
Maximum cached response count. |
HTTP_CACHE_MAX_BYTES |
67108864 (64 MiB); positive integer |
Maximum stored cache bytes. |
HTTP_CACHE_COMPRESSION_THRESHOLD_BYTES |
16384 (16 KiB); positive integer |
Minimum response size considered for compression. |
HTTP_ALLOW_PLAINTEXT |
false |
Vendor base URLs (TEAMCITY_URL, GRAFANA_URL, …) must be https, or http on loopback; the transport refuses anything else before a token is sent. true lifts that check, globally in the shared section or environment, or for one vendor when set in that vendor’s section. Warning: every request to that vendor then carries its credential in clear; lab environments only. |
HTTP_RAW_RESPONSE_PERSIST |
false |
When true, every successful JSON API response (URL, method, request body, response body) is written to a per-process 0700 directory under the system temp dir for offline debugging, and truncated tool output names that file. Off by default because the files contain vendor data. |
MCP_STRUCTURED_OUTPUT |
false |
When true, the tools listed in code mode advertise outputSchema and return structuredContent alongside their unchanged text. Off, the tool surface and every result are byte-identical to a server without the feature. |
MCP_STRUCTURED_MAX_BYTES |
262144; clamped to 16384–16777216 |
Upper bound on one serialized structuredContent value. A larger value has its data replaced by null plus an incomplete object, and the full JSON is saved as an owner-scoped artifact readable with artifact_read. |
STREAMING_PARTITION_CONCURRENCY |
4; 1–16 |
Maximum concurrent time-partition requests. |
STREAMING_ARTIFACT_RETENTION_SECONDS |
3600; clamped to 300–604800 |
How long completed download artifacts remain readable. |
STREAMING_ARTIFACT_SWEEP_INTERVAL_SECONDS |
60; clamped to 5–3600 |
Interval between expired-artifact sweeps. |
HTTP_REQUEST_BODY_LIMIT_BYTES |
1000000 (1 MB); clamped to 1000000–536870912 |
Largest request body accepted on /mcp over streamable HTTP. Raise it so upload tools can receive larger inline (base64) files from remote clients; the cap never drops below the 1 MB default. Stdio is unaffected. With a WAF, also raise its inspection limit (waf.requestBodyLimitBytes in Helm; both directives in deploy/waf/body-limit.conf for Compose/k8s), nginx client_max_body_size, and any ingress cap. |
UPLOAD_MAX_FILE_BYTES |
10485760 (10 MiB); positive integer, capped at 256 MiB |
Largest single file an upload tool (bb_upload, jira_upload_attachment) accepts, measured after base64 decoding. May be set in one vendor section to tighten it for that vendor only. |
UPLOAD_MAX_TOTAL_BYTES |
26214400 (25 MiB); positive integer, capped at 256 MiB |
Largest decoded total across every file in one upload call. Same vendor-section override as above. |
Upload tools receive file contents inline (base64) or by server-side artifact id; they never read client-local paths. Over streamable HTTP the whole tools/call request must also fit the /mcp request body cap (HTTP_REQUEST_BODY_LIMIT_BYTES, 1 MB by default), so inline uploads there are bounded to roughly three quarters of that cap in decoded bytes; raise the cap, use artifactId, or use stdio for larger files.
Canonical outbound URLs
Section titled “Canonical outbound URLs”The shared API transport sends one canonical URL form in every authentication mode, including local mode. This is the supported behavior, so policy evaluates the same target that the transport sends:
- Path dot-segments are resolved and duplicate slashes collapse. A trailing slash is preserved, normalized to at most one.
- Unreserved path escapes are decoded (
%2Dbecomes-). Reserved escapes remain encoded with uppercase hex (%2fbecomes%2F). - Query keys are sorted stably; repeated keys retain their relative order.
Keys and values are decoded once and re-encoded as
application/x-www-form-urlencoded: spaces (including input+or%20) become+, percent-encoded plus signs remain%2B, and~becomes%7E.
For example, /a//b/../x%2Dy?z=1&a=p%20q&a=%7E is sent as
/a/x-y?a=p+q&a=%7E&z=1. Caller-supplied URL spelling is not preserved.
tests/transport_tests.rs::wire_bytes_are_the_canonical_form locks the shared
transport’s encoding; it does not establish every vendor’s query semantics.
Vendor-specific transports, such as AWS request signing, retain their own
encoding contracts.
Enterprise mode (opt-in)
Section titled “Enterprise mode (opt-in)”Everything in this section is off by default. With it off, unset and an
explicit off are identical to each other in protocol behaviour and tool
surface (tests/auth_mode_tests.rs locks that). Outbound URL behavior follows
canonical outbound URLs above, and the journal,
when configured, carries checkpoint records.
It is the configuration surface of the enterprise plan
(enterprise product plan, Phases A–C). Startup is
fail-closed: an incomplete or contradictory enterprise configuration
refuses to start with a reason, never falls back to local mode.
Unless noted, these settings use the same three-source cascade as the
integration settings, so they may live in the environment, .env, or a
configs.json section.
| Setting | Default | Purpose |
|---|---|---|
MCP_ROLE |
all |
Process env only (or serve --role). all, gateway, or control (plan ADR-010). control serves the health banner and runs the control plane’s background work — audit forwarding (MCP_AUDIT_FORWARD_URL, below); gateway never forwards. |
MCP_AUTH_MODE |
off |
off, oidc, or okta. oidc validates inbound bearer tokens against an OpenID Connect provider chosen by MCP_OIDC_PROFILE; it requires an HTTP transport, a non-loopback-safe configuration below, a policy, and a journal. okta is an alias of oidc with the Okta profile implied, reading the same settings under the MCP_OKTA_* names it has always used — a deployment configured before profiles existed keeps working unchanged. Any other value is refused. |
MCP_BIND_ADDR |
127.0.0.1 |
Process env only. IP or ip:port. A non-loopback bind is refused unless inbound authentication is on (oidc / okta). |
MCP_PUBLIC_URL |
— (required in enterprise mode) | The URL clients reach the server at through the ingress: the RFC 9728 resource identifier and the base of the resource_metadata URL in every WWW-Authenticate challenge. Must be https (or http on loopback). Its origin is also accepted from browsers (see MCP_ALLOWED_ORIGINS). |
MCP_ALLOWED_ORIGINS |
— | Comma-separated exact origins (scheme://host[:port], https or http on loopback) that browser-hosted MCP clients are served from. The transport refuses every browser request whose Origin is not loopback, the MCP_PUBLIC_URL origin, or one of these — that refusal is the DNS-rebinding guard, so list only the origins you serve clients from. A malformed entry refuses startup. Requests without an Origin (every non-browser client) are unaffected. |
MCP_OIDC_PROFILE |
— (required for oidc) |
okta, entra, keycloak, auth0, or generic. A profile is a set of defaults — where the signing keys are (MCP_OIDC_JWKS_URL), which claim is the subject (MCP_OIDC_SUBJECT_CLAIM), which claim carries groups (MCP_OIDC_GROUPS_CLAIM) — plus the provider’s claim-shape corrections, all over the one validator. Each is described in identity-provider-runbook.md. Not read in okta mode (the profile is Okta). |
MCP_OIDC_ISSUER / MCP_OKTA_ISSUER |
— (required in enterprise mode) | The provider’s issuer URL; tokens must carry it as iss, with or without a trailing slash (Auth0 and Entra v1 issuers end in one). Must be https (or http on loopback). One key family per deployment: setting a MCP_OKTA_* key in oidc mode, or a MCP_OIDC_* key in okta mode, is refused at startup rather than resolved by precedence. |
MCP_OIDC_AUDIENCE / MCP_OKTA_AUDIENCE |
— (required in enterprise mode) | The audience tokens must carry in aud (a string, or one member of an array). Keycloak emits no aud at all until the client has an audience mapper; the token is refused, not the check relaxed. |
MCP_OIDC_JWKS_URL / MCP_OKTA_JWKS_URL |
per profile | Where signing keys are fetched. Okta: {issuer}/v1/keys; Keycloak: {issuer}/protocol/openid-connect/certs; Auth0: {issuer}/.well-known/jwks.json; Entra and generic: the jwks_uri of {issuer}/.well-known/openid-configuration, resolved on every fetch, which must name the configured issuer and must itself be https (or http on loopback). Keys are cached, refreshed in the background every 10 minutes, and refetched (rate-limited to one per 30 s) when a token names an unknown kid. Only RSA signing keys with a kid are admitted (use must be sig and alg must be RS256 when present); two admissible keys under one kid admit neither. The document is limited to 256 KiB (discovery: 64 KiB), enforced while it is read. A key that disappears from the set, or is republished under the same kid with different material, evicts the tokens it validated from the token cache. |
MCP_OIDC_SUBJECT_CLAIM |
per profile: oid for Entra, otherwise sub |
The claim that becomes the principal’s subject — what policy subjects, the revocation list, session binding, and every audit record key on. Entra’s sub is pairwise per application, so it is not a stable identity across apps; oid is. okta mode has no such key (the subject is sub). |
MCP_OIDC_MACHINE_CLAIM / MCP_OIDC_HUMAN_CLAIM |
per profile | oidc mode only. A claim that marks a token as issued to a machine (a service account, a CI pipeline) or to a person, as name (present and not null) or name=value (that exact string, or true/false); name may be a dotted path like the groups claim. These add to the markers each profile already reads — Okta’s uid/cid, Entra’s idtyp, Auth0’s gty, Keycloak’s service-account mappers; the generic profile reads none — and any machine reading, the profile’s or the operator’s, outranks a human marker. A subject-name convention is never a marker. What the kind changes is MCP_ADMIN_PRINCIPALS below; see service accounts. |
MCP_OIDC_JWKS_FILE |
— | OIDC mode only; mutually exclusive with MCP_OIDC_JWKS_URL. Service-readable regular JWKS file (256 KiB maximum), read before every authentication and installed only when content changes. File failures clear signing keys and validated tokens and refuse authentication; no remote fallback. Rotation/withdrawal uses the same admission and cache invalidation as remote JWKS. Mount its parent directory for atomic replacement. See air-gap operations. |
MCP_OIDC_GROUPS_CLAIM / MCP_OKTA_GROUPS_CLAIM |
per profile: groups, none for Auth0 |
The claim holding group memberships: a claim name, or a dotted path into nested objects (realm_access.roles); a literal name wins over a path, so Auth0’s namespaced https://example.com/groups is looked up as written. Non-string members are ignored. Keycloak’s /-prefixed group paths lose the leading slash. When the claim is configured but absent and the token says the groups were too many to include (Entra’s _claim_names / _claim_sources, or hasgroups), the token is refused as groups_overage rather than read as “no groups”. |
MCP_OIDC_CLOCK_SKEW_SECONDS / MCP_OKTA_CLOCK_SKEW_SECONDS |
60; at most 300 |
Leeway applied to exp, nbf, and a future iat. |
MCP_REQUIRED_SCOPE |
mcp:tools |
The scope a token must carry to reach /mcp; missing it is a 403 insufficient_scope. |
MCP_TENANT |
issuer host | Tenant label stamped on every principal, audit record, and artifact owner. |
MCP_POLICY_FILE |
— (required in enterprise mode) | Policy document. A file whose name ends in .cedar is a native Cedar policy set, evaluated as written under MCP_POLICY_ENGINE=cedar and refused under any other engine (native Cedar policies); the rest of this row applies to it unchanged. Any other name is a YAML policy document (src/policy/engine.rs documents the schema; tests/fixtures/policy/phase-a.yaml is a worked example). Compiled at startup and hot-reloaded on change; a document that does not compile is refused at startup and ignored on reload. A file that vanishes or becomes unreadable is treated the same way: the last good policy stays in force, the health banner reports that reloads are failing (the reason itself goes to the operator log, not the public banner) (deleting the file is not an emergency deny — publish rules: [] to deny everything). Validate with mcp-devtools policy check <file>; ask why a call is allowed or denied with mcp-devtools policy explain <file> --tool … --arguments … --group …, which builds the call exactly as the gateway would — same extractor, same upstream identity from the same configuration cascade (MCP_VENDOR_ENVIRONMENT, the credential slot) — and lists the first key every rule fails on; --environment overrides the configured environment for a what-if, --client-name/--client-version supply the reporting client. Schema 3 (schema_version: 3) adds classes and content rules: the content guard inspects decoded text (runtime security plan §4–§5, §7) and denies, holds, redacts or labels on a finding; detection is probabilistic and the request-side rules remain the provable boundary. Rules name one or more subjects: write_argument (a non-read call’s arguments before dispatch; deny, hold or label), tool_result (every call’s result, text blocks and structuredContent together, before the client, the task record or any artifact sees it; deny, redact or label), prompt (a model request body before forwarding; deny or label, plus binary_blocks: deny to refuse undecoded base64 blocks) and completion (a model response; deny or label, with mode: stream to terminate on a finding or mode: buffer to hold the whole response and release all or nothing). detect names detector classes (secret) for the built-in detectors; engine: webhook sends the subject to MCP_GUARD_WEBHOOK_URL instead and may name any class. A prompt rule may also hold: the request waits for a second person under the call-approval gate, with the body digest and the findings digest in the hold key. Anything a subject cannot do (redact on a prompt, hold on a result or a completion, mode outside a completion rule) is refused at load rather than ignored. The models section decides inference requests. flows rules (runtime security plan §8) deny a call to named vendors, or a prompt, once the caller’s owner has read every class in when.owner_has (an environment class of an upstream the owner acted on, or a class a tool_result or completion rule labelled), unless the principal is in a named group; the marks live in the per-process taint store (MCP_TAINT_MAX_OWNERS, MCP_TAINT_TTL_SECONDS), keyed by the verified owner and never by session. budgets reserve at admission and settle at outcome: each names a window (8h, 30m, 2d), a scope (owner or tenant), at least one of tokens and tool_calls, an action (deny, or hold for the approval gate), unknown_usage (deny, estimate or ignore, for an OpenAI-compatible stream that did not ask for usage) and estimate.completion_tokens for a body with no max_tokens; spend_usd is refused because this build prices nothing. With flows or budgets in force the gateway runs as one replica (decision 1). policy explain … --content shows which content rules cover a call and what the built-in detectors find in --arguments; --flow (with --owner-class) which flow rule would deny the call’s vendor or a prompt; --model (with --provider, --model-name) the models decision; --budget (with --estimate-tokens) the budgets and whether an estimate fits an empty window: each from the bundle alone, with no secret resolution, credential selection or connection. Setting it with MCP_AUTH_MODE=off enforces the policy against the local principal — a dry run. |
MCP_POLICY_ENGINE |
file |
Decision engine over MCP_POLICY_FILE: file or cedar (case-insensitive; blank means file). Cedar requires --features cedar and a policy file; invalid configuration refuses startup, including injected-policy builds. A .cedar policy file is a native Cedar document: cedar evaluates it as written, with no YAML translation, and file refuses to start with one (native Cedar policies). For a YAML policy file, selecting Cedar translates and strictly validates the snapshot before serving and validates each reload/admin candidate; the rest of this row describes that translation path. File-only loads do not invoke Cedar. Signed YAML, signatures, reloads, admin operations, and audit versions remain shared. Compare decisions from one snapshot with policy explain <file> --engine cedar --tool …; the file trace remains, with a Cedar effect, rule id, and agreement flag. policy check checks YAML without implying a Cedar translation. The published image omits Cedar; build a custom image with docker build --build-arg POLICY_ENGINE_FEATURES=cedar -t mcp-devtools:cedar . and select that image in the deployment. The unoptimised Cedar baseline was 1.225 ms at 500 rules on the Linux record host; scalar filtering sends 125 candidates to Cedar, which measured 427 µs there on 2026-09-24 and 82.5 µs after PR #54 (68 µs, 57,448 bytes, 270 allocations on an Apple M1, 2026-10-02). A native .cedar document of 500 policies measures about 5 µs per decision on one resource (native benchmark). The file engine’s 20 µs budget does not apply to Cedar; see the Cedar benchmark for the separate per-rule ceiling and measured results. |
MCP_POLICY_DECISION_CACHE |
off |
on wraps the enforcing engine (file or cedar) in a bounded, in-process decision cache (docs/policy-decision-cache-design.md, issue #65). Measured (docs/benchmarks/2026-09-28-policy-decision-cache.txt): a hit costs about 3 µs on x86 hosts without SHA-256 extensions and about 1 µs on hosts with them, against 80 µs for an uncached Cedar decision at 125 candidates and 13–15 µs for the file engine; a miss adds 4 µs (Linux) or 1 µs (Apple M1) to the uncached cost. It pays for the Cedar engine at a 5 % hit rate (Linux) and for the file engine only above 24 %, which is why the file engine stays off; the Cedar default is decided in its own PR. A cache hit is the decision the current policy snapshot gives; policy changes take effect on the next call, because every entry is bound to the snapshot generation that computed it and a reload, rollback, or admin publication starts a new generation. Keys on a SHA-256 fingerprint of every authorization input (never the reported client name); stores only the decision (effect, rule id, version, reason). Evaluation failures are never cached and a degraded engine bypasses the cache. Never wraps the local-mode AllowAll decision point. The health banner appends policy decision cache enabled; GET /metrics gains mcp_policy_decision_cache_{hits,misses,inserts,evictions,bypasses,generation_resets}_total and mcp_policy_decision_cache_entries. Any value other than off/on (case-insensitive; blank means off) refuses startup. |
MCP_POLICY_DECISION_CACHE_ENTRIES |
4096; 1–1048576 |
Most decisions the cache holds (about 32 bytes of key plus the decision’s three strings each; 4096 is well under 2 MiB). Oldest-inserted entries are evicted first. Validated at startup whether or not the cache is on. |
MCP_POLICY_DECISION_CACHE_TTL_SECS |
300; 0 misses every call |
How long a cached decision may be served. Defense in depth for anything outside the policy snapshot, not the invalidation mechanism (that is the snapshot generation). Validated at startup whether or not the cache is on. |
MCP_GUARD_MAX_SUBJECT_BYTES |
4194304; 1024–67108864 |
Most decoded bytes (the sum of every string, object keys included) the content guard inspects in one subject: a write call’s arguments, a tool result’s text blocks and structuredContent together, a prompt’s strings, or a completion held in buffer mode (the same bound caps that buffer). A larger subject is not inspected: the content rule’s on_error applies (deny unless the rule says label) and a content_finding record names the cause subject_too_large; a buffered completion past the bound is refused regardless, since its bytes were not kept. Bounds the guard’s CPU time and memory per call to a size the operator chose. Out-of-range values are clamped and an unparseable value is the default. Read at startup; consulted only for calls a schema 3 content rule covers. |
MCP_GUARD_STREAM_LOOKBACK_BYTES |
4096; 256–65536 |
Decoded completion text a completion rule in stream mode keeps ahead of each new delta, so a credential split across two deltas, or JSON-escaped across them, is whole in the window the detectors see (runtime security plan §7 step 1). The window never exceeds this plus one event. Out-of-range values are clamped. Read at startup with the inference settings. |
MCP_GUARD_WEBHOOK_URL |
unset (no external engine) | Where content rules with engine: webhook send a subject: an https URL (or http on loopback) with no credentials or fragment. The request is {subject_kind, bytes_b64, classes, tenant} with the decoded strings joined by newlines and base64-encoded; the answer {action, findings: [{detector, class, count}]} is applied under the rule’s own action, so an engine cannot escalate a label rule or soften a deny. Requires MCP_GUARD_WEBHOOK_TOKEN; a URL without a token, or a token without a URL, refuses startup. Without it a rule naming the engine gets guard_unavailable and its on_error applies. Each call is on the outcome’s egress record (vendor: guard). The additive x-mcp-guard-complete-subject header distinguishes whole subjects from streamed lookback windows. The optional local classifier requires complete subjects; use buffered completions. Its model/runtime/cache settings belong to the companion service, not gateway environment variables. |
MCP_GUARD_WEBHOOK_TOKEN |
unset (required with the URL) | The bearer the engine expects: a literal, keychain, or a secret reference (vault://, awssm://, azkv://, file://), resolved per call like a vendor credential and never logged or journaled. |
MCP_TAINT_MAX_OWNERS |
10000; 1–1000000 |
The most owners (issuer, tenant, subject) the per-process taint store tracks at once (runtime security plan §8 step 1, decision 7). At the cap an insert first drops owners whose every mark has expired; when nothing can be dropped a call that would mark a new owner is refused and nothing is tracked (fail closed, like the rate limiter). A tracked owner’s marks never fail for lack of room. Out-of-range values are clamped. Read at startup under an enforcing policy; local mode tracks nothing. |
MCP_TAINT_TTL_SECONDS |
86400; 1–2592000 |
How long a taint mark lives: a class an owner read (the upstream’s environment class, or a class a content rule labelled on a result or completion) stays an input to flows rules for this long after the last call that touched it, across sessions, tokens and run ids; nothing an owner sends clears it, and revocation leaves it in place. Out-of-range values are clamped. |
MCP_GUARD_WEBHOOK_TIMEOUT_MS |
2000; 100–30000 |
Longest wait for the engine’s answer. A timeout, a transport error, a non-2xx status or an unreadable answer is an inspection failure (engine_unavailable or engine_malformed_answer in the content_finding record), and the rule’s on_error decides: deny by default. The answer body is read up to 64 KiB. Out-of-range values are clamped. |
MCP_POLICY_PUBLIC_KEY |
— (required in enterprise mode) | Base64 Ed25519 public key. When set, MCP_POLICY_FILE (and MCP_REVOCATION_FILE, and the MCP_AUTHORIZATION_GENERATION_FILE manifest) must carry a verified detached signature in <file>.sig, produced by mcp-devtools policy sign <file> --key <keyfile> (or generation init|seal, or revoke) with the private key from mcp-devtools policy keygen. A document whose signature is missing or does not verify is refused at startup and ignored on reload (last good policy in force, health degraded). Every load, change, and rejection is a control record in the audit journal; a change is journaled before it takes effect and is not applied if the record cannot be made durable. |
MCP_AUTHORIZATION_GENERATION_FILE |
— (required in enterprise mode) | The signed authorization generation manifest (src/policy/generation.rs, CF-24): a generation number and the SHA-256 of the exact bytes of MCP_POLICY_FILE and MCP_REVOCATION_FILE, signed with the policy key in <file>.sig. The policy and the revocation list are in force only as the pair a manifest binds: a document changed on disk without a newer manifest is not applied (health degraded, one authorization_generation_rejected record), a manifest whose generation is not newer than the one in force is refused (an older signed pair cannot be replayed), and a newer one puts both documents in force together after its authorization_generation_changed record is durable. Requires MCP_POLICY_FILE, MCP_REVOCATION_FILE, and MCP_POLICY_PUBLIC_KEY; a manifest that does not bind the files on disk refuses startup. Create generation 1 with mcp-devtools generation init; advance it with generation seal (after policy sign), revoke … --generation, or PUT /admin/generation. Every decision record carries the generation it was taken under. Writers serialize on <file>.lock. See docs/revocation-runbook.md and docs/admin-api-runbook.md#authorization-generations. |
MCP_REVOCATION_FILE |
— (required in enterprise mode) | The revocation list (src/auth/revocation.rs): revoked subjects, revoked token ids (jti), and a not_before cut-off that refuses every token issued before it (revoke all). Signed with the same key as the policy and hot-reloaded the same way; enforced on every request after token validation, regardless of the validated-token cache, so a change takes effect within one poll (≤ 500 ms). On every change the token cache is cleared and the revoked subjects’ sessions are closed (all principal sessions when the cut-off moves). Edit with mcp-devtools revoke init|subject|token|all|remove|clear-all|show; every refusal is a revoked_token_rejected record in the journal. See docs/revocation-runbook.md. |
MCP_WRITE_MAX_TOKEN_AGE_SECONDS |
300; 0 disables |
Plan §3.6 “high-risk operations”: a non-read tool call is refused unless the token was issued (iat) within this many seconds — a token with no iat is refused for writes. Reads are unaffected. Applies only when inbound authentication is required. The same age bound also gates /admin/proposals/{id}/approve, including console approvals. |
MCP_CONSOLE_CLIENT_ID |
unset | Public OIDC PKCE client id; mounts /console only in a console feature build with OIDC authentication on all/control. No client secret. D.3 authoring and proposal decisions use the same authenticated session and configured approval gate; there is no separate console write bypass. See console runbook. |
MCP_CONSOLE_REDIRECT_PATH |
/console/callback |
Absolute callback path, without query, fragment, wildcard or route parameters. Register MCP_PUBLIC_URL (no trailing slash) plus this path at the IdP; default used when blank. |
MCP_CONSOLE_SCOPES |
openid mcp:admin |
Space-separated authorization scopes; default used when blank. Entra needs the resource-prefixed scope, e.g. openid api://<resource-app-id>/mcp:admin; see provider registration. The access token must still grant mcp:admin. |
MCP_ADMIN_APPROVALS |
off |
Which admission gate every /admin/* mutation consults before it appends its durable admin_mutation intent (plan §3.10.1, ports::MutationGate). off, or unset, is the direct gate: every mutation is admitted and behaves exactly as it did before the gate existed. required is two-person approval (plan §3.10.3): policy install and deny-list replacement are journaled as proposals and answered 202 until a different administrator of the same tenant approves them with a fresh token; reload, session revoke, and artifact purge stay direct. Requires MCP_AUDIT_JOURNAL_DIR (proposals are journal records; pending ones are rebuilt from it at startup) and refuses startup without it. Any other value is refused at startup. Console signed uploads follow this setting too: off installs immediately, required creates a pending proposal. Shapes, records, and errors are in the admin API runbook. |
MCP_ADMIN_APPROVAL_TTL_SECONDS |
86400; 1–2592000 |
How long a proposal stays approvable under MCP_ADMIN_APPROVALS=required. A proposal past it reads as expired, and the first decision asked of it journals an admin_rejection with reason expired. |
MCP_ADMIN_PRINCIPALS |
human-or-unknown |
Which kinds of principal /admin/* admits, over and above mcp:admin (CF-42, AE-1). The kind is read from validated claims per profile (MCP_OIDC_MACHINE_CLAIM above). human-or-unknown refuses a token the provider positively marks as a machine’s (403 machine_principal) and admits everything else, so a deployment whose tokens carry no marker is unchanged. human admits only tokens positively marked as a person’s (403 unverified_principal_kind otherwise) — right where the profile marks both directions (Okta; Entra with idtyp on user tokens), and locks every administrator out, the console included, where it cannot. any admits machines and is refused at startup together with MCP_ADMIN_APPROVALS=required or MCP_CALL_APPROVALS=required: the self-approval rule compares subjects, not kinds, so two service accounts would satisfy it with nobody in the loop. The MCP boundary is unaffected: a service account’s tool calls are governed by policy, not by this. A profile that cannot see a machine (generic without a marker) logs a warning at startup. |
MCP_CALL_APPROVALS |
off |
Which gate a tool call goes to when a policy rule returns require_approval (issue #41, ports::CallApprovalGate, ADR). off, or unset, is the no-backend gate: every held call is refused with _meta.code: approval_required and the rule’s reason, and nothing is sent upstream. required records the held call as a proposal keyed by its hold key (tenant, subject, upstream identity including its environment class, tool, canonical arguments), a different person in the same tenant approves it, and the requester re-issues the same call, which passes admission again and consumes the approval exactly once. The call always executes under the requester’s own token and credentials, never on the server’s initiative. Requires MCP_AUDIT_JOURNAL_DIR. Any other value is refused at startup. Records, states, and refusal codes are in the admin API runbook. |
MCP_CALL_APPROVAL_TTL_SECONDS |
900; 1–86400 |
How long a tool-call proposal stays approvable, and how long an approval stays consumable, under MCP_CALL_APPROVALS=required. Shorter than the admin TTL because vendor state drifts. Expiry is observed, not swept: a proposal past it reads as expired, the first decision asked of it journals a call_rejection with reason expired, and a re-issue opens a fresh proposal. Validated whenever set, whether approvals are enabled or disabled. |
MCP_CALL_APPROVAL_MAX_PENDING |
100; 1–100000 |
Per-tenant cap on pending tool-call proposals. Beyond it a newly held call is refused approval_backlog; re-issuing an already pending call is not a new proposal and is not counted against it. The pending set is a per-process projection of the journal (see the scaling design). |
MCP_VENDOR_ENVIRONMENT |
unclassified | prod, staging, qa, or dev. Vendor-scoped: put it in a vendor’s configs.json section to classify that account. An unclassified environment matches no environment-scoped rule. |
MCP_AUDIT_JOURNAL_DIR |
— (required in enterprise mode) | Directory for the durable, sequence-numbered audit journal. Every tool call’s intent is written and synced before dispatch; a journal that cannot accept a record refuses the call, and the health banner answers 503. Direct vendor CLI subcommands (jira, bb, conf) use the same journal and admission boundary; give them a separate journal directory if a running server already holds this one. The writer holds an exclusive lock on an empty audit-journal.lock sidecar in the same directory (never on the journal itself, so readers — the SIEM shipper, audit verify, the evidence download — are never blocked, Windows included); a second writer on the same directory refuses to start. |
MCP_AUDIT_SIGNING_KEY |
— (required in enterprise mode) | Path to the gateway’s Ed25519 PKCS#8 key from mcp-devtools audit keygen. The file must be readable by nobody but its owner and, at most, its group (0600, 0640, 0440); a Kubernetes Secret projected under fsGroup arrives as root:<fsGroup> 0440 and is accepted, anything world-readable or group-writable is refused at startup. Signs every journal checkpoint. Keep the printed public key for mcp-devtools audit verify --public-key. |
MCP_AUDIT_CHECKPOINT_RECORDS |
256 |
Records between checkpoints. Every line extends a SHA-256 chain; a checkpoint records the chain value over everything before it, signed. Also written when this many seconds pass (MCP_AUDIT_CHECKPOINT_SECONDS) with unsealed records, and at every clean shutdown. |
MCP_AUDIT_CHECKPOINT_SECONDS |
60 |
Longest a written record stays unsealed by a checkpoint. |
MCP_AUDIT_CHECKPOINT_EXPORT_DIR |
— | Directory each checkpoint is copied to (checkpoint-<seq>.jsonl, atomically written), for shipping to object storage or a SIEM. mcp-devtools audit verify --checkpoints <dir> compares it to the journal and reports a truncated tail, and reports every journal checkpoint the directory lacks. An export failure is logged (audit checkpoint export failed) and never refuses calls; treat the log line as an alert, because until the copy is restored a truncation back to that checkpoint would go unreported. |
MCP_AUDIT_FORWARD_URL |
— | Enables forwarding of the journal to a SIEM from the control (or all) role, behind the AuditForwarder port (plan §3.9, C.3). The scheme selects the adapter: syslog+tls://host[:6514] is RFC 5424 over RFC 5425 octet counting on TLS (plaintext syslog:// is refused); relp+tls://host[:6514] sends RFC 5424 messages through RELP with a matching success acknowledgement for every message (128 KiB maximum message including its syslog header; larger records send a sequence/byte-count/SHA-256 notice while the full line remains in the journal); https://…/services/collector[/event] is Splunk HTTP Event Collector; any other https:// URL is a generic JSON endpoint (a POST of a JSON array of records, Authorization: Bearer when a token is set). http:// is accepted on loopback only. Records are the journal’s own lines, byte for byte except RELP oversized-record notices, forwarded in sequence from the last acknowledged one; the cursor survives restarts; RELP/HTTPS delivery is at least once subject to receiver queue durability, while TCP syslog has the documented acknowledgement gap; seq is the receiver’s deduplication key. An unreachable receiver never stops the process or a tool call — the journal is retained, the health banner reports audit forwarding is failing; journal retained, and delivery is retried with backoff (up to a minute). A URL with no adapter, or a certificate that does not load, refuses startup. See siem-forwarding-runbook.md. |
MCP_AUDIT_FORWARD_FORMAT |
inferred from the URL | hec or json, when the URL alone does not say (a Splunk collector on an unusual path, or a JSON endpoint whose path happens to contain /services/collector). |
MCP_AUDIT_FORWARD_TOKEN |
— (required for HEC) | The HEC token (Authorization: Splunk …) or the bearer token for a JSON endpoint. May be a secret reference (file://, vault://); the value in force is read at each delivery, so a rotation takes effect on the next batch without a restart. Never logged. |
MCP_AUDIT_FORWARD_JOURNAL_DIR |
MCP_AUDIT_JOURNAL_DIR |
The journal to forward. In the split topology the control replica points this at the gateway’s journal volume, mounted read-only. |
MCP_AUDIT_FORWARD_STATE_DIR |
the forwarded journal directory | Where the cursor (audit-forward-cursor.json) is kept; must be writable, so a read-only journal mount needs its own. Checked at startup. |
MCP_AUDIT_FORWARD_BATCH |
256; 1–4096 |
Most records per delivery. |
MCP_AUDIT_FORWARD_INTERVAL_SECONDS |
5; 1–3600 |
How long the shipper waits once it has caught up before looking for new records. After a failure the wait doubles per attempt, capped at a minute. |
MCP_AUDIT_FORWARD_TIMEOUT_SECONDS |
10; 1–300 |
Per-delivery bound: connect, TLS handshake, and the write or request. RELP applies the bound separately to TCP connect, TLS handshake, open negotiation and each record’s write/acknowledgement exchange; a healthy batch may exceed one timeout. |
MCP_AUDIT_FORWARD_ORIGIN |
$HOSTNAME, else mcp-server-devtools |
What this process calls itself to the receiver: the syslog HOSTNAME field, the HEC host. Set it per gateway when several forward to one receiver. |
MCP_AUDIT_FORWARD_CA_FILE |
— | PEM file holding the CA that signed the receiver’s certificate, added to the system trust store, for a private CA. |
MCP_AUDIT_FORWARD_CLIENT_CERT / MCP_AUDIT_FORWARD_CLIENT_KEY |
— | (syslog and RELP) A PEM client certificate and its PKCS#8, SEC1, or PKCS#1 key, for a receiver that requires mutual TLS. Both or neither. |
MCP_ROLLUP_STORE |
— | Usage-rollup backend on a role that serves the control plane: sqlite:///absolute/path.db (embedded SQLite) or memory:// (development/tests). postgres:// is reserved and refuses startup until that adapter is built. Rows contain usage metadata, including tenant and subject, but never arguments, content, or credentials. In a split gateway/control topology usage is not yet pushed between roles (CF-33); run all for complete rollups. See usage-and-metrics-runbook.md. |
MCP_ROLLUP_RETENTION_DAYS |
90; 0 keeps all |
Age of usage rows retained by the hourly pruning task. This is independent of the audit journal’s evidence retention. |
MCP_USAGE_CHANNEL_CAPACITY |
4096; clamped to 64–65536 |
Capacity of the non-blocking channel from tool calls to the rollup consumer. A full or closed channel drops usage and increments mcp_usage_events_dropped_total; it never delays or refuses a tool call. |
MCP_METRICS |
off | on or true exposes unauthenticated GET /metrics in Prometheus text format. Labels are bounded to vendor/tool/decision/outcome and never contain a subject, tenant, token, arguments, or content. Keep the endpoint cluster-internal. |
MCP_RATE_LIMIT_PER_PRINCIPAL |
0 (off) |
Sustained requests per second for each validated subject; positive decimals are accepted. Enforced after bearer validation and the mcp:tools scope check. The limiter is in-process and therefore assumes one gateway replica (CF-16). A refusal is HTTP 429 with Retry-After. At most 10,000 subjects are tracked per process; past that, a new subject is refused (not admitted untracked) until one goes idle. See the usage runbook. |
MCP_RATE_LIMIT_BURST |
twice the rate, at least 1 |
Token-bucket capacity per principal. Must be at least 1 when the rate limit is enabled. |
MCP_SECRET_REFRESH_INTERVAL_SECONDS |
30; clamped to 1–3600 |
How often secret references (file://…, “Secret references” above) are re-read. Not enterprise-only: applies whenever the configuration holds a reference. |
MCP_VAULT_ADDR |
— | Enables the vault:// adapter: the Vault / OpenBao address, https://vault.example:8200. Must be https, or http on loopback (a dev server, or a Vault Agent sidecar on the pod). Read at startup; not hot-reloaded. |
MCP_VAULT_AUTH |
— (required with MCP_VAULT_ADDR) |
kubernetes, approle, or token. Explicit: the adapter never tries one method and falls back to another. |
MCP_VAULT_ROLE |
— (required for kubernetes) |
The Vault role bound to the pod’s service account (vault write auth/kubernetes/role/<role> bound_service_account_names=… bound_service_account_namespaces=… token_policies=… token_ttl=…). The projected service-account token is read at every login, so a rotated projection is used without a restart. |
MCP_VAULT_JWT_PATH |
/var/run/secrets/kubernetes.io/serviceaccount/token |
Where the projected service-account token is (kubernetes auth). |
MCP_VAULT_ROLE_ID |
— (required for approle) |
The AppRole role id. |
MCP_VAULT_SECRET_ID |
— (required for approle) |
The AppRole secret id, or a file:// reference to a file holding it (re-read at every login, so a rotated secret id is picked up). Only a literal or a file:// reference: a provider’s own credential cannot come from a provider. |
MCP_VAULT_TOKEN |
— (required for token) |
A client token, or a file:// reference to one. The adapter calls lookup-self to learn the lease and renews a renewable token; a token whose policy forbids lookup-self is used as given. |
MCP_VAULT_AUTH_MOUNT |
approle / kubernetes |
The auth method’s mount path when it is not the default. |
MCP_VAULT_NAMESPACE |
— | Sent as X-Vault-Namespace on every request (Vault Enterprise, HCP Vault). |
MCP_VAULT_CACERT |
— | PEM file holding the CA that signed Vault’s certificate, for a private CA. |
MCP_AWS_AUTH |
— | Enables the awssm:// adapter and the aws_* tools, as one identity: web-identity (IRSA and any OIDC-federated identity: sts:AssumeRoleWithWebIdentity with a projected token), pod-identity (the EKS Pod Identity / ECS credentials endpoint), or static (an access key pair). Explicit: never a credential chain, profile, SSO cache, or credential_process; the adapter never tries one mode and falls back to another. The secret source reads it at startup (not hot-reloaded); the vendor reads it per call. |
MCP_AWS_REGION |
— (required; AWS_REGION accepted) |
The region of the secrets, of STS, and the default region of the aws_* tools. The platform’s spelling is accepted because IRSA injects it; nothing else about the mode is inferred. |
MCP_AWS_ROLE_ARN |
— (required for web-identity; AWS_ROLE_ARN accepted) |
The role to assume. |
MCP_AWS_WEB_IDENTITY_TOKEN_FILE |
— (required for web-identity; AWS_WEB_IDENTITY_TOKEN_FILE accepted) |
The projected OIDC token, re-read at every exchange so a rotated projection is used without a restart. |
MCP_AWS_ROLE_SESSION_NAME |
mcp-devtools |
The session name STS records in CloudTrail. |
MCP_AWS_CREDENTIALS_URI |
http://169.254.170.23/v1/credentials (AWS_CONTAINER_CREDENTIALS_FULL_URI accepted) |
pod-identity: the agent’s endpoint. http is allowed on loopback and link-local only. On ECS and Fargate, where the platform injects AWS_CONTAINER_CREDENTIALS_RELATIVE_URI instead, that path on the task endpoint http://169.254.170.2 is used when this is unset (deploy/aws). |
MCP_AWS_CREDENTIALS_TOKEN_FILE |
/var/run/secrets/pods.eks.amazonaws.com/serviceaccount/eks-pod-identity-token for the EKS agent’s endpoint, otherwise unset (AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE accepted) |
pod-identity: the file whose content is sent as Authorization to the agent. Unset for any other endpoint, including the ECS task endpoint, which takes no token: the request then carries no Authorization at all. |
MCP_AWS_ACCESS_KEY_ID |
— (required for static; AWS_ACCESS_KEY_ID accepted) |
The access key id. |
MCP_AWS_SECRET_ACCESS_KEY |
— (required for static; AWS_SECRET_ACCESS_KEY accepted) |
The secret access key, or a file:// reference to a file holding it. Only a literal or a file:// reference: a provider’s own credential cannot come from a provider. |
MCP_AWS_SESSION_TOKEN |
— (AWS_SESSION_TOKEN accepted) |
static: the session token when the key pair is temporary (a federated CLI or CI session, or aws configure export-credentials --format env on a workstation). Literal or file://. A temporary session is a snapshot: nothing refreshes it when it expires — export a new one. |
MCP_AWS_SECRETS_MANAGER_ENDPOINT |
https://secretsmanager.<region>.amazonaws.com |
A VPC or FIPS endpoint. The service root only (no path); https, or http on loopback. |
MCP_AWS_STS_ENDPOINT |
https://sts.<region>.amazonaws.com |
Likewise for STS (web-identity). |
MCP_AZURE_AUTH |
— | Enables the azkv:// adapter: workload-identity (AKS workload identity federation and any OIDC-federated identity: a projected token presented as a client assertion — the recommended mode, no secret anywhere), managed-identity (IMDS on a VM or node pool, or the platform identity endpoint on Container Apps and App Service when MCP_AZURE_IDENTITY_ENDPOINT and MCP_AZURE_IDENTITY_HEADER are set), or service-principal (tenant, client id, client secret). Explicit; never a DefaultAzureCredential-style chain. Read at startup; not hot-reloaded. |
MCP_AZURE_TENANT_ID |
— (required for workload-identity and service-principal; AZURE_TENANT_ID accepted) |
The Entra tenant. |
MCP_AZURE_CLIENT_ID |
— (required for workload-identity and service-principal; AZURE_CLIENT_ID accepted) |
The application (client) id. For managed-identity, optional: the client id of a user-assigned identity. |
MCP_AZURE_FEDERATED_TOKEN_FILE |
/var/run/secrets/azure/tokens/azure-identity-token (AZURE_FEDERATED_TOKEN_FILE accepted) |
workload-identity: the projected token, re-read at every exchange. |
MCP_AZURE_CLIENT_SECRET |
— (required for service-principal) |
The client secret, or a file:// reference to a file holding it. Literal or file:// only. |
MCP_AZURE_AUTHORITY_HOST |
https://login.microsoftonline.com (AZURE_AUTHORITY_HOST accepted) |
The Entra authority (https://login.microsoftonline.us, https://login.chinacloudapi.cn for the sovereign clouds). https, or http on loopback. |
MCP_AZURE_IMDS_ENDPOINT |
http://169.254.169.254/metadata/identity/oauth2/token |
managed-identity: the metadata service. http is allowed on loopback and link-local only. Not used when the platform identity endpoint below is set. |
MCP_AZURE_IDENTITY_ENDPOINT |
— (IDENTITY_ENDPOINT accepted) |
managed-identity: the platform’s identity endpoint where IMDS is not exposed (Container Apps, App Service inject IDENTITY_ENDPOINT). http is allowed on loopback and link-local only. Set together with MCP_AZURE_IDENTITY_HEADER; one without the other refuses startup. The token is requested with api-version=2019-08-01 and the resource, and expires at the response’s expires_on. |
MCP_AZURE_IDENTITY_HEADER |
— (IDENTITY_HEADER accepted) |
managed-identity: the value sent as X-IDENTITY-HEADER to the platform endpoint, which refuses a request without it; a literal or a file:// reference. Never logged or shown in diagnostics. |
MCP_AZURE_KEYVAULT_DNS_SUFFIX |
vault.azure.net |
The suffix a vault name is mapped onto, and the token’s audience (vault.azure.cn, vault.usgovcloudapi.net). |
MCP_AZURE_KEYVAULT_ENDPOINT |
— | A base URL every vault is read from instead of https://<vault>.<suffix> — for a fake in tests, not for production. |
MCP_AUDIT_APPEND_TIMEOUT_MS |
5000; clamped to 100–60000 |
Bound on the wait for durable acknowledgement. Before dispatch, a timeout refuses the call. After dispatch, the outcome record is not abandoned: the caller stops waiting at the bound, the write continues on a tracked task, and a graceful shutdown drains such writes for up to 30 s; see src/policy/egress.rs for what an intent without an outcome then means. |
Service accounts already authenticate. Access tokens acquired through the
client-credentials grant validate like ordinary tokens: issuer, audience,
expiry, subject and required scope, with groups empty when none are present.
The client authenticates to the external AS using its registered secret or
private-key JWT; only the resulting access token reaches this gateway.
MCP_CLIENT_CREDENTIALS_ENABLED=true additionally advertises
io.modelcontextprotocol/oauth-client-credentials through authenticated HTTP
server/discover. It defaults off and does not infer or enforce an acquisition
grant. Legacy initialize and ordinary token admission remain unchanged. See the
service-account setup
for least-privilege policy and the tested Keycloak flow. What a service account
may administer remains governed by MCP_ADMIN_PRINCIPALS above.
Validated tokens are cached for min(exp, 5 min) keyed by the token’s
SHA-256, so a revoked group membership takes effect when the client’s next
token is issued (plan §3.6); the revocation list above is what cuts a
token off sooner, and it is checked on every request regardless of the
cache. A token whose iat lies more than the clock skew in the future is
refused as not_yet_valid: it could not be dated against a revoke all
cut-off or the fresh-token rule. The policy_loaded and
revocation_loaded records are appended, and made durable, before the
port is bound; a journal that cannot take them stops the server from
starting.
Process-only runtime settings
Section titled “Process-only runtime settings”These are read directly from the process environment during startup. They do not take effect in .env or ~/.mcp/configs.json; set them in the shell, service definition, or MCP client’s env object.
| Setting | Default | Purpose |
|---|---|---|
TRANSPORT_MODE |
stdio |
stdio or http; unknown values log a warning and use stdio. |
MCP_DOTENV_PATH |
unset | Path of the .env file to read in place of ~/.mcp/.env. |
MCP_DOTENV_CWD |
unset | true reads .env from the working directory (the behaviour before 0.18.0). Ignored when MCP_DOTENV_PATH is set. |
MCP_GLOBAL_CONFIG_PATH |
unset | Path of the global config file to read in place of ~/.mcp/configs.json. Set this for a service account with no real $HOME (nobody, a systemd DynamicUser=yes unit, a distroless nonroot container user), or to point at a mounted Kubernetes Secret/ConfigMap at a fixed path. |
PORT |
3000 |
Port for HTTP mode. The server binds to 127.0.0.1. |
RUST_LOG |
info,mcp_server_devtools=debug |
Tracing filter. A valid non-empty value overrides DEBUG. |
DEBUG |
unset | Exact value true or 1 selects the debug filter when RUST_LOG is absent. |
LOG_STDERR |
enabled | off, false, 0, or no disables console logging; the diagnostic file remains enabled. |
AUDIT_LOG |
enabled | off, false, 0, or no disables structured audit logging. |
AUDIT_LOG_MAX_BYTES |
10485760 (10 MiB) |
Maximum bytes per audit segment. |
AUDIT_LOG_MAX_FILES |
5 |
Maximum segments retained by the current process. |
AUDIT_LOG_RETENTION_DAYS |
30 |
Age limit for prior-session audit logs. |
AUDIT_LOG_RETENTION_MAX_BYTES |
104857600 (100 MiB) |
Total size limit for prior-session audit logs. |
Diagnostic and audit logs are written below ~/.mcp/data. Diagnostic log retention is fixed at seven days and 50 MiB; audit retention is configurable as shown above.
HTTP diagnostics emit HTTP request received and HTTP response ready at
info level for every request, including unauthenticated discovery, CORS
preflights, and refusals. Both events carry a server-generated request_id,
standard HTTP method, matched route template (<unmatched> for unknown paths),
and bearer_present boolean. The response event adds status and duration_ms
until response headers are ready; it does not measure a streaming body’s
lifetime. Authentication warnings within that request share the same ID. The
response carries this ID in x-request-id; caller-supplied IDs are ignored.
Literal path parameters, query strings, cookies, credentials, bodies and token
claims are excluded from these diagnostic fields.
For customer authentication troubleshooting, retain the attempt timestamp and
response x-request-id, then find that ID in the diagnostic file or service
stderr. The default filter includes these events; to enable only this module’s
request diagnostics alongside warnings, use
RUST_LOG=warn,mcp_server_devtools::server::diagnostics=info. A metadata fetch
followed by no authenticated retry narrows the investigation to the client’s
OAuth flow or identity-provider setup. Okta-side registration and login errors
need client or identity-provider evidence; this server cannot observe those
requests. These diagnostic records supplement the durable audit journal.
Enterprise-managed authorization (C.1)
Section titled “Enterprise-managed authorization (C.1)”MCP_EMA_ENABLED=true opts authenticated HTTP deployments into EMA discovery
checks and extension advertisement. The configured OIDC audience must equal
MCP_PUBLIC_URL; the issuer must advertise the ID-JAG profile and JWT-bearer
grant. Default is false. See EMA setup, exchange helper and required external
client matrix. External client interoperability is not yet
claimed. mcp-devtools auth exchange --help describes the Unix file-based
protocol helper; it is separate from vendor commands and the admin API.
| Setting | Default | Purpose |
|---|---|---|
MCP_CLIENT_CREDENTIALS_ENABLED |
false |
Advertise io.modelcontextprotocol/oauth-client-credentials only through authenticated HTTP server/discover. Accepts exactly true or false and rejects conflicting vendor values at HTTP startup; enabling requires inbound OIDC (MCP_AUTH_MODE=oidc or okta). Ignored by stdio and direct CLI commands. Discovery follows global-config reloads; invalid or conflicting reloaded values disable advertisement and log a warning. Read-only in the console. Does not infer or enforce token acquisition grants, alter ordinary token admission, or advertise Tasks. See the service-account setup. |
MCP_EMA_ENABLED |
false |
true opts an OIDC-authenticated HTTP deployment into enterprise-managed authorization (EMA) discovery checks and extension advertisement; the HTTP server refuses to start on any value other than true or false, including conflicting vendor values. See the EMA runbook. |
Linux .deb/.rpm defaults and protected systemd environment configuration
are documented in package operations.
The Compose reference runs all behind TLS
with persistent journal, rollup and policy volumes. Environment changes
require restarting the service; JWKS file contents are checked automatically.
NinjaOne QA/dev database tools
Section titled “NinjaOne QA/dev database tools”The default ninjaone-db Cargo feature adds ninjaone_db_resolve_division,
ninjaone_db_query_central, ninjaone_db_query_division, and
ninjaone_db_query_named. Connections are
opened only when a tool is called. Builds without this feature omit these tools;
WRDS can be enabled independently.
| Setting | Default | Purpose |
|---|---|---|
NINJAONE_DB_ENVIRONMENTS |
Unset | QA/dev PostgreSQL environment map with central, division, optional nested lockhart governor/shard connections, and other databases named connections; each named connection fixes its host, database, credentials, and TLS settings. Lockhart database names default to governor for governor and lockhart for shard, and can be overridden with database. |
Set NINJAONE_DB_ENVIRONMENTS in the ninjaone section of configs.json as a
nested object or a JSON-encoded string. Environment variables accept the encoded
string form. Use database accounts with read-only privileges.
The three ninjaone_db_query_* tools accept an optional explain object
(analyze, buffers, verbose, format of text or json) that returns the
query plan instead of rows. sql must still be one SELECT or
WITH ... SELECT; EXPLAIN of a write is not supported. With analyze the
query runs once under the 10-second statement timeout and rowLimit does not
apply. There is no setting for it.
{ "ninjaone": { "environments": { "NINJAONE_DB_ENVIRONMENTS": { "qa5": { "centralHost": "central.qa5.internal", "username": "central_reader", "password": "replace-with-central-password", "lockhart": { "governor": { "host": "lockhart-governor.qa5.internal", "username": "governor_reader", "password": "replace-with-governor-password" }, "shard": { "host": "lockhart-shard.qa5.internal", "username": "shard_reader", "password": "replace-with-shard-password" } }, "divisionHosts": { "db-host-1": { "host": "division-1.qa5.internal", "username": "division_reader", "password": "replace-with-division-password" } } } } } }}Aliases must start with qa or dev; other aliases cause the entire document
to be rejected. centralDatabase defaults to centraldb, port to 5432, and
sslMode to require. Division host entries may also be hostname strings,
which inherit the environment’s credentials and connection settings. Tool
arguments select a configured host key, never an arbitrary hostname.
The optional lockhart block configures governor and shard connections using
the same connection fields as division hosts. An optional databases map
configures other named databases alongside central, division, and Lockhart.
Each Lockhart entry specifies host, username, and password, with optional
port, sslMode, and allowInvalidCertificates using the same defaults as
central. Keys contain letters, digits, hyphens, underscores, or dots. Query a
configured entry with ninjaone_db_query_named, for example
{"environment":"qa5","database":"lockhart.governor","sql":"SELECT 1"}.
Lockhart governor and shard are separate entries, selected independently with
lockhart.governor and lockhart.shard. Configure their actual physical database
names with an optional database field: governor defaults to governor, and shard
defaults to lockhart. For example, set "database":"custom_lockhart" on shard
to override its database name. Other named entries require database explicitly.
Multiple shards can use additional entries such as shard-1
and shard-2 under lockhart. The lockhart. prefix is reserved for that block.
The physical database name is fixed in configuration and need not start with div_.
Resolve a division first, then pass its db_name and db_host to the division
query tool. dbHost can be omitted when only one division host is configured.
Queries accept one SELECT or WITH ... SELECT, run in a read-only transaction,
and use a ten-second statement timeout. Results default to 500 rows and are
capped at 10,000; division lookups return at most 25 rows. All four tools support
jq and outputFormat. The read-only guard is a courtesy error for the model,
not the authorisation boundary: the database role’s grants are. Functions the
role may call (pg_sleep, dblink_exec, lo_export) are not blocked by the
guard, so grant the role only what a reader needs.
sslMode: "prefer" permits plaintext fallback when the server declines TLS.
allowInvalidCertificates: true explicitly disables server certificate
verification for a QA/dev connection; its default is false.
Playwright browser tools
Section titled “Playwright browser tools”The optional playwright Cargo feature (off by default) adds playwright_open,
playwright_snapshot, playwright_click, playwright_fill,
playwright_screenshot and playwright_close. They are advertised over stdio
by default. An HTTP session omits them and refuses a direct call before
anything is launched, as it does for bb_clone, unless the operator sets
PLAYWRIGHT_HTTP_ENABLED=true and declares the browser’s network
containment with PLAYWRIGHT_CONTAINMENT; the deployment shape, the proxy
allowlist and the single-replica constraint are in
Playwright over HTTP. Each playwright_open
spawns one headless, in-memory-profile Microsoft Playwright MCP child under
Node and returns a handle bound to the validated caller; the other tools take
that handle, and another principal’s call gets the refusal an unknown handle
gets. Idle sessions close after PLAYWRIGHT_IDLE_TTL_SECS (stdio) or
PLAYWRIGHT_HTTP_IDLE_TTL_SECS (HTTP), a revoked subject’s browsers close
with its sessions, and every session closes when the server exits.
Installing the runtime (headless servers)
Section titled “Installing the runtime (headless servers)”The gateway always starts the browser headless (--headless, an in-memory
profile, no display, no X server or Wayland needed), so the runtime is
installed the same way on a developer machine and on a server with no
desktop. Nothing is downloaded by the gateway itself; install once, outside
it, as the user the gateway runs as (or make the directories world-readable):
-
Install Node 20 or newer (
node --version); on Debian/Ubuntu the NodeSource packages ornvmboth work. The gateway spawnsPLAYWRIGHT_NODE_BIN(defaultnodeon itsPATH). -
Run the repository’s script. It pins
@playwright/mcp@0.0.83and installs Chromium (Playwright’s headless shell build is included) into the browser directory:Terminal window scripts/install-playwright-runtime.sh /opt/playwright-mcp /opt/playwright-browsersOn a bare server Chromium’s shared libraries (fonts, NSS, libatk, …) are usually missing; run the script once as root with
PLAYWRIGHT_INSTALL_DEPS=1to have Playwright install them through the distribution’s package manager (Debian/Ubuntu), or install the listnpx playwright install-deps --dry-run chromiumprints. -
Point the settings at it, exactly as the script prints at the end:
PLAYWRIGHT_ENABLED=truePLAYWRIGHT_MCP_CLI=/opt/playwright-mcp/node_modules/@playwright/mcp/cli.jsPLAYWRIGHT_BROWSERS_PATH=/opt/playwright-browsers -
Start the gateway and read its first log lines:
browser tools advertised over stdio(orenabled over HTTP) means the probe found the runtime. WhenPLAYWRIGHT_ENABLED=trueand the runtime is not where the settings say, the tools are withdrawn, the startup log names the missing path and this script, and the health banner saysbrowser tools disabled (runtime not installed).
In a container without user namespaces Chromium’s own sandbox cannot start;
set PLAYWRIGHT_NO_SANDBOX=true there (the playwright image target does)
and let the container be the sandbox. The image target is the other way to
install: docker build --target playwright --build-arg EXTRA_FEATURES=playwright
bakes Node, the pinned package and Chromium in
(Playwright over HTTP).
{ "playwright": { "environments": { "PLAYWRIGHT_ENABLED": "true", "PLAYWRIGHT_MCP_CLI": "/opt/playwright-mcp/node_modules/@playwright/mcp/cli.js", "PLAYWRIGHT_BROWSERS_PATH": "/opt/playwright-browsers" } }}The child sees an allowlisted environment (PATH, HOME, temp and locale
variables, PLAYWRIGHT_BROWSERS_PATH), never NODE_OPTIONS or the gateway’s
secrets. No JavaScript execution or shell capability is mapped. The browser’s
own traffic does not pass the HTTP egress policy; the call’s outcome record
names the URL it was pointed at, and under a policy file the tools are
classified as page actions (open_page scoped to the host being opened,
read_page, act_on_page, close_page) so a rule can confine where a
browser may be pointed and hold playwright_click and playwright_fill for
approval. See the settings table above for the caps.
Mobile stores
Section titled “Mobile stores”App Store Connect (appstore) and Google Play (googleplay) expose read and
JSON publishing tools. See credentials, configuration keys, and publishing
examples. Store-specific keys are APPSTORE_KEY_ID,
APPSTORE_ISSUER_ID, APPSTORE_PRIVATE_KEY, APPSTORE_TOKEN,
GOOGLEPLAY_SERVICE_ACCOUNT, and GOOGLEPLAY_TOKEN. Private keys,
service-account JSON and tokens support the shared secret-reference resolver.
Cloudflare REST API
Section titled “Cloudflare REST API”Set CLOUDFLARE_API_TOKEN in the environment or in the cloudflare section
of ~/.mcp/configs.json. Keychain and secret references use the shared secret
resolver. Use a scoped API token with the permissions required by the endpoints
and access to the accounts or zones you intend to manage.
{"cloudflare": {"environments": {"CLOUDFLARE_API_TOKEN": "<scoped-api-token>"}}}The integration exposes cloudflare_get, cloudflare_post, cloudflare_put,
cloudflare_patch, and cloudflare_delete. With automatic vendor discovery,
configuring the token enables these tools. For an explicit selection, include
cloudflare in MCP_ENABLED_VENDORS.
For example, call cloudflare_get with:
{ "path": "/zones", "queryParams": {"name": "example.com", "page": "1", "per_page": "20"}, "outputFormat": "json"}The host is fixed to https://api.cloudflare.com; /client/v4 is prepended
when missing. Use returned zone IDs in /zones/{zone_id}/dns_records, or
account IDs in /accounts/{account_id}/pages/projects. List calls return one
page, including Cloudflare’s result_info when present. jq is a JMESPath
expression over the complete envelope; result[*].{id:id,name:name} selects
just the resources. TOON is the default output format.
Writes accept JSON bodies. DELETE sends no body. Binary and multipart uploads, including Worker script deployment, and streaming APIs are not supported. The integration uses the shared policy and egress checks; unclassified Cloudflare operations remain subject to the existing default-deny policy in enforcing deployments.
See Cloudflare’s API documentation and API token setup.
Deployment WAF settings
Section titled “Deployment WAF settings”These settings belong to the Helm chart and proxy configuration, not the server environment.
| Setting | Default | Purpose |
|---|---|---|
waf.requestBodyLimitBytes |
2097152 |
Helm ModSecurity inspection cap in bytes, 1–1073741824; renders both body-limit directives after engine.conf. Requests above it are rejected, never partially inspected. Compose/k8s: edit both directives in deploy/waf/body-limit.conf together. |
waf.clientMaxBodySize |
1m |
Helm nginx request cap; positive integer bytes or binary k/m units. Must not exceed waf.requestBodyLimitBytes. Compose/k8s: edit client_max_body_size in the proxy config. Pair changes with HTTP_REQUEST_BODY_LIMIT_BYTES and any ingress cap. |
waf.clusterDomain |
cluster.local |
Helm Kubernetes DNS suffix for complete role Service names. Runtime resolution uses the pod’s /etc/resolv.conf, caches for 10 seconds, and times out after 5 seconds. For plain k8s manifests, edit the .svc.cluster.local suffix if the cluster differs. |
Inference path and telemetry (opt-in)
Section titled “Inference path and telemetry (opt-in)”The gateway can forward model requests to one configured provider
(/v1/messages, /v1/messages/count_tokens, /v1/chat/completions,
/v1/responses, /v1/models) under the provider credential, decided by the
policy’s models rules and journaled as inference_intent and
inference_outcome records that never hold a prompt or a completion
(runtime security plan §6; docs/inference-path.md).
The routes mount only when the upstream is set, on the all and gateway
roles, behind the same bearer validation, revocation list and rate limiter
as /mcp with the mcp:inference scope. Startup is fail-closed: an upstream
without a credential, or a credential without an upstream, refuses to start.
| Setting | Default | Purpose |
|---|---|---|
MCP_INFERENCE_UPSTREAM_URL |
unset (routes absent) | The provider origin and base path the routes forward to, https (or http on loopback), with no credentials, query or fragment. The route path is appended as the client sent it. Requires MCP_INFERENCE_CREDENTIAL. Read at startup; a changed upstream is a restart. |
MCP_INFERENCE_PROTOCOL |
anthropic |
anthropic (native Messages: the credential is sent as x-api-key) or openai (an OpenAI-compatible endpoint: the credential is sent as a bearer). Selects the error-body shape, the usage frames read off the response, and the providers name models rules match. |
MCP_INFERENCE_CREDENTIAL |
unset (required with the upstream) | The provider credential: a literal, keychain, or a secret reference (vault://, awssm://, azkv://, file://), resolved at startup like any server setting and never logged or journaled. The caller’s Authorization, x-api-key and cookie headers are stripped; this value is the only credential the upstream sees. |
MCP_INFERENCE_MAX_REQUEST_BYTES |
4194304; 16384–67108864 |
Largest request body an inference route accepts; a larger body is refused with 413 before policy or forwarding. Out-of-range values are clamped. |
MCP_INFERENCE_STREAM_IDLE_TIMEOUT_SECONDS |
120; 5–3600 |
Longest wait for the next upstream chunk before the relay ends the stream and journals idle_timeout. Bytes already relayed are counted in the record. Out-of-range values are clamped. |
MCP_TELEMETRY_OTLP_URL |
unset (no export) | Where tool-call and inference telemetry is exported as OTLP/JSON traces over HTTP (https, or http on loopback), one span per event with the W3C trace and span ids verbatim and every other field as an attribute (strings as strings; numbers and booleans typed, so a trace store can sum token counts and durations). Metadata only: never arguments, prompts, completions or secrets. Delivery is off the request path and lossy by contract; drops are counted on the health banner’s metrics. |
MCP_TELEMETRY_CHANNEL_CAPACITY |
4096; 64–1048576 |
Events the telemetry channel buffers between the request path and the exporter. When full, new events are dropped and counted; the request is never delayed. Out-of-range values are clamped. |
Prompt and completion inspection (runtime security plan §7) is policy, not configuration: a schema 3 content rule over the prompt or completion subject turns it on, and MCP_GUARD_STREAM_LOOKBACK_BYTES, MCP_GUARD_MAX_SUBJECT_BYTES and the MCP_GUARD_WEBHOOK_* settings in the enterprise table bound it. Flow rules, budgets and findings-triggered holds (plan §8) are policy too: the flows and budgets sections and a prompt rule with action: hold, with MCP_TAINT_MAX_OWNERS and MCP_TAINT_TTL_SECONDS bounding the taint store and MCP_CALL_APPROVALS=required supplying the gate a held request waits on.
Durable Tasks
Section titled “Durable Tasks”The optional lifecycle is available over local stdio and HTTP using the pinned 2026-07-28 per-request protocol. With explicit current-request negotiation, approval-held calls return status tasks and admitted calls to the long-running tools listed in deployment and recovery run in the background; other calls and legacy approval responses retain their behavior. Initialize capabilities never opt a request into Tasks.
| Setting | Default | Purpose |
|---|---|---|
MCP_TASKS_ENABLED |
false |
Enable durable Tasks discovery and lifecycle handlers. Exactly true or false; requires MCP_TASKS_DIRECTORY. Restart required; read-only in the console. |
MCP_TASKS_DIRECTORY |
unset | Absolute path to an existing private persistent directory dedicated to this process’s task database. Unix permissions must exclude group/other access. No network filesystem or multiple replicas. Restart required; read-only in the console. |
MCP_TASKS_MAX_RECORDS |
10000 |
Maximum unexpired task rows, working and terminal combined; integer 1–100000. Expired rows awaiting cleanup do not count. Lowering the limit preserves startup and existing records; new tasks are refused until below capacity. Restart required; read-only in the console. |
MCP_TASKS_QUEUE_CAPACITY |
128 |
Bounded database-worker admission queue; integer 1–4096. Full queues immediately return busy. Restart required; read-only in the console. |
MCP_TASKS_TTL_SECONDS |
86400 |
Initial lifetime from creation, integer 60–604800 seconds. Approval tasks additionally retain status until at least one hour past proposal expiry. Completion or generic restart failure retains a result for at least one additional hour; returned ttlMs reflects the new expiry. Restart required; read-only in the console. |
MCP_TASKS_MAX_RUNNING |
16 |
Concurrent background tool executions; integer 1–256. When every slot is busy, a negotiated call to a background-capable tool runs synchronously instead. Restart required; read-only in the console. |
MCP_TASKS_POLL_INTERVAL_MS |
1000 |
Advertised and enforced minimum interval between successful polls of each task; integer 100–60000 milliseconds. Restart required; read-only in the console. |
MCP Skills
Section titled “MCP Skills”Publishes the reviewed workflows embedded in the binary through the MCP
Skills extension (io.modelcontextprotocol/skills) to 2026-07-28 clients.
Off, and for older-protocol sessions, the resource and Skills surface is
unchanged. See MCP Skills.
| Setting | Default | Purpose |
|---|---|---|
MCP_SKILLS_ENABLED |
false |
Advertise resources and the Skills extension in server/discover, and serve skills/list, skills/get, resources/list and resources/read for the embedded catalog. Exactly true or false; a malformed value refuses startup and is treated as false after a reload. Read per request, so a reload (including a console edit) takes effect on the next request; the catalog is validated at startup either way. Editable in the console. |
Local guard companion supervision
Section titled “Local guard companion supervision”These are CLI flags for scripts/local_guard_service/serve.py, not gateway
settings or console-editable keys. See local model guards
and the systemd example. No gateway default
policy, model selection, artifact verification or ONNX provider changes.
| Setting | Default | Purpose |
|---|---|---|
--max-restarts |
3 |
Maximum worker replacements for the service process lifetime, integer 0–10. Zero disables recovery. Successful readiness does not reset the count; exhaustion stays unavailable until an operator restarts the service. |
--restart-backoff |
1 |
Initial worker restart delay in seconds, finite 0.1–60, at most --restart-backoff-max. Doubles for each consumed restart. |
--restart-backoff-max |
30 |
Maximum restart delay in seconds, finite and between --restart-backoff and 60. |
--heartbeat-interval |
1 |
Idle worker heartbeat interval in seconds, finite 0.1–30. A heartbeat shares the single admission slot and uses --deadline; it checks IPC responsiveness. |
--startup-timeout |
600 |
Per-attempt initialization deadline in seconds (1–3600), covering hardware probe, provisioning, loading and warmup. A worker-stage timeout may consume a restart; a provisioning timeout is terminal. |
--deadline |
1.5 |
Per-inspection/heartbeat deadline in seconds, finite 0.1–30. No automatic inspection retry; cleanup and backoff cannot replay a subject. Keep the gateway webhook timeout above this plus cleanup/network margin. |
There are zero automatic download retries and zero per-request inspection retries. Each replacement repeats artifact verification and warmup. Permanent configuration, backend, artifact/access and model-contract errors require operator intervention. The deployment example intentionally disables automatic whole-service restarts to avoid resetting the internal budget.