Created: 2026-05-10Status: Living document — update on every PR that adds, renames, or removes a configuration parameter. See also configuration.md for full parameter semantics.
The MCP Gateway Registry is configured identically across three deployment surfaces. The same logical parameter carries a different name, lives in a different file, and is verified with a different command depending on how you deploy. This document maps every parameter across all three surfaces so that:
Operators can find the right variable name for their deployment.
Reviewers can confirm a new parameter was wired through all three surfaces.
The naming drift between SCREAMING_SNAKE_CASE, snake_case, and camelCase is visible and intentional, not accidental.
GET /api/config/export?format={env\|json\|tfvars\|yaml}
Admin only, cookie session only — same limitation as /api/config/full
Export config in the surface-native format. Use include_sensitive=true with caution.
These endpoints are the authoritative source for what the running registry actually sees — always use them to verify before filing a "config not applied" bug.
Current limitation: Bearer token does not work on /api/config/full or /api/config/export (2026-05-10)¶
Verified against a deployed registry (https://<registry-host>) using a signed JWT from the "Generate JWT Token" UI flow (a self-signed admin token with is_admin=true, the same token family registry_management.py uses):
Endpoint
Auth
Result
GET /api/config
Authorization: Bearer <jwt>
200 OK — returns deployment_mode, registry_mode, nginx_updates_enabled, registration_gate_enabled, asset_lifecycle_statuses, features
GET /api/auth/me
Authorization: Bearer <jwt>
200 OK — confirms is_admin: true, admin groups, admin scopes
GET /api/config/full
Authorization: Bearer <jwt>
401{"detail":"Authentication required"} — despite token being admin
GET /api/config/export?format=env
Authorization: Bearer <jwt>
401{"detail":"Authentication required"} — same
Root cause: the 401 text matches the session-cookie-only helpers in registry/auth/dependencies.py:30,74 (get_current_user and get_user_session_data), not the 403 "Admin access required" that the handler itself would raise. That means the Depends(enhanced_auth) chain for these two endpoints resolves into a cookie-session-only path on this deployment and never reaches the is_admin check.
Workarounds for now: - Use the UI at Settings → System Config → Configuration (logged in as admin via browser). - Or curl -b cookies.txt after logging into the UI and saving the session cookie. - Or hit /api/config (works with Bearer) for the subset of fields exposed there.
Cookie-based curl attempt (2026-05-10). Retested with a manually captured mcp_gateway_session cookie (1,737 bytes, Starlette-signed). nginx returned HTTP 401 with WWW-Authenticate: Bearer, meaning the auth-server's /validate rejected the cookie via validate_session_cookie() (auth_server/server.py:630). Most likely causes: cookie expired, SECRET_KEY rotated since the cookie was issued, or the cookie was issued on a different host. Net effect: even the documented cookie-only path is brittle for out-of-band testing — browser session is the only reliable path right now.
Follow-up work:/api/config/full and /api/config/export should accept Bearer JWTs for admin callers too. The fix is in registry/auth/dependencies.py — widen the enhanced_auth path so Bearer admin tokens are accepted on these endpoints. Separate issue to be filed once we confirm intent (some deployments may deliberately keep these cookie-only as defense-in-depth).
Conventions: - A blank cell means the parameter is not configurable on that surface — either it is deployment-specific (e.g. CloudFront only applies to ECS) or the wiring is missing (flag this in a PR). - — in Purpose means the parameter mirrors the row directly above. - Secrets/sensitive values are flagged with (secret) — these must use AWS Secrets Manager (Terraform) or existingSecret / secretKeyRef (Helm), never plain values in version control.
Internal and external URLs for the auth server, plus internal JWT signing.
Parameter
Docker (.env)
Terraform (.tfvars)
Helm (values.yaml)
Purpose
Auth server internal URL
AUTH_SERVER_URL
— (constructed by module)
registry.app.authServerUrl
Server-to-server URL inside the container network.
Auth server external URL
AUTH_SERVER_EXTERNAL_URL
— (from domain config)
auth-server.app.externalUrl
Public URL for browser redirects.
Internal JWT issuer
(constant in code)
—
auth-server.app.jwtIssuer
iss claim on internal service JWTs.
Internal JWT audience
(constant in code)
—
auth-server.app.jwtAudience
aud claim on internal service JWTs.
App secret key (secret)
SECRET_KEY (required)
secret_key via TF_VAR_* / Secrets Manager (required)
global.secretKey (Helm chart auto-generates at install time if unset)
JWT signing + session-cookie signing + at-rest encryption of OAuth id_token. Required — auth_server and registry refuse to start without it (the previous per-replica random fallback caused BadSignature across replicas). Must be identical across all auth_server and registry replicas. Rotating invalidates stored creds and active sessions; rotation requires a process restart, not a SIGHUP reload. Must be high-entropy (32+ bytes from a CSPRNG) — read access to the oauth_sessions_* collection is equivalent to credential compromise unless this key is strong and never written to a logged location. Generate with python3 -c 'import secrets; print(secrets.token_urlsafe(32))'.
Advertised OAuth scopes
MCP_ADVERTISED_SCOPES
—
registry.app.mcpAdvertisedScopes
Space-separated override for the scopes_supported array in the PRM (Protected Resource Metadata) document. When set, only these scopes are advertised to MCP discovery clients. Useful when the IdP performs RFC 7591 DCR and rejects scope names it does not recognize. Example: openid email profile offline_access. When unset, all scopes from the registry authorization config are advertised (default).
Affects only with-gateway deployments (nginx reverse proxy).
Parameter
Docker (.env)
Terraform (.tfvars)
Helm (values.yaml)
Purpose
Extra nginx server_name entries
GATEWAY_ADDITIONAL_SERVER_NAMES
—
— (ingress annotations handle this)
Space-separated list of additional hostnames / IPs to accept.
Server bind address (IPv6 opt-in)
BIND_HOST (and HOST for currenttime/mcpgw)
bind_host
mcpgw.app.bindHost
Default 0.0.0.0 (IPv4) works everywhere. Set to :: only for IPv6-only deployments — requires net.ipv6.bindv6only=0 on the host AND an IPv6 loopback in the container. Issue #863 / PR #864. Local-dev uvicorn direct invocation and servers/currenttime keep the safer 127.0.0.1 default.
Nginx IPv6 listeners (opt-in)
NGINX_ENABLE_IPV6
—
via extraEnv
Default false keeps the in-pod nginx reverse proxy's IPv4-only listen directives, which work on every host (binding [::] fails where IPv6 is unavailable). Set to true on IPv6-only / dual-stack clusters so the entrypoint adds listen [::]:8080; (and [::]:8443 ssl;), letting the load balancer and kubelet readiness probe reach the pod over IPv6. Nginx counterpart to BIND_HOST=::.
Async batch register/patch/replace/delete for agent cards, drained by an in-process worker. Helm renders these into the registry-batch-config ConfigMap (non-secret).
Parameter
Docker (.env)
Terraform (.tfvars)
Helm (values.yaml)
Purpose
Worker enabled
BATCH_WORKER_ENABLED
batch_worker_enabled
registry.app.batchWorkerEnabled
Enable in-process worker. v1: exactly one replica true.
Max ops per job
BATCH_MAX_OPERATIONS_PER_JOB
batch_max_operations_per_job
registry.app.batchMaxOperationsPerJob
Items per submission. Default 1000.
Max concurrent jobs/user
BATCH_MAX_CONCURRENT_JOBS_PER_USER
batch_max_concurrent_jobs_per_user
registry.app.batchMaxConcurrentJobsPerUser
Active jobs per submitter. Default 3.
Job retention (days)
BATCH_JOB_RETENTION_DAYS
batch_job_retention_days
registry.app.batchJobRetentionDays
TTL on updated_at. Default 7.
Worker poll interval
BATCH_WORKER_POLL_INTERVAL_SECONDS
batch_worker_poll_interval_seconds
registry.app.batchWorkerPollIntervalSeconds
Queue poll cadence. Default 1.0.
Max request bytes
BATCH_MAX_REQUEST_BYTES
batch_max_request_bytes
registry.app.batchMaxRequestBytes
Body size cap. Default 4194304 (4 MiB).
Worker lease TTL
BATCH_WORKER_LEASE_TTL_SECONDS
batch_worker_lease_ttl_seconds
registry.app.batchWorkerLeaseTtlSeconds
Seconds before an unrenewed lease expires. Default 60.
Worker lease heartbeat
BATCH_WORKER_LEASE_HEARTBEAT_SECONDS
batch_worker_lease_heartbeat_seconds
registry.app.batchWorkerLeaseHeartbeatSeconds
Lease renewal interval. Should be below TTL. Default 15.
Derived from entra_enabled / okta_enabled / auth0_enabled flags
global.authProvider.type
keycloak, cognito, entra, okta, auth0.
IDE OAuth client id
IDE_OAUTH_CLIENT_ID
ide_oauth_client_id
registry.ideOauthClientId
Registry-wide default pre-registered public OAuth client_id that IDEs (Cursor, Claude Code, Codex) use to start the gateway login flow. When set, a server's Connect config advertises this client_id and omits the static gateway token, so the IDE shows a login button and runs the OAuth/PKCE flow. A per-server oauth_client_id (see below) overrides this default. Use when anonymous Dynamic Client Registration is disabled and a fixed public client is registered instead. Empty (default) keeps the static-token Connect config. Not a secret.
IDE OAuth callback port
IDE_OAUTH_CALLBACK_PORT
ide_oauth_callback_port
registry.ideOauthCallbackPort
Fixed loopback callback port the IDE uses for the OAuth login redirect (http://localhost:<port>/callback). Needed for IdPs that match the redirect_uri literally including the port (Okta, Entra, Cognito): register that exact URI on the public client and set the same port here so the Connect dialog emits --callback-port (Claude Code). 0 (default) lets the IDE pick a port, which is correct for Keycloak (wildcard loopback redirect). Note: Codex/Cursor cannot pin the port, so this only fully helps Claude Code.
Issue #1127. Comma-separated IdP providers (e.g. pingfederate) for which the registry's local idp_user_groups collection is consulted to populate empty JWT groups claims. Empty disables fallback for all providers. Default: pingfederate. Read by both registry and auth-server.
PingFederate admin URL
PF_ADMIN_URL
pf_admin_url
registry.pingfederateAdmin.url
Issue #1127. Admin API URL used by the registry to create OAuth clients and Simple PCV users. Default: dev-only https://pingfederate:9999; override for BYO PingFederate. Read by registry only.
PingFederate admin user
PF_ADMIN_USER
pf_admin_user
registry.pingfederateAdmin.user
Issue #1127. Basic-auth user for the PF admin API. Default: dev-only administrator; override in production. Read by registry only.
Issue #1127. Secret. Basic-auth password for the PF admin API. Used by registry to create OAuth clients and Simple PCV users. Default: dev-only 2FederateM0re; override in production. Wired through AWS Secrets Manager (Terraform) and secretKeyRef (Helm). Read by registry only.
These live on the server entry (registration form / server JSON), not in global config, and are surfaced through GET /api/servers/{path}/connect-config to shape that server's Connect dialog:
Server field
Type
Purpose
oauth_client_id
string
Per-server public OAuth client_id. Overrides the registry-wide IDE_OAUTH_CLIENT_ID default for this server. When resolved (per-server or global), the Connect config for Cursor (auth.CLIENT_ID), Claude Code (--client-id), and Codex (--oauth-client-id) drops the static gateway token and runs the OAuth/PKCE login flow.
append_mcp_path
bool | null
Override the trailing /mcp transport segment on the gateway Connect URL. null (default) auto-detects from proxy_pass_url. Set false for root-endpoint servers (e.g. AWS Knowledge) that serve MCP at the server path itself; set true to force the suffix. For an entirely custom URL, use mcp_endpoint.
Optional override for Microsoft Graph base URL. Leave unset on standard Entra deployments — auto-inferred from ENTRA_LOGIN_BASE_URL via the documented sovereign-cloud mapping. Set explicitly only for proxied or air-gapped deployments.
Must be true in HTTPS, false on plain-HTTP localhost.
Cookie domain
SESSION_COOKIE_DOMAIN
session_cookie_domain
auth-server.app.sessionCookieDomain
Leading dot for cross-subdomain; empty is safest.
Group 13a — Tool-level Access Enforcement (Issue #1026)¶
Controls the per-user tool allowlist filter applied at the registry REST endpoints and the MCP tools/list JSON-RPC response. The allowlist is resolved from the mcp-scopes collection used by existing server-level access checks. All three values are non-sensitive.
Admin-defined, schema-driven catalog types (catalog-only; never proxied or executed). These are registry-only — the auth-server does not read them. All three values are non-sensitive. When the Main switch is off (default) the dynamic tabs and /api/custom* endpoints are not registered, so there is no behavior change.
Parameter
Docker (.env)
Terraform (.tfvars)
Helm (values.yaml)
Purpose
Enable custom entity types
CUSTOM_ENTITY_TYPES_ENABLED
custom_entity_types_enabled
registry.app.customEntityTypesEnabled
Main switch for dynamic tabs + /api/custom* endpoints. Default false; off = routers not registered.
Descriptor cache TTL (s)
CUSTOM_TYPE_CACHE_TTL_SECONDS
custom_type_cache_ttl_seconds
registry.app.customTypeCacheTtlSeconds
TTL for the in-process custom-type descriptor cache. Default 60.
Max records per type
MAX_CUSTOM_RECORDS_PER_TYPE
max_custom_records_per_type
registry.app.maxCustomRecordsPerType
Soft (best-effort) cap on records per type; create rejected at cap. Default 1000 (0 = unlimited).
Max custom types
MAX_CUSTOM_TYPES
max_custom_types
registry.app.maxCustomTypes
Cap on number of custom types; type create rejected at limit. Default 50 (0 = unlimited).
Group 13c — Update Check (Admin Banner, Issue #1218)¶
Background poll of the GitHub Releases API that surfaces a newer registry version in an admin-only banner (GET /api/system/update-check). Registry-only and non-sensitive. Fail-silent (never affects registry operation, so it is air-gap safe) and skipped on dev/local builds (a plain docker compose up has BUILD_VERSION unset; build_and_run.sh sets it to a non-semver git-describe string that the version parser skips). Set the enable flag to false for air-gapped clusters or to silence the banner.
Parameter
Docker (.env)
Terraform (.tfvars)
Helm (values.yaml)
Purpose
Enable update check
UPDATE_CHECK_ENABLED
update_check_enabled
registry.app.updateCheck.enabled
Enable the background GitHub-release poll + admin banner. Default true. Set false for air-gapped.
Poll interval (hours)
UPDATE_CHECK_INTERVAL_HOURS
update_check_interval_hours
registry.app.updateCheck.intervalHours
Polling interval in hours (minimum 1). Default 24.
Group 14 — GitHub Private Repo Access (for SKILL.md fetching)¶
Only the Helm mcpgw subchart and Docker expose these today.
Advisory check that surfaces likely-duplicate servers when a user registers a new one. Reuses the embedding model from Group 20 — the query embedder and the persisted-corpus embedder must be the same model for cosine scores to be meaningful. Used by the registry service only. Path uniqueness remains the only hard rule; this feature is purely advisory and never blocks registration.
Weights must sum to 1.0 ± 0.001 or the registry process refuses to start (validated in Settings).
Parameter
Docker (.env)
Terraform (.tfvars)
Helm (values.yaml)
Purpose
Enable feature
DEDUP_ENABLED
dedup_enabled
registry.app.dedup.enabled
Master switch. false makes POST /servers/similar return enabled=false and skip all work.
Score threshold
DEDUP_SCORE_THRESHOLD
dedup_score_threshold
registry.app.dedup.scoreThreshold
Minimum composite score (0.0..1.0) for a candidate to surface. Default 0.7.
Max suggestions
DEDUP_MAX_SUGGESTIONS
dedup_max_suggestions
registry.app.dedup.maxSuggestions
Cap on suggestions returned. Default 3.
Candidate pool size
DEDUP_CANDIDATE_POOL_SIZE
dedup_candidate_pool_size
registry.app.dedup.candidatePoolSize
k for vector search before scoring. Default 20. Tripled internally for restricted callers (whose visibility filter would otherwise decimate results).
Semantic weight
DEDUP_WEIGHT_SEMANTIC
dedup_weight_semantic
registry.app.dedup.weightSemantic
Default 0.55.
URL weight
DEDUP_WEIGHT_URL
dedup_weight_url
registry.app.dedup.weightUrl
Default 0.30.
Name-exact weight
DEDUP_WEIGHT_NAME_EXACT
dedup_weight_name_exact
registry.app.dedup.weightNameExact
Default 0.15.
Max query text chars
DEDUP_MAX_TEXT_CHARS
dedup_max_text_chars
registry.app.dedup.maxTextChars
Cap on (name + description) fed to the embedder per call. Default 2000. Cost guard.
OAuth per-client-id secrets. These are dynamic and named after the client_id.
Parameter
Docker (.env)
Terraform (.tfvars)
Helm (values.yaml)
Purpose
Per-client secret (secret)
OAUTH_CLIENT_SECRET_<client_id>
—
—
Overrides Cognito auto-retrieval. Vendor-level fallbacks (AUTH0_CLIENT_SECRET, OKTA_CLIENT_SECRET, ENTRA_CLIENT_SECRET, KEYCLOAK_CLIENT_SECRET) live in the provider groups above.
Group 29 — Container Registry Credentials (CI only)¶
Used only by the publish workflow; not by the running registry.
Parameter
Docker (.env)
Terraform (.tfvars)
Helm (values.yaml)
Purpose
Docker Hub username
DOCKERHUB_USERNAME
—
—
—
Docker Hub token (secret)
DOCKERHUB_TOKEN
—
—
—
Docker Hub org
DOCKERHUB_ORG
—
—
—
GitHub username
GITHUB_USERNAME
—
—
—
GitHub token (secret)
GITHUB_TOKEN
—
—
—
GitHub org
GITHUB_ORG
—
—
—
Group 29 — Extra Environment Variable Injection (Issue #1000)¶
User-supplied environment variables passed to the registry, auth-server, and mcpgw containers in addition to the chart-managed variables. Each surface enforces a reserved-name list so users cannot override chart-managed values; the canonical list lives in charts/<subchart>/reserved-env-names.txt and is the shared source of truth across all three surfaces.
Parameter
Docker (.env / extra_env)
Terraform (.tfvars)
Helm (values.yaml)
Purpose
Registry extra env
File: extra_env/registry.env at the repo root (override with $MCP_EXTRA_ENV_DIR); key=value per line, picked up via env_file: with required: false
registry_extra_env = [{ name, value }, ...](sensitive)
registry.extraEnv: [{ name, value }] / registry.extraEnvFrom: [...]
Inject custom env vars into the registry container.
Auth-server extra env
File: extra_env/auth-server.env (same directory as above)
auth_server_extra_env = [{ name, value }, ...](sensitive)
Inject custom env vars into the auth-server container.
mcpgw extra env
File: extra_env/mcpgw.env (same directory as above)
mcpgw_extra_env = [{ name, value }, ...](sensitive)
mcpgw.extraEnv: [...] / mcpgw.extraEnvFrom: [...]
Inject custom env vars into the mcpgw container.
Reserved names (shared across all three surfaces): - charts/registry/reserved-env-names.txt - charts/auth-server/reserved-env-names.txt - charts/mcpgw/reserved-env-names.txt
Collision enforcement per surface: - Docker / Docker Compose: build_and_run.sh runs validate_extra_env preflight on every start; rejects reserved-name collisions with the exact file and line number. - Terraform / ECS: terraform plan uses a validation block on each *_extra_env variable that reads the same reserved-env-names.txt via file() and rejects reserved names with contains(). - Helm: registry.validateExtraEnv / auth-server.validateExtraEnv / mcpgw.validateExtraEnv helpers in _helpers.tpl fail helm template/install with a clear error if a reserved name is supplied via extraEnv.
Secret handling: For production secrets, prefer Kubernetes extraEnvFrom (Helm) or AWS Secrets Manager ARNs wired into the task definition's secrets block (Terraform; see mongodb_connection_string_secret_arn as a reference pattern) rather than passing plaintext values via *_extra_env. The extra_env/*.env files on the Docker surface are plaintext on disk and should not be used for production secrets.
Group 30 — Infrastructure-Only (Terraform and Helm) Parameters¶
These have no .env equivalent because they describe the infrastructure, not the running registry.
Register the field in registry/api/config_routes.pyCONFIG_GROUPS so it appears on Settings → System Config and in GET /api/config/full. Mark sensitive values with is_sensitive=True.
Add a new row to the appropriate group in this file. If it belongs in a brand-new group, add a new group section. Confirmed by reviewer before merge.
If one of the three surfaces legitimately does not apply, leave the cell blank and explain in the PR description — do not silently omit.