Skip to content

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.

Integration, HTTP-cache, request-timeout, and streaming settings use this priority order (highest first):

  1. Process environment variables
  2. .env in ~/.mcp, beside the global config
  3. 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.

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.

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.

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.

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.

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.

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 (%2D becomes -). Reserved escapes remain encoded with uppercase hex (%2f becomes %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.

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.

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.

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.

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.

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.

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):

  1. Install Node 20 or newer (node --version); on Debian/Ubuntu the NodeSource packages or nvm both work. The gateway spawns PLAYWRIGHT_NODE_BIN (default node on its PATH).

  2. Run the repository’s script. It pins @playwright/mcp@0.0.83 and 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-browsers

    On a bare server Chromium’s shared libraries (fonts, NSS, libatk, …) are usually missing; run the script once as root with PLAYWRIGHT_INSTALL_DEPS=1 to have Playwright install them through the distribution’s package manager (Debian/Ubuntu), or install the list npx playwright install-deps --dry-run chromium prints.

  3. Point the settings at it, exactly as the script prints at the end:

    PLAYWRIGHT_ENABLED=true
    PLAYWRIGHT_MCP_CLI=/opt/playwright-mcp/node_modules/@playwright/mcp/cli.js
    PLAYWRIGHT_BROWSERS_PATH=/opt/playwright-browsers
  4. Start the gateway and read its first log lines: browser tools advertised over stdio (or enabled over HTTP) means the probe found the runtime. When PLAYWRIGHT_ENABLED=true and 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 says browser 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.

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.

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.

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.

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.

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.

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.

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.