Reference
Reference

Configuration reference

Every MCPG gateway configuration key, generated from the live AppConfig schema. The gateway rejects unknown fields at boot, so this is the exact, complete surface.

Generated from apps/gateway/src/config/ via mcpg config doc.
The audience-facing sections below are curated inside the generator; the per-block reference at the bottom is sourced from /// rustdoc + #[serde(...)] annotations on the live AppConfig tree.
Re-generate with: mcpg config doc > apps/gateway/docs/configuration.md.

Overview

Top-level gateway configuration. Loaded from YAML file and/or MCPG_ environment variables via figment. Bindings live under mcp.capabilities.{tools,prompts,resources,resource_templates}[]. Each binding carries an explicit nested backend: block that picks the implementation, discriminated by kind: (kind: http, kind: sql, kind: openai_chat, …). Env-var expansion (${env.X}) happens at startup time via CEL.

deny_unknown_fields is set so a typo at the root (or a stale renamed block left in an operator's YAML) fails parsing instead of silently parsing to defaults. The same strictness applies to every typed sub-config; this flag closes the gap at the root.

Two YAML conveniences are resolved before that check: merge keys (<<: *anchor) are applied, and a top-level key that starts with x- is set aside, so a file can keep shared anchors in an x- block (as in Compose files). Below the top level an x- key is an ordinary field name.

Operator workflow

The four config-tooling binaries cover the operator's full loop — pick a starting point, drill into a field, validate, boot:

bash
$ mcpg config init                                    # pick a deployment template, write config.yaml
$ mcpg config explain governance.audit.on_failure    # describe a field by dotted path
$ mcpg config check config.yaml                       # pre-flight validate (multi-file too)
$ MCPG_CONFIG=config.yaml mcpg                        # boot

Multi-file layering is supported — files later on the command line override earlier ones, and MCPG_* env vars apply last:

bash
$ MCPG_CONFIG=base.yaml:production-overrides.yaml mcpg

For in-place config rotation (kill -HUP) vs restart-required fields, see the config sources & hot-reload guide.

For IDE autocomplete, point your YAML language server at the committed schema:

yaml
# yaml-language-server: $schema=../../examples/deployments/config.schema.json
gateway:
  server:
    bind_address: "127.0.0.1:8787"
    # ↑ IDE autocompletes here, with `///` doc-comment as hover.

Top-level keys (Layout D'')

Layout D'' collapsed the pre-D'' flat root into seven typed top-level keys. The migration map for any pre-D'' YAML or env-var examples you might still be reading:

Pre-D'' keyLayout D'' key
auth:governance.access:
policy:governance.policy:
audit:governance.audit:
approvals:governance.approvals:
server:gateway.server:
admin:gateway.admin:
control_plane:gateway.control_plane:
content_storage:storage:
mcp.tools[] (etc.)mcp.capabilities.tools[] (etc.)
plugins.entries[]plugins[] (flat array, no wrapper)
plugins.kv / caches / secrets / configs / transports / policy / capability_grants / trust / credentialsDELETED — point-of-use slots + per-entry configuration replace these
plugins.health_probeobservability.plugin_health_probe
plugins.registrygateway.plugin_registry
plugins.config_overlaygateway.config_overlay
plugins.response_cachestorage.response_cache
plugins.enabled / plugins.plugin_dirDELETED
BackendImpl.type: (in a binding's backend: block)BackendImpl.kind:

The ten D'' top-level keys are: mcp:, governance:, gateway:, observability:, feature_flags:, debug:, schema_registry:, storage:, cluster:, plugins: (plus MCPG_CONFIG-only config_source:). governance: and gateway: are umbrellas — their children correspond one-to-one to former root peers.

MCPG_* env vars track the new shape with __ as the dotted-path separator. The corresponding env-var prefix migrations:

Pre-D'' env varLayout D'' env var
MCPG_AUTH__*MCPG_GOVERNANCE__ACCESS__*
MCPG_POLICY__*MCPG_GOVERNANCE__POLICY__*
MCPG_AUDIT__*MCPG_GOVERNANCE__AUDIT__*
MCPG_APPROVALS__*MCPG_GOVERNANCE__APPROVALS__*
MCPG_SERVER__*MCPG_GATEWAY__SERVER__*
MCPG_ADMIN__*MCPG_GATEWAY__ADMIN__*
MCPG_CONTROL_PLANE__*MCPG_GATEWAY__CONTROL_PLANE__*
MCPG_CONTENT_STORAGE__*MCPG_STORAGE__*
MCPG_MCP__TOOLS__* (etc.)MCPG_MCP__CAPABILITIES__TOOLS__* (etc.)
MCPG_PLUGINS__HEALTH_PROBE__*MCPG_OBSERVABILITY__PLUGIN_HEALTH_PROBE__*
MCPG_PLUGINS__REGISTRY__*MCPG_GATEWAY__PLUGIN_REGISTRY__*
MCPG_PLUGINS__CONFIG_OVERLAY__*MCPG_GATEWAY__CONFIG_OVERLAY__*
MCPG_PLUGINS__RESPONSE_CACHE__*MCPG_STORAGE__RESPONSE_CACHE__*

Quick start (minimal viable)

Goal: gateway answers /health, tools/list, and one tools/call against your binding. No auth, no cluster, no compliance plumbing.

Template: dev-single-node (mcpg config init --template dev-single-node). Six fields are load-bearing — everything else takes a sensible default.

FieldWhy it mattersDefaultReference
gateway.server.bind_addressThe TCP listener. 127.0.0.1:8787 for dev; 0.0.0.0:8787 once you trust auth."127.0.0.1:8787"ServerConfig
gateway.server.allowed_originsCORS allowlist for browser clients. Empty disables browser-cross-origin.[]ServerConfig
mcp.capabilities.tools[] (and prompts[], resources[], resource_templates[])The tools / prompts / resources this gateway exposes. At least one entry to be useful.[]McpConfig, BackendConfig
governance.accessInbound identity. Empty = anonymous (loopback only).{}AccessConfig
observability.logs.sinksWhere logs go. Default stderr-JSON is fine for dev.one stderr JSON sinkLogsConfig
governance.auditCompliance audit. On by default with the built-in local-file sink — drop directory into a tmpfs / scratch mount if your dev disk is read-only.enabled, file sinkAuditConfig

Boot it:

bash
$ mcpg config init --template dev-single-node --output config.yaml
$ mcpg config check config.yaml
$ MCPG_CONFIG=config.yaml mcpg

Production hardening

Goal: external traffic, OIDC, audit you can ship to compliance, multi-replica behind an LB. Pick this once dev clicks.

Templates: production-single-redis (single instance), production-redis-cluster (multi-replica), production-nats-cluster (NATS variant) — all available via mcpg config init --template <name>.

BlockWhat it gatesReference
gateway.server.tlsListener TLS — drop if your LB terminates TLS instead.TlsConfig
gateway.server.allowed_originsBrowser CORS allowlist. Wildcards are rejected.ServerConfig
gateway.server.max_sessions_per_tenantPer-tenant session quota. 0 = unlimited; tighten for SaaS deploys.ServerConfig
clusterCoordinator (KV + pub/sub). single_node for single-replica, redis / nats for multi-replica.ClusterConfig
governance.access.oidc_oauthInbound OIDC — verifies Bearer tokens against your IdP's JWKS.OidcOAuthConfig
governance.access.jwksStatic JWKS variant for air-gapped deploys (no IdP discovery call).JwksConfig
governance.policyPre-dispatch tool gate. default_minimum_trust + per-tool overrides + CEL allow_if.PolicyConfig
governance.auditCompliance audit fan-out. required: true refuses to boot without a serving sink.AuditConfig
governance.approvalsHuman-in-the-loop approvals — signing key + callback URL + grace window.ApprovalsConfig
plugins[]Tool-gate / transform / identity / cluster / catalog plugins. Rate limiting, IP allowlist, circuit breakers all live here. Each entry carries its own signature.trusted_keys: + granted_capabilities: (per-entry, not a wiring block).PluginEntryConfig
observability.metrics / tracesPrometheus scrape endpoint + OTLP traces to your collector.MetricsConfig, TracesConfig

Cluster pub/sub inheritance. Capability store:/bus: overrides default to kind: cluster (the cluster backend's primitive) when omitted, so when cluster.kind is redis or nats, mcp.configurations.delivery.bus and mcp.configurations.cancellation.bus automatically use the cluster's pub/sub. Server-initiated messages (cancellations, sampling responses, elicitations) reach the right replica without operator config. Override these only when you explicitly want single-replica behaviour despite a cluster (e.g. bus: { kind: memory }).


Advanced / experimental

Goal: pipeline tools, server-initiated suspensions, per-plugin observability carve-outs, control-plane attachment. Ignore until production basics are solid.

FeatureBlockReference
Pipeline bindings — multi-step tools that chain HTTP / SQL / Command / Transform / CEL gate steps.mcp.capabilities.tools[].backend: { kind: pipeline, steps: [...] }PipelineBackendConfig, PipelineStepConfig
Suspending pipeline steps — elicitation, sampling, roots_list. The pipeline pauses, the gateway sends a server-initiated request, the step resumes when the client responds.mcp.capabilities.tools[].backend.steps[].kind: elicitation | sampling | roots_listPipelineElicitationStepConfig, PipelineSamplingStepConfig, PipelineRootsListStepConfig
Approval gates — block a tool call until a human approves it via callback URL.governance.approvals + plugin entry for the approvals providerApprovalsConfig
MCP App URL — link a resource to a rich UI for client-side rendering.mcp.capabilities.resources[].mcp_app_url / mcp.capabilities.resource_templates[].mcp_app_url (CEL-templatable)BackendConfig
Resource subscription with custom watch strategy — push-based change notification with notifications/resources/updated.mcp.capabilities.resources[].watch / mcp.capabilities.resource_templates[].watchResourceWatchConfig
Per-plugin observability override — silence a noisy plugin or boost its verbosity in isolation, optionally redirect its events to a separate sink set.plugins[].observabilityPluginObservabilityToggle, SignalToggle, SinkMode
Control-plane attachment — gateway registers with a CP at boot, opens an agent Channel, ships per-tool-call samples.gateway.control_plane + cp-attached Cargo featureControlPlaneAttachConfig
Notification filter — server-side filtering of tools/list_changed etc. before broadcast.mcp.capabilities.resources[].watch.notification_filter / mcp.capabilities.resource_templates[].watch.notification_filterNotificationFilterConfig
Plugin config overlay — operator-staged dynamic config delivered via config-provider plugins (consul / k8s ConfigMap / etc.).gateway.config_overlayConfigOverlayConfig

Multi-tenant deployments

MCPG has no top-level tenants: block by design. Per-tenant differentiation composes from three orthogonal primitives that already exist:

  1. Tenant identity. Comes off the verified principal — identity.subject_id, identity.attributes.<claim>, identity.roles[], identity.groups[]. Whichever OIDC claim represents your tenant (commonly tid, org_id, or a custom claim) ends up under identity.attributes.<claim> once the inbound JWT verifier resolves it. No config knob is needed for the tenant ID itself.

  2. Per-tenant binding allowlist. A CEL predicate references the principal's tenant claim. A governance.policy.tool_access.rules[] entry names one exact tool, never a pattern, so a whole name prefix is matched in the global cel_allow_if, which runs for every call and every list entry (a single binding can instead carry its own governance.allow_if):

    yaml
    governance:
      policy:
        tool_access:
          default_minimum_trust: verified
          cel_allow_if: >-
            (!tool_name.startsWith("acme.")
              || ("tenant" in identity.attributes && identity.attributes["tenant"] == "acme")
              || "platform" in identity.groups)
            && (!tool_name.startsWith("partner.")
              || ("tenant" in identity.attributes && identity.attributes["tenant"] == "partner"))
          # shared.* passes both clauses: any verified caller may use it.
    

    The CEL predicate runs pre-dispatch alongside trust-floor checks, so a deny is observable through the same audit shape (mcpg.policy.tool_call.denied) as any other policy denial.

  3. Per-tenant rate limit + quota. Lives in the rate-limit plugin (rate limiting is plugin-only). The plugin's config is keyed by tenant identity from the same identity surface. Example shape (rate-limit plugin's config; the actual fields depend on the plugin you load):

    yaml
    plugins:
      - id: dev.mcpg.builtin.rate_limit
        config:
          default:
            tools_per_minute: 1000
          by_tenant:
            acme: { tools_per_minute: 10000, burst: 200 }
            partner: { tools_per_minute: 100, burst: 10 }
          tenant_key: 'identity.attributes.tenant'
    

    Per-binding rate-limit references go through the backend's own per-use slot (point-of-use wiring) — same shape as any other plugin reference.

The pre-existing gateway.server.max_sessions_per_tenant knob is the one gateway-resident tenant-aware quota; it's enforced inside the session store (per-tenant cap on concurrent sessions) and works regardless of which CEL gate let the request through.

A first-class tenants: block that desugars into the above is a possible future addition. Until the recipes turn out painful in operator hands, the existing primitives stay the source of truth — adding a parallel mechanism would split tenant config across two places.


Templating and secret resolution

MCPG uses a single CEL-based expression syntax everywhere — ${...} outer markers wrap a CEL expression. There's exactly one form for environment variables, identity, arguments, OAuth tokens, and credential lookups; no parallel layers.

yaml
mcp:
  capabilities:
    tools:
      - name: github.user.repos.list
        description: Fetch a user's repositories from GitHub.
        backend:
          kind: http
          url: "https://api.github.com/users/${arguments.username}/repos"
          method: get
          headers:
            Authorization: "Bearer ${env.GITHUB_TOKEN}"
            X-Trace-Id: "${context.principal_id}-${arguments.username}"

gateway:
  plugin_registry:
    auth:
      username: "${env.GHCR_USERNAME}"
      password: "${env.GHCR_TOKEN}"

plugins:
  # Outbound OAuth 2.0 (RFC 6749 client_credentials) lives behind
  # the `dev.mcpg.credential.oauth-client-credentials` plugin.
  # Bindings reference issued tokens via the standard `cred://`
  # URI scheme — see the table below.
  - id: dev.mcpg.credential.oauth-client-credentials
    config:
      providers:
        analytics:
          token_url: "https://auth.example.com/oauth/token"
          client_id: "mcpg-prod"
          client_secret: "${env.ANALYTICS_CLIENT_SECRET}"
          scopes: ["read:events"]

Available roots:

RootResolved atNotes
env.<NAME>Config-load (once)Process environment. Errors if unset.
secret.<NAME>Config-load, re-read on every reloadFile <gateway.secrets.dir>/<NAME>, the secrets directory mounted into the gateway. Errors if the directory is unset or the file is unreadable. The secrets watcher reloads the gateway when a file changes.
arguments.<key>Per requestTool-call arguments.
identity.<field>Per requestsubject_id, attributes.<key>, roles[N], groups[N], …
cred://<plugin_id>/<target>[#part]Per requestCredential plugin lookup. Covers outbound OAuth tokens (cred://dev.mcpg.credential.oauth-client-credentials/<provider>), Vault dynamic DB creds (cred://vault-dynamic-db/orders#username), and any other registered credential_issuer plugin.
context.<field>Per requestTransport, principal, trust level, etc.
tool_namePer requestCurrent tool's MCP name.
steps.<id>.outputPipeline onlyPrevious step's result.

env.X is resolved once at config-load — restarts pick up new values. secret.X is resolved at config-load too, but the gateway watches the directory and reloads itself when a file changes, so a rotated file reaches new calls within one poll interval. Everything else is per-request, so a token rotation reaches in-flight calls on the next dispatch without a reload.


Reference

What follows is the full alphabetical reference of every type reachable from AppConfig, generated from /// rustdoc + #[serde(...)] annotations. Use mcpg config explain <field> to drill into a single field on the command line.

Top-level structure (AppConfig)

Every field on the root AppConfig, alphabetised. Click a type to jump to its per-block reference below.

FieldTypeDefaultSummary
cloudCloudConfig(see type)cloud: — managed-fleet (mcpg.cloud) identity + placement. Absent for self-host; inert when present-but-empty, so the gateway binary is byte-identical whether or not it runs in the cloud. Server-managed fields (instance_id, subdomain, provenance.*) are stamped by the provisioner/operator and ignored if hand-written.
clusterClusterConfig(see type)Cluster coordinator. Singleton: the operator picks one coordinator and configures it inline. Default is the built-in single-node coordinator — safe for single-instance deployments. Other kinds map to mcpg-plugin-cluster-* cdylib plugins; the cdylib must still be declared under plugins[] for the gateway to load it. The inline cluster block is the single source of truth for the coordinator's runtime config — it overrides any config: block on the matching plugins[] row.
credentialsCredentialsConfig(see type)credentials: — the gateway-side L1 credential cache for cred:// URI substitution (sizing, per-entry TTL cap, the key_attributes cache-key dimension) plus the optional cluster pub/sub wrapper. Defaults are safe for single-node; a multi-instance deploy issuing per-caller dynamic credentials configures credentials.cluster to keep peer caches consistent.
debugDebugConfig(see type)Operator-defined diagnostic tools (mcpg.command.* / mcpg.network.*) plus their probe profiles. The block is fully ignored unless feature_flags.debug_tools_enabled is true; production deploys keep that flag off and treat this block as scaffolding for CI / dev rollouts.
feature_flagsFeatureFlagsConfig(see type)Operator-controlled strictness / compatibility flags. Every flag defaults off; flipping one is an explicit acknowledgement that the operator is taking on the risk the default protects against. Collapsing them into this block lets them show up in the curated reference + JSON Schema and audit-emit when active.
gatewayGatewayConfig(see type)gateway: umbrella — the binary's network face: listener (server), admin surface (admin), Control Plane attachment (control_plane).
governanceGovernanceConfig(see type)governance: umbrella — tool-call lifecycle: identity (access) → authorization (policy) → human gate (approvals) → evidence (audit). Co-located under one umbrella so the governance story reads as a coherent block.
licenseLicenseConfig(see type)license: — offline license token (or the non-production declaration). The license gate refuses entitlement-gated plugins and feature-gated config blocks (interactive sign-in, DPoP, rich authorization requests) the resolved envelope does not admit. On a gateway mcpg.cloud delivers a license to (a delivery its proof shows mcpg.cloud made) this block is ignored, and the envelope is the org license it delivers; under a customer's own control plane the block counts. Every license verifies against the MCPG issuer keys compiled into the binary.
mcpMcpConfig(see type)mcp: namespace — the MCP protocol surface. Two children: capabilities: (tools / prompts / resources / resource_templates / tasks / elicitation / sampling / roots — what the server advertises in initialize) and configurations: (sessions / pipelines / subscriptions / delivery / cancellation — runtime-emergent state). Capability persistence (store: / bus:) defaults to kind: cluster — the cluster coordinator's primitive — and can be overridden per capability with kind: memory / file.
observabilityObservabilityConfig(see type)All observability concerns — log/metric/trace emission, the binding-backend health prober, and the sink fan-out routing for telemetry / log events. Sub-fields all default to safe single-node values so the block is fully optional.
pluginsarray<PluginEntryConfig>[]Loaded plugin entries — flat array, no wrapper. Each entry is self-contained (id / class / source / signature / config / limits / enforce / granted_capabilities / observability / http_route / disabled). Identity / policy / credential / catalog / cluster plugins all dispatch via the class: field. An empty array is the kill switch — no plugins are loaded.
schema_registrymap<string, SchemaEntry>{}Named JSON Schemas operator-declared once, referenced by {"$schema_ref": "<name>"} in any binding's input_schema: / output_schema:. Named schema_registry: (rather than schemas:) to disambiguate from the per-binding schema fields (input_schema:, output_schema:). Each entry (SchemaEntry) is inline / file / url.
storageStorageConfig(see type)storage: block. Holds operator-declared content-store providers AND the gateway-managed LLM response cache. Each provider entry produces a named ContentStore in the gateway's registry; bindings reference providers by id via their own content_storage: field. When providers is empty AND no binding declares a content_storage: route, the gateway auto-creates a single in-process provider with id default and the standard 256 MiB cap.
usage_reportingUsageReportingConfig(see type)usage_reporting: — anonymous adoption ping. A minimal, vendor-facing, opt-out signal (product version + first-party plugin set) so we can see how the community grows. Wholly distinct from observability: (the operator's own OTel/metrics/log sinks). Fail-open, schema-pinned, and self-suppressing when air-gapped / licensed / CP-attached / CI; also disabled by DO_NOT_TRACK / MCPG_TELEMETRY=off.

Per-block reference

Every type reachable from AppConfig, alphabetised. Field tables show type, default, and the field's /// doc-comment summary.

AauthResourceMetadataConfig

AAuth resource metadata (/.well-known/aauth-resource.json, draft-hardt-oauth-aauth-protocol "Resource Metadata"). Field names follow the spec so an operator can paste a spec-shaped document; unrecognized spec fields ride through extra verbatim.

FieldTypeDefaultSummary
accept_signature_algsarray<string>(see type)Exactly the fully-specified JWS algorithms the verifier accepts. Defaults to what dev.mcpg.identity.aauth implements.
access_modestring"agent-token"The credential flow agents should expect. Defaults to agent-token (identity-based access — what dev.mcpg.identity.aauth verifies); set person-token when the plugin's person-token mode is the primary credential.
additional_signature_componentsarray<string>Covered components agents MUST include beyond the profile's base set. Mirror the identity plugin's additional_covered_components.
insecure_dev_modebooleanDevelopment escape hatch for the revocation endpoint's outbound person-server key fetch: admit http:// and private addresses. NEVER set in production.
issuerstringThe gateway's AAuth server identifier — HTTPS scheme+host only, no port/path/trailing slash, lowercase (e.g. https://gw.example). MUST match the origin the document is fetched from.
resource_token_ttl_secsinteger300Resource-token lifetime, seconds (protocol ceiling 300).
scope_descriptionsmap<string, string>Scope values this resource grants, each with a Markdown description the person server shows at consent. A resource token may only request scopes declared here (plus the standard OpenID identity scopes); tool required_scopes should name values from this map.
signature_windowinteger (optional)Signature validity window advertised to agents, seconds. Mirror the identity plugin's signature_window_secs when overriding the 60 s default.
signing_keyAauthSigningKeyConfig (optional)The Ed25519 key that signs resource tokens (aa-resource+jwt); its public half is published at jwks_uri. Required for access_mode: auth-token, unused otherwise.

AauthSigningKeyConfig

Where the AAuth resource signing key comes from — exactly one source.

FieldTypeDefaultSummary
ephemeralbooleanGenerate a fresh key at every start. Development only: tokens minted before a restart cannot be verified after it.
seedstring (optional)The seed inline as unpadded base64url (typically ${env.X}).
seed_filestring (optional)File holding the 32-byte Ed25519 seed — raw bytes or base64url text.

AccessConfig

FieldTypeDefaultSummary
authorization_serverAuthorizationServerConfig (optional)Embedded Enterprise-Managed Authorization server (MCP io.modelcontextprotocol/enterprise-managed-authorization). When set, the gateway acts as the OAuth Resource Authorization Server for ID-JAG grants: it serves RFC 8414 metadata at GET /.well-known/oauth-authorization-server advertising the urn:ietf:params:oauth:grant-profile:id-jag grant profile, and redeems Identity Assertion JWT Authorization Grants issued by the configured trusted enterprise IdPs at POST /oauth/token (urn:ietf:params:oauth:grant-type:jwt-bearer), minting audience-restricted access tokens the gateway itself accepts. With a trusted_idps[].login block it also signs users in interactively: an authorization endpoint (authorization_code with PKCE) that sends the user to that IdP, rotating refresh tokens, and optional dynamic client registration (see interactive). Without one there is no authorization endpoint, no refresh token and no registration endpoint. Requires resource_metadata, which is how clients discover this server.
dpopDpopConfig (optional)DPoP (RFC 9449) for the access tokens that oidc_oauth or jwks verifies, such as a Personal Agent Protocol Company's Session Tokens. With enabled: true, a token presented with Authorization: DPoP is verified like a Bearer token and must carry cnf.jkt (in the JWT, or in the introspection response); its one DPoP proof must pass every check of RFC 9449 §4.3 (typ, an allowed asymmetric alg, a public key only, the signature, htm and htu against the request under a resource_metadata resource, iat within proof_max_age_secs, and ath), be of that key, and be unspent; it is then recorded in the replay ledger every replica shares. A token with cnf presented with Bearer is refused, and a Bearer token without cnf keeps working unless required is set. The caller carries the key's thumbprint as the dpop_jkt attribute, and the protected resource metadata publishes dpop_signing_alg_values_supported. nonce must stay off: no key is there to derive nonces from. Tokens of authorization_server follow authorization_server.dpop instead. Requires oidc_oauth or jwks, and resource_metadata; enabled: true requires a licence with the oauth.dpop feature.
jwksJwksConfig (optional)
oidc_oauthOidcOAuthConfig (optional)Inbound OIDC: verifies Bearer tokens against each provider. A provider's claim_mappings.attribute_claim_mappings may not map to an attribute that only a credential of this gateway sets (token_issuer, grant_type, grant_id, dpop_jkt, authorization_details, authorization_details_types) or to a name starting with subject_token: such a configuration is refused at load, whether DPoP and authorization details are on or not. An identity plugin's attributes of those six names are dropped.
personal_agentPersonalAgentConfig (optional)Serve as a Personal Agent Protocol Company's MCP server: read the Session Token's PAP values from identity attributes, answer a signed-out Personal Agent a tool's scopes refuse with sign_in_required (403) instead of insufficient_scope, read the extensions a Personal Agent lists, refuse one without require_extensions, serve poppy.json, and turn chosen tools into operations the Personal Agent confirms. Requires oidc_oauth or jwks; only oidc_oauth maps claims to identity attributes, so with jwks alone every caller is a plain MCP client.
require_authenticationbooleanfalseRefuse every MCP request (POST, GET and DELETE on the MCP path, every JSON-RPC method including initialize, */list and server/discover) from a caller below verified with HTTP 401 and the WWW-Authenticate: Bearer resource_metadata="…" challenge. An OAuth or EMA client that authenticates only after a 401 starts its flow from that challenge; with this off, an anonymous initialize succeeds and such a client never authenticates. A request whose Origin is not allowed is still refused with 403 first. Only the MCP path is covered: the well-known metadata, the embedded authorization server's /oauth/* endpoints (token, jwks, revoke, register, authorize, consent, callback, connect), the AAuth resource endpoints, health, readiness, metrics, /runtime, /v0.1/servers, the /webhooks/* callbacks and the plugin HTTP routes (/plugins/{id}/{entity} and override paths) stay open to anonymous callers, each gated as it is without this key. Needs at least one verifier: jwks, oidc_oauth, authorization_server, an identity_provider plugin, or a transport plugin in gateway.server.transports that attests callers (such as dev.mcpg.transport.tailscale).
resource_metadataOAuthResourceMetadataConfig (optional)OAuth 2.1 Protected Resource Metadata (RFC 9728), served at GET /.well-known/oauth-protected-resource and its path-aware form. Without it that endpoint answers 404: the gateway never guesses its public resource from the bind address. Required with authorization_server.

AdminAuthConfig

Variants:

  • static_bearer

    • bearer_token_env: string
    • read_bearer_token_env: string (optional)
  • trusted_header — Security: trusted-header mode requires a value match via trusted_value_env. Header-presence-only is insecure and generates warnings on every request.

    • header_name: string
    • trusted_value_env: string (optional)
  • disabled

AdminConfig

FieldTypeDefaultSummary
authAdminAuthConfig(see type)
base_pathstring"/admin/v1"
bind_addressstring"127.0.0.1:9090"
disclosureDisclosureLevel"summary"
enabledbooleanfalse

AdmissionOnError

The decision when the admission tool gives none.

Allowed values:

  • deny
  • allow

AgentExtensionsConfig

Where a Personal Agent's extensions are read.

FieldTypeDefaultSummary
allow_private_networkbooleanfalseFetch client metadata documents from private addresses too. For a test network only.
allowed_hostsarray<string>[]Hosts a client metadata document may be fetched from; empty allows any public host.
attributestring"agent_extensions"The identity attribute read with source: attribute.
cache_secsinteger3600How long a fetched document is used, in seconds (60–86400); its Cache-Control: max-age shortens it.
sourceAgentExtensionsSource"client_metadata"client_metadata: from extensions of the client metadata document at the agent's client_id URL, fetched over https without redirects, private addresses refused, and cached. attribute: from the identity attribute attribute, a JSON object of name to { "version": … } or a space-separated list of names. none: no agent lists any.

AgentExtensionsSource

See [AgentExtensionsConfig::source].

Allowed values:

  • client_metadata
  • attribute
  • none

Align

Horizontal cell alignment.

Allowed values:

  • start
  • center
  • end

AppColumn

A table column bound to a JSON-path over each row.

FieldTypeDefaultSummary
alignAlign (optional)
fieldstringJSON-path into the row object.
formatColumnFormat"text"
headerstring (optional)
visible_ifstring (optional)Client-evaluated visibility expression over the row.
widthstring (optional)

AppCspDecl

Per-app author CSP declaration. Each axis is still intersected by the egress csp_policy; declaring an axis can only narrow, never widen.

FieldTypeDefaultSummary
connect_domainsarray<string>
frame_domainsarray<string>
redirect_domainsarray<string>
resource_domainsarray<string>

AppDensity

Layout density.

Allowed values:

  • comfortable
  • compact

AppField

A detail/form field bound to a JSON-path.

FieldTypeDefaultSummary
fieldstring
formatColumnFormat (optional)
labelstring (optional)

AppProvidedTool

A read-only App-Provided Tool: surfaces client state to the agent.

FieldTypeDefaultSummary
descriptionstring (optional)
namestringAdvertised name; the host sees it auto-prefixed app.<id>.<name>.
sourceAppToolSourceWhich whitelisted client reader backs this tool.

AppRowAction

A per-row action: a tools/call with arguments mapped from the row.

FieldTypeDefaultSummary
arg_mapmap<string, string>argName → JSON-path over the row.
confirmstring (optional)Optional confirmation prompt before firing.
idstring
labelstring
toolstringThe tool this action invokes (re-enters the full pipeline).

AppTheme

Accent + density theming hints for the shell.

FieldTypeDefaultSummary
accentstring (optional)
densityAppDensity (optional)

AppToolSource

The whitelisted read-only client state sources for App-Provided Tools.

Allowed values:

  • selection
  • visible_rows
  • form_draft
  • map_viewport

ApprovalsConfig

Tool-gate human approval configuration.

FieldTypeDefaultSummary
callback_base_urlstring (optional)Public base url the gateway hands to notifiers as the callback URL prefix (e.g. "https://gw.example.com"). The runtime appends /webhooks/approvals/<id>?expires=...&sig=....
callback_grace_msinteger60000Seconds beyond deadline_at during which late callbacks still authenticate. Defence-in-depth — the registry's own deadline timer already rejects late resolutions; this just keeps the URL valid for short retries. Default 60s.
signing_key_envstring (optional)Env var name from which the gateway reads the HMAC signing key for approval callback URLs. The key MUST be at least 32 bytes (256 bits) — the gateway hard-fails boot if shorter. When unset, the gateway falls back to a random per-process key (callbacks won't survive a restart).

AppsConfig

apps: config — SEP-1865 MCP Apps support.

MCP Apps lets a server attach an interactive HTML UI to a tool. The ui/* postMessage protocol runs host↔iframe and never reaches the gateway; MCPG's role is passthrough of _meta.ui, capability advertisement (both downstream to clients and upstream to federated servers), federation resourceUri rewriting, and a tighten-only CSP/permission policy layer.

Off by default. When enabled: false, MCPG omits the io.modelcontextprotocol/ui extension from its capability advertisement and applies no policy — but _meta.ui still round-trips on tool/resource descriptors (passthrough is wire-shape, not capability-gated, so a client with a cached template keeps working). When enabled: true, advertisement lights up and the csp_policy / allowed_permissions / allowed_domains clamps below are enforced on egress.

FieldTypeDefaultSummary
allowed_domainsarray<string> (optional)If set, a _meta.ui.domain outside this list is dropped (the host falls back to its default sandbox origin); in strict mode the whole response is rejected instead. None ⇒ any domain allowed.
allowed_permissionsarray<AppsPermission>(see type)iframe permissions MCPG will let through; any _meta.ui.permissions key outside this list is stripped on egress. Default: all four standard permissions.
csp_policyAppsCspPolicy(see type)CSP upper bound. Each axis is intersected (never unioned) with the upstream's declared _meta.ui.csp. ["*"] on an axis imposes no bound on that axis (upstream passes through). An omitted axis on the upstream is left omitted (the host applies its restrictive default — frame-src 'none' / base-uri 'self'); policy never materializes an absent axis.
enabledbooleanfalseMaster switch for DOWNSTREAM advertisement + egress policy. Default false (opt-in).
federate_upstreamboolean (optional)Advertise the Apps capability on MCPG's OUTGOING (client→upstream) initialize so federated servers emit their UI-enabled tools. A spec-compliant upstream checks the client's io.modelcontextprotocol/ui capability before registering UI tools — omit this and such an upstream withholds every UI tool. None ⇒ inherit enabled. Set true explicitly to pull UI tools from upstreams while still withholding the capability from your own clients.
registryarray<GatewayAppConfig>Gateway-authored templated apps. Each entry mints a ui://mcpg/<id> resource whose behavior is driven by this config; the gateway ships the HTML, the operator supplies only the binding. Empty ⇒ no authored apps (pure proxy posture). Non-empty requires enabled: true.
strictbooleanfalseReject (vs sanitize) an upstream response whose _meta.ui escaped the policy below — a domain/permission/CSP entry outside the operator allow-list. Default false (permissive: narrow + log, never reject).

AppsCspPolicy

The CSP-axis allow-lists. Defaults mirror a sensible middlebox posture: no bound on what the app may fetch/connect/redirect to (["*"]), but frame embedding and <base> pinned to self.

FieldTypeDefaultSummary
base_uri_domainsarray<string>(see type)
connect_domainsarray<string>(see type)
frame_domainsarray<string>(see type)
redirect_domainsarray<string>(see type)Allow-list for openExternal redirect targets. Clamps only the OpenAI openai/widgetCSP.redirect_domains alias (there is no _meta.ui.csp axis for it). Default ["*"] ⇒ no bound.
resource_domainsarray<string>(see type)

AppsPermission

A standard iframe permission. Serialized snake_case in config; maps to the camelCase _meta.ui.permissions key on the wire.

Allowed values:

  • camera
  • microphone
  • geolocation
  • clipboard_write

AuditArguments

See [EventTypeAuditConfig::arguments].

Allowed values:

  • hash
  • raw
  • none

AuditConfig

governance.audit: block. Kept apart from observability:, with an inner schema aligned to the OTel signal-triad sinks-list pattern (logs / metrics / traces). Spec §9.12 defines the semantics; the fields here are the Rust projection.

Audit sinks fan out via sinks: [{kind, config, level?}]. The built-in dev.mcpg.builtin.audit.local-file activates only when its plugin id appears in sinks[].kind (there is no disable_builtins toggle — just omit the sink to disable it).

FieldTypeDefaultSummary
emit_tool_call_allowedbooleantrueEmit mcpg.tool.call.allowed after every successful pre-dispatch tool_gate chain. Default true for the compliance posture most operators want — every tool call on record. High-volume / low-compliance deploys can set false; deny + challenge paths still emit regardless.
emit_tool_call_completedbooleantrueEmit mcpg.tool.call.completed after every successful post-dispatch tool_gate chain. Default true. Records execution_duration_ms for auditors flagging long-running calls.
enabledbooleantrueMaster toggle for the audit channel. When false, no audit sinks are registered (built-in or plugin), no audit events are emitted, and required is ignored. Default true. Set false only for dev/test runs where compliance is out of scope.
on_failureAuditOnFailure"fail_closed"Policy for per-event emit failures. Today the fan-out is always best-effort at the registry level (failures are metricsed but don't block the request). This field is captured + validated so the operator's intent is durable; the runtime behavior hookup lands in a future improvement (the emit site needs to translate this into "return error from the tool-gate chain" on fail_closed).
requiredbooleantrueWhen true (default), the gateway REFUSES TO START unless at least one audit sink is serving traffic after plugin registration completes. Ignored when enabled: false. Operators explicitly set false only for dev / CI runs where compliance is not in scope.
sinksarray<AuditSinkConfig>(see type)Audit-sink fan-out. Each entry's kind: is a plugin id resolved against the registered audit sinks at boot. The built-in dev.mcpg.builtin.audit.local-file is the canonical default — listed in [AuditConfig::default].

AuditOnFailure

Operator policy when an audit-sink emit fails.

Variants:

  • fail_closed — (default) On emit failure, refuse to serve the triggering request. Compliance-safe: no action happens without a durable audit trail. Subtle: this can wedge the gateway if every registered sink is broken — operators should always configure at least one sink whose availability they actually monitor.

  • fail_open — On emit failure, log + continue. The triggering request completes even if the audit event does not persist. Dev / CI use only — a compliance auditor will not accept this as SOC2-clean.

AuditSinkConfig

One entry of governance.audit.sinks.

FieldTypeDefaultSummary
configanySink-specific config object. Schema depends on kind:.
gatingbooleantrueA gating sink (the default) is awaited: under on_failure: fail_closed a record it loses refuses the operation, and it satisfies required. false makes the sink best-effort: it gets each record in the background, never delays or refuses a request, and drops (and counts in mcpg_audit_sink_dropped_total) what it cannot keep up with.
kindstringPlugin id (or plugins[] alias) of the audit sink.
levelstring (optional)Per-sink severity floor; parsed, not enforced.
routingAuditSinkRoutingWhich records this sink receives.

AuditSinkRouting

Action filters of one audit sink. Patterns are globs over the record's action (* matches any run), e.g. mcpg.resource.*.

FieldTypeDefaultSummary
allow_actionsarray<string>Only these actions; empty takes every action.
deny_actionsarray<string>Never these actions; wins over allow_actions.

AuthConfig

How MCPG authenticates to the upstream.

FieldTypeDefaultSummary
credentialstring (optional)Credential-issuer reference for oauth_client_credentials: a standard cred://<plugin_id>/<target> URI, e.g. cred://dev.mcpg.credential.oauth-client-credentials/notion. The referenced issuer plugin mints + refreshes the upstream bearer; no client secret lives in the federation config.
credential_configanyPer-issuance config object forwarded verbatim to the credential issuer on the OAuth modes (a template issuer's audience / resource / redeem_token_url overrides). Registry OAuth discovery populates this on synthesized federations; hand-written federations may set it to steer a template provider without a per-target issuer entry.
importAuthConfig (optional)Credential for the upstream sessions that run without a caller: the catalogue import, its list_changed and TTL refreshes, and the notification listener. mode must be service_token or oauth_client_credentials. Tool calls, resource reads and prompt fetches never use it; they keep the outer mode. Unset, those sessions authenticate with the outer mode when it needs no caller, and connect anonymously for pass_through and oauth_impersonation. The imported catalogue is what this credential can list, so per-user tool visibility upstream is not reflected in it.
modeAuthMode"none"
subject_tokenSubjectTokenoauth_impersonation only: the subject token the credential issuer exchanges. caller_bearer (the default) is the caller's own bearer token; a token this gateway minted is never sent upstream, so a caller who signed in here, or redeemed an ID-JAG here, is refused with it. idp_refresh_token and idp_id_token are the caller's enterprise IdP sign-in the gateway keeps (the refresh token, or an ID token kept fresh with it), for an Okta Cross App Access upstream through dev.mcpg.credential.oauth-id-jag; they need a governance.access.authorization_server.trusted_idps[].login block, credential must name dev.mcpg.credential.oauth-id-jag or dev.mcpg.credential.oauth-token-exchange (the issuers that send the stored token only to the IdP that issued it), and that issuer's IdP token URL and client must be the login's. A caller with no stored sign-in gets an error naming /oauth/connect, where they sign in once. Refused inside import, whose sessions have no caller.
tokenstring (optional)Static bearer token for service_token (supports ${env.X}).

AuthMode

Identity-propagation mode: what the gateway presents to the upstream.

Variants:

  • none — No auth sent.

  • service_token — Static bearer token.

  • pass_through — Forward the inbound Authorization header verbatim.

  • oauth_client_credentials — Machine-identity token via an OAuth provider.

  • oauth_impersonation — Per-caller token-exchange (RFC 8693).

AuthorizationDetailLocations

What the locations of an authorization details object may name.

Variants:

  • resource — A resource identifier of this server.

  • any — Any string.

AuthorizationDetailsConfig

Rich Authorization Requests (RFC 9396) at the embedded authorization server (governance.access.authorization_server.authorization_details). Off while types is empty: an ID-JAG that carries authorization_details is then refused (invalid_grant), and the authorization_details request parameter is ignored. Turn it on only after every replica runs a build that knows this block: a replica without it drops the details of a sign-in's grant when it rotates the grant's refresh token, and the grant's next tokens carry none. Turning it on (a non-empty types) requires a license with the oauth.rich_authorization feature; a block without a type needs none.

FieldTypeDefaultSummary
max_entriesinteger16Most objects one authorization_details array may hold (1–64). The array is also limited to 8 KiB of JSON.
typesarray<AuthorizationDetailsTypeConfig>[]The authorization details types this server accepts. Non-empty turns RFC 9396 on: the authorization_details parameter of the authorization endpoint (shown on the consent page, which is then always shown unless the client's consent is skip, and never remembered) and of the token endpoint (which may only narrow what was granted); the authorization_details claim of an ID-JAG (every object must be valid, or the assertion is refused with invalid_grant); the granted details in the token response and in the access token's authorization_details claim; the identity attributes authorization_details (compact JSON, which audit records carry only as keyed-blake3: and its digest under a key derived from the first signing key, so the value cannot be guessed back from it) and authorization_details_types (the distinct types, space-separated), and the policy binding identity.authorization_details, a list of maps (for example identity.authorization_details.exists(d, d.type == "mcp_tool" && "tools/call" in d.actions)); and authorization_details_types_supported in the authorization server metadata and the protected resource metadata. A parameter that fails a rule is refused with invalid_authorization_details. An authorization request with details and no scope is granted no scope. Non-empty requires a license with the oauth.rich_authorization feature.

AuthorizationDetailsTypeConfig

One authorization details type the server accepts.

FieldTypeDefaultSummary
actionsarray<string> (optional)The values actions may hold. Unset: any.
datatypesarray<string> (optional)The values datatypes may hold. Unset: any.
descriptionstring (optional)What the consent page shows for an object of this type (1–120 characters). Defaults to the type.
locationsAuthorizationDetailLocations"resource"What locations may name: resource (the default), one of this server's resource identifiers (resource_metadata.resource and its additional_resources, a trailing / ignored); or any value.
privilegesarray<string> (optional)The values privileges may hold. Unset: any.
schemaanyA JSON Schema every object of this type must satisfy, inline. Local $refs only; its depth and size are bounded. Without it, an object may carry only type, locations, actions, datatypes, identifier and privileges (RFC 9396 §2.2); with it, any member the schema admits. locations, actions, datatypes and privileges are arrays of at most 64 non-empty strings and identifier is a string either way.
typestringThe RFC 9396 type, compared exactly: 1–256 characters, no whitespace or control characters, each type listed once.

AuthorizationServerClientConfig

One OAuth client registered with the embedded authorization server.

FieldTypeDefaultSummary
accept_token_endpoint_audiencebooleanfalseprivate_key_jwt: also accept an assertion whose aud is the token endpoint URL ({issuer}/oauth/token), as clients written for Okta send it. Otherwise aud must be the issuer exactly, its only value.
allow_private_networkbooleanfalseLocal-development escape hatch for jwks_uri: permit http:// and private/loopback addresses. Production deployments leave this false.
client_idstringThe client identifier the enterprise IdP binds into ID-JAGs (client_id claim). For an MCP client that identifies with a Client ID Metadata Document, the document URL: this entry then stands in for the document, which is never fetched.
client_namestring (optional)The client's name on the consent page (1–80 characters). Defaults to the client_id.
client_secretstring (optional)Shared secret for client_secret_basic / client_secret_post. Supply via ${env.X} or ${secret.NAME}.
consentClientConsent"auto"When the consent page is shown before sign-in: auto (the default) skips it when every redirect URI is https:// and asks otherwise; always asks every time; skip never asks and is refused with a loopback redirect URI. A request with prompt=consent always asks.
dpop_bound_access_tokensbooleanfalseRFC 9449 §5.2: every token request of this client must carry a DPoP proof, and every token it receives is bound to the proof's key. A request without one is refused. Requires dpop.enabled.
grant_typesarray<ClientGrantType> (optional)The grants this client may use: urn:ietf:params:oauth:grant-type:jwt-bearer (ID-JAG redemption), authorization_code (interactive sign-in, which needs redirect_uris and a trusted_idps[].login block) and refresh_token (which needs authorization_code). Defaults to jwt-bearer alone without redirect_uris, and to [authorization_code, refresh_token] with them.
jwksanyprivate_key_jwt: the client's public keys inline, as an object with a keys array or the same document as a JSON string (for example ${env.CLIENT_JWKS}).
jwks_uristring (optional)private_key_jwt: URL of the client's JSON Web Key Set. Fetched keys are reused for 5 minutes and refetched on an unknown kid at most every 30 seconds; https:// only, no redirects, at most 1 MiB. Mutually exclusive with jwks.
redirect_urisarray<string>[]Interactive sign-in: where authorization responses may be sent (at most 10, each once). https:// URIs match byte for byte (case, port, trailing / and percent-encoding included). http:// only on 127.0.0.1, [::1] or localhost, written so, where any port matches (RFC 8252 §7.3): a native client picks its port at run time, so register it without one. A loopback URI's path and query are compared as written, so they may not hold dot segments or characters a browser rewrites. ASCII only, with no fragment, userinfo, wildcard, backslash or private-use scheme. A client with one URI that is not loopback may omit redirect_uri in its request.
token_endpoint_auth_methodClientAuthMethod (optional)How the client authenticates at the token endpoint: client_secret_basic (HTTP Basic), client_secret_post (client_id and client_secret form fields), private_key_jwt (a signed client_assertion, RFC 7523) or none (a public client). Defaults to both secret methods when client_secret is set, to private_key_jwt when jwks or jwks_uri is set, else to none. A request that authenticates any other way is refused (invalid_client). A private_key_jwt assertion carries iss and sub equal to the client_id, aud equal to the issuer, an exp at most 5 minutes ahead and a jti that is accepted once; it is signed with RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384 or EdDSA.

AuthorizationServerConfig

Embedded EMA authorization server (governance.access.authorization_server).

FieldTypeDefaultSummary
access_token_ttl_secsinteger3600Lifetime of minted access tokens, in seconds (1–86400). The IdP cannot revoke a minted token, and without interactive sign-in (which adds POST /oauth/revoke) neither can its client, so it stays valid for up to this long after the IdP revokes the user; values above 3600 draw a warning. A longer lifetime means fewer ID-JAG exchanges, which matters where the IdP meters them (Okta: 250 ID-JAGs per user, per resource app, per month).
allowed_scopesarray<string> (optional)When set, the scopes granted on minted tokens are the intersection of the ID-JAG's scope claim with this list (the resource server may narrow, never widen, IdP-granted scopes). When omitted, IdP-granted scopes pass through unchanged. A scope parameter on the token request narrows the grant further to the requested scopes. Requested scopes this server does not know — listed neither here, in resource_metadata.scopes_supported, nor in the ID-JAG — are ignored, and a request that names no known scope counts as one without scope. A request whose known scopes the grant holds none of is refused with invalid_scope.
authorization_detailsAuthorizationDetailsConfig(see type)Rich Authorization Requests (RFC 9396): grants and access tokens limited to fine-grained authorization_details of the types listed here. Off while types is empty. A non-empty types requires a license with the oauth.rich_authorization feature; a block without a type needs none.
client_id_metadata_documentsClientIdMetadataDocumentsConfig(see type)OAuth Client ID Metadata Documents (draft-ietf-oauth-client-id-metadata-document): an MCP client identifies itself with the https:// URL of a JSON document that describes it, instead of a registration.
client_rolesmap<string, array<string>>{}Roles added to identity.roles of every caller whose access token was minted for a client, keyed by client_id: a clients[].client_id, or a metadata document URL that client_id_metadata_documents.allowed_hosts admits. They apply to every user of the client, so they describe the client (for example ai-agent) rather than grant any one user something. Read on each request, so a change also applies to tokens already issued.
clientsarray<AuthorizationServerClientConfig>[]OAuth clients registered here: clients that redeem ID-JAGs at the token endpoint and clients that sign users in (redirect_uris, grant_types), each authenticating with its token_endpoint_auth_method. An ID-JAG's client_id claim must name the authenticated client. A request that uses more than one client authentication method is refused (invalid_request). A failed client authentication answers invalid_client with 401 and a Basic challenge after HTTP Basic, else with 400 (RFC 6749 §5.2). May be empty when client_id_metadata_documents.allowed_hosts admits clients by their metadata document instead.
clock_skew_secsinteger60Clock-skew leeway applied to ID-JAG exp/iat/nbf validation and to the expiry of minted tokens, in seconds (at most 300).
dpopDpopConfig(see type)DPoP (RFC 9449): access tokens bound to a key the client proves it holds. Off by default. enabled: true requires a license with the oauth.dpop feature; a block that leaves it off needs none.
enforce_single_usebooleantrueEnforce single-use ID-JAG redemption: an assertion (iss and jti) redeemed once is refused until it expires. Redemptions are recorded in the cluster coordinator's key-value store, so a replay is refused on every instance of a clustered deployment and across configuration reloads. When that store cannot be reached, redemption answers temporarily_unavailable rather than admit an unrecorded assertion.
interactiveInteractiveLoginConfig (optional)Settings of interactive sign-in, which a trusted_idps[].login block turns on; every key has a default. Refused without such a block. Requires a license with the sso.interactive_login feature.
issuerstringThis authorization server's issuer identifier (RFC 8414): the external origin the gateway is reached at, scheme://host[:port], with no path. The metadata and token endpoints are served at the root of that origin. Enterprise IdPs bind every ID-JAG to it as the aud claim, compared exactly, so it must be exactly the value entered as the authorization server's issuer in the IdP (Okta: the resource app's Issuer URL, which cannot change without resetting the connection), a trailing / included: an issuer that ends in / is published and compared with it. Also the iss of every access token this server mints. One issuer serves every resource identifier in resource_metadata, including additional_resources: keep it on the canonical origin. Use https://; http:// draws a warning outside loopback, since clients send assertions and client secrets to the token endpoint.
max_assertion_lifetime_secsinteger600Longest ID-JAG lifetime (exp − iat) accepted, in seconds (1–3600). Together with clock_skew_secs it also bounds how old an accepted assertion's iat can be. Okta issues ID-JAGs for 300 seconds.
rate_limit_per_mininteger120Refused token requests allowed per minute from one client IP address at POST /oauth/token, which POST /oauth/revoke and POST /oauth/register share. Every request takes from the budget and a successful redemption gives it back, so only refused requests (a bad assertion or client credential, an unknown client) spend it; the budget refills continuously and a full minute's worth may arrive at once. While it is spent, requests from that address are answered 429 with Retry-After and the OAuth error temporarily_unavailable. The client IP is the first X-Forwarded-For hop when gateway.server.trust_proxy_ip is set, else the connection's peer, so behind a proxy without trust_proxy_ip every client shares one budget. Clients behind one egress address — a NAT, or a hosted MCP client calling from its own servers — also share it. 0 disables the limit. Each replica counts its own requests, as for gateway.server.anonymous_rate_limit_per_min.
require_scopebooleanfalseRefuse a redemption whose granted scope set is empty with invalid_scope, instead of minting a token without scopes.
resourcestring (optional)Resource identifier (RFC 8707) minted access tokens are audience-restricted to when neither the token request nor the ID-JAG names a resource. Defaults to governance.access.resource_metadata.resource; keep it one of the resource identifiers resource_metadata advertises. A resource named by the token request or the ID-JAG must be this value, resource_metadata.resource or one of its additional_resources (a trailing / is ignored); a request's resource must also be among the ID-JAG's when it carries any. The token is minted for the one named, and the token response returns it as resource. Any other value fails with invalid_target.
signing_keysarray<SigningKeyConfig>[]Keys that sign and verify minted access tokens. The first entry signs every new token; every entry verifies the tokens that carry its kid, so a key rotation lists the new key first and keeps the old one until the tokens it signed have expired (access_token_ttl_secs). A token whose kid is not listed is refused. The public keys of the asymmetric entries (ES256, EdDSA, RS256) are published at GET /oauth/jwks, advertised as jwks_uri in the authorization server metadata, so other resource servers can verify the tokens; an HS256 secret is never published. Every gateway instance in a cluster must carry the same keys.
signing_secretstring (optional)HS256 signing secret for minted access tokens (≥ 32 bytes), a shorthand for one signing_keys entry {alg: HS256, secret: …}. Supply via ${env.X} or ${secret.NAME}. Set this or signing_keys, not both. To rotate to other keys without invalidating the tokens this secret minted, move it into signing_keys as an HS256 entry without a kid, after the new signing key.
trusted_idpsarray<TrustedIdpConfig>[]Enterprise IdPs trusted to issue ID-JAGs, and, for the one entry with a login block, to sign users in; at least one is required. An assertion whose iss is not listed here is refused (invalid_grant). The access tokens minted for an IdP's users are refused as soon as the IdP is removed, or its allowed_clients or required_tenant no longer admit them, and those minted for a client as soon as it can no longer authenticate here.

BackendAnnotationsConfig

Tool annotation hints configurable per binding. Maps to MCP ToolAnnotations.

FieldTypeDefaultSummary
destructiveboolean (optional)
idempotentboolean (optional)
open_worldboolean (optional)
read_onlyboolean (optional)

BackendConfig

FieldTypeDefaultSummary
annotationsBackendAnnotationsConfig (optional)
backendBackendImplImplementation backend — discriminated by kind:. The backend is an explicit nested object (backend: { kind: http, url: ... }) rather than fields hoisted onto the binding itself.
cacheKindRef (optional)Per-binding LLM response-cache override. Resolves via resolve_kind(SlotClass::Cache, ...) at boot:
content_storagestring (optional)Content-store provider this binding routes through. Must match one of the storage.providers: [{id, ...}] entries declared at the top level. When unset, the binding falls back to the provider id named in content_storage.default (or the conventional default id when neither is set).
descriptionstring
descriptor_metaany
governanceBackendGovernanceConfig(see type)
iconsarray<BackendIconConfig> (optional)MCP 2025-11-25 descriptor extensions — icons and free-form _meta. Populated directly on the tool/prompt/resource/template descriptor that this binding produces.
input_schemaany
mcp_app_urlstring (optional)MCP App URL — a link to a rich UI for this resource. Populated on _meta.mcpAppUrl in resources/list descriptors and resources/read results. Supports CEL interpolation for dynamic segments (e.g., https://app.example.com/docs/${arguments.id}). Only meaningful on kind: resource or kind: resource_template.
mime_typestring (optional)
namestring
output_schemaany
prompt_argumentsarray<PromptArgumentConfig> (optional)
quotasBackendQuotasRef (optional)Per-binding quota policy reference. Names at most one of each kind by id; each id must resolve to a registered policy in governance.quotas.{rate_limits,budgets,concurrency}[]. None (default) means this binding is exempt from quota enforcement. The runtime gate that consults this field is behind the governance-quotas cargo feature — until that feature is on, the field is parsed + validated but inert.
resource_annotationsBackendResourceAnnotations (optional)Optional resource annotations (audience, priority, lastModified) surfaced on resources/list entries. Meaningful only on kind: resource or kind: resource_template bindings.
resource_sizeinteger (optional)Optional per-resource size hint (bytes) surfaced on resources/list entries. Meaningful only on kind: resource bindings; ignored elsewhere.
retryRetryConfig (optional)
task_supportstring (optional)
titlestring (optional)
uristring (optional)
uri_templatestring (optional)
variable_completionsmap<string, VariableCompletionSource> (optional)Optional completion sources per template variable. The completion/complete handler returns the filtered subset matching the caller's prefix when the MCP client opens auto-complete on a resource template variable. Keys MUST match a {variable} declared in uri_template; mismatched keys are dropped at registration with a warning.
watchResourceWatchConfig (optional)

BackendGovernanceConfig

FieldTypeDefaultSummary
allow_ifstring (optional)
minimum_trustTrustLevelConfig (optional)The lowest caller trust level the capability accepts. When unset, a binding takes governance.policy.tool_access.default_minimum_trust, and a federation, a registry's defaults or an openapi source's auto-exposed capabilities take header_asserted.
required_scopesarray<string>Scopes the caller's credential MUST carry to invoke this binding (OAuth scope, or an AAuth auth token's scope). A caller short of them is answered with the step-up challenge naming the missing scopes (WWW-Authenticate … error="insufficient_scope"; for an AAuth person or auth-token caller, AAuth-Requirement: requirement=auth-token carrying a resource token). Empty (the default) means no scope requirement.

BackendIconConfig

Configurable descriptor icon shape, mirrored onto MCP's Icon type.

FieldTypeDefaultSummary
mime_typestring (optional)
sizesarray<string> (optional)
srcstring
themestring (optional)

BackendImpl

Implementation backend, identified by kind: with an opaque flattened spec. The gateway enumerates NO plugin kinds: a binding names a kind (a loaded BackendPlugin::kind() string) and every other key flattens into spec, forwarded verbatim to the plugin's register_profile / execute. The plugin owns and validates the schema; the gateway resolves kind against the registry at boot and fails closed on unknown / non-backend kinds. Mirrors WatchStrategyConfig::Plugin.

spec is an open object (no deny_unknown_fields); unknown keys are forwarded verbatim, so a spec-key typo is caught only by the owning plugin's register_profile (InvalidSpec at boot), not at config-load.

FieldTypeDefaultSummary
kindstringTarget BackendPlugin::kind() string, resolved against the registry at boot.

BackendQuotasRef

Per-binding quota reference — operator names at most one of each policy kind by id. The runtime gate that consults these refs is gated behind the governance-quotas cargo feature.

All three fields are independent options; an absent field means "no policy of that kind for this binding". A binding may name more than one kind at once (e.g. both a rate limit AND a concurrency cap), but at most one policy of each kind.

FieldTypeDefaultSummary
budgetstring (optional)Id from governance.quotas.budgets[].id. Optional.
concurrencystring (optional)Id from governance.quotas.concurrency[].id. Optional.
rate_limitstring (optional)Id from governance.quotas.rate_limits[].id. Optional.

BackendResourceAnnotations

Operator-configurable MCP ContentAnnotations for a resource binding.

FieldTypeDefaultSummary
audiencearray<string> (optional)
last_modifiedstring (optional)
prioritynumber (optional)

BudgetPolicy

One named budget policy (cost cap / call count / token count).

A budget is counted in the quota store with atomic counters per window, so it is one budget for every replica that shares the store. A call counts itself before it runs: cap_calls is exact. Tokens and cost are charged after the call from the usage its backend reports (the LLM chat and embedding backends report it; the cost comes from their rate card), so a call runs while the spend is under the cap and the call that crosses it can go over by its own spend.

FieldTypeDefaultSummary
acknowledge_unenforcedbooleanAcknowledge that a cost / token_count budget charges nothing on some of its bindings. A budget's tokens and cost are charged from the usage a backend reports with each result (the LLM backends report it). A cost/token_count budget referenced by a binding whose backend reports no usage loads only with this set, so the no-op is an explicit operator choice rather than a silent fail-open. No effect for call_count.
cap_callsinteger (optional)Call cap when kind: call_count. Required for that kind.
cap_token_countinteger (optional)Token cap when kind: token_count. Required for that kind.
cap_usdnumber (optional)Cost cap in USD when kind: cost. Required for that kind.
idstring
identity_claimstring (optional)JWT claim path for per_identity scope.
kindstring"cost"Budget kind: cost (USD), call_count, or token_count.
on_exceededstring"deny"Action when the cap is hit (same vocabulary as RateLimitPolicy).
scopestring"per_identity"Scope discriminator (same vocabulary as RateLimitPolicy).
warn_at_pctinteger (optional)Emit governance.quota.warn when drawdown crosses this percentage of the cap: once per window and measure, on the call or the charge that crosses it. Defaults to no warning. 0..=100.
windowstringWindow over which the cap applies, from the first call of the window; the next call after it ends opens a new window at zero. Suffixes s/m/h/d. Maximum 30d.

BuiltinEventSource

An event the gateway raises itself.

Variants:

  • mcp.task.updated — A task of the subscriber changed status. Delivered only to the principal whose session owns the task.

  • approval.requested — A tool gate asked a person to approve a call.

  • approval.resolved — An approval was granted, denied or ran out of time.

BusOverrideConfig

<capability>.bus: { kind, … } — produces an Arc<dyn mcpg_cluster_api::PubSub> at boot.

Recognised kind values: cluster, memory. (redis and nats are not accepted here — set cluster.kind: redis | nats and use kind: cluster here, or omit the override entirely.)

FieldTypeDefaultSummary
kindstring

CancellationConfig

cancellation: config — cluster-wide cancellation fan-out (notifications/cancelled and tasks/cancel) plus a per-capability bus: override. Same shape as DeliveryConfig.

FieldTypeDefaultSummary
busBusOverrideConfig (optional)
partition_by_principalbooleanfalseWhen true, cancellations publish to mcpg.cancel.<principal> and the subscriber listens on the mcpg.cancel.* wildcard, so broker-native subject ACLs can fence per-principal cancel traffic. Defaults to false (a single flat mcpg.cancel topic). Requires a wildcard-capable pub/sub backend (redis/nats) — AppConfig::validate rejects it on the in-process single-node / memory bus, which is exact-match only and would silently drop every cancellation under a wildcard subscribe.

ChildInvokeConfig

Governance for the agentic child-dispatch surface (invoke_tool).

Direct tools/call always runs the full pre-dispatch stack; the child path an LLM binding drives did not. When enforce_gates is on, child invocations are routed through the same external policy_engine chain and tool_gate plugin chain as a direct call before reaching the backend, so tool-level access controls are not silently absent on the LLM-driven surface.

FieldTypeDefaultSummary
enforce_gatesbooleanfalseRun the policy_engine chain + tool_gate plugin chain on every child invoke_tool. Default false (the agentic surface is ungated, matching prior behaviour) — enable to require the same authorization a direct tools/call gets. A child whose identity is unresolved (the LLM path carries no per-call principal today) evaluates against the inherited parent identity.

ClaimMappingConfig

FieldTypeDefaultSummary
attribute_claim_mappingsmap<string, string>{}Claim path (dotted, as realm_access.roles) to the identity attribute it fills. A string claim maps as it is, a number or boolean as its JSON text, an object or array as compact JSON.
group_claim_pathsarray<string>[]
role_claim_pathsarray<string>[]
scope_claim_pathsarray<string>(see type)
subject_claimstring"sub"

ClientAuthMethod

A token endpoint client authentication method (RFC 7591 §2).

Allowed values:

  • client_secret_basic
  • client_secret_post
  • private_key_jwt
  • none

ClientCertMode

Operator-facing client-cert acceptance mode.

Allowed values:

  • none
  • optional
  • mandatory

ClientConsent

When a registered client's user sees the consent page.

Variants:

  • auto — Skip it when every redirect URI is https://, else ask.

  • always — Always ask.

  • skip — Never ask; refused for a client with a loopback redirect URI, which any local program can receive.

ClientGrantType

A grant type a registered client may use.

Variants:

  • urn:ietf:params:oauth:grant-type:jwt-bearer — Redeem an ID-JAG (urn:ietf:params:oauth:grant-type:jwt-bearer).

  • authorization_code — Interactive sign-in with PKCE.

  • refresh_token — Refresh an interactive grant.

ClientIdMetadataDocumentsConfig

Client ID Metadata Documents at the embedded authorization server (governance.access.authorization_server.client_id_metadata_documents).

FieldTypeDefaultSummary
allow_private_networkbooleanfalseLocal-development escape hatch: permit http:// document and key URLs and private/loopback addresses. Production deployments leave this false.
allowed_hostsarray<string>[]Hosts whose https:// client_id URLs are accepted without a clients[] entry (an exact host, or a parent domain of it). The gateway fetches such a document with no redirects and at most 5 KiB, requires its client_id to equal the URL exactly, and reads token_endpoint_auth_method from it: none, or private_key_jwt with the document's jwks or jwks_uri, whose host must also be listed here. Without the member, a document that publishes keys is private_key_jwt and one that publishes none is none. A shared-secret method is refused. While dpop.enabled, a document's dpop_bound_access_tokens (RFC 9449 §5.2) is read: any value but false requires a DPoP proof on every token request of the client. The client_id URL must be in canonical form (lower-case host, no default port, no dot segments). A document is reused for its Cache-Control: max-age, kept between 60 seconds and 24 hours (5 minutes without one). A clients[] entry with the same client_id takes precedence, and its document is never fetched. Empty = registered clients only.
enabledboolean (optional)Advertise client_id_metadata_document_supported: true in the authorization server metadata, and resolve unregistered URL client_ids on allowed_hosts. An MCP client such as Claude identifies with its metadata document URL only when the metadata advertises this and none among the token endpoint authentication methods. Defaults to true when a clients[].client_id is an https:// URL or allowed_hosts is set, else false.
redirect_uri_policyRedirectUriPolicy"same_host"Interactive sign-in: which https:// redirect URIs a document may list. same_host (the default): only on exactly the host of its client_id URL. allowed_hosts: on any host allowed_hosts admits. Loopback redirect URIs (http://127.0.0.1, http://[::1], http://localhost, any port) are always allowed; the consent page is then shown on every sign-in. A listed URI that breaks the rule is never matched. A document signs users in only when it carries client_name and redirect_uris, response_types is absent or ["code"], and grant_types ([authorization_code] when absent) holds authorization_code; the client gets refresh tokens only when grant_types also holds refresh_token. Sign-in reads a document only while it is fresh, and fetches it again (at most every 30 seconds) when a requested redirect URI is missing from it.

CloudConfig

cloud: — present only on managed-fleet (mcpg.cloud) instances. Absent for self-host; every field defaults so a bare cloud: {} is inert.

FieldTypeDefaultSummary
allow_anonymousbooleanfalsePublish-time acknowledgement that this managed instance intentionally serves /mcp WITHOUT a configured token verifier (an anonymous / public MCP server). The CP publish guard requires EITHER a verifier (governance.access.jwks / governance.access.oidc_oauth / governance.access.authorization_server) OR this opt-out, so a tenant can't expose an unauthenticated gateway on the public edge by omission. The gateway runtime does not read this field — it is a declaration the publish guard checks.
custom_domainsarray<string>Additional customer-owned hostnames that resolve to this instance (CNAME → the instance edge). Developer-owned; each must be a valid DNS hostname. Empty for the default-domain-only case.
environmentstring (optional)Environment slug (dev / staging / prod …).
instance_idInstanceId (optional)Server-assigned stable id. None for self-host. Read-only — set by the CP.
isolationCloudIsolation"shared"Isolation tier for placement.
namestring (optional)Human-friendly display name for the instance.
provenanceCloudProvenance{}Server-managed placement provenance. Stamped by the provisioner/operator; ignored / overwritten if hand-written.
regionstring (optional)Placement region hint (free-form; matched against fleet capacity).
subdomainstring (optional)Globally-unique DNS label that addresses this instance: https://{subdomain}.mcpg.cloud/mcp. Read-only — assigned/reserved by the CP. Defaults conceptually to instance_id when unset.
tenantstring (optional)Tenant / org slug — billing + console grouping. Developer-owned.
tierCloudTier"unspecified"Billing tier the instance runs under.
workspacestring (optional)Workspace slug within the tenant.

CloudIsolation

Default isolation tier for the instance's placement.

Variants:

  • shared — Shares a namespace pool with other tenants (default).

  • dedicated — Dedicated nodes / namespace for the tenant.

CloudProvenance

Server-managed placement facts. Never trusted from a published config — the operator overwrites these at render time.

FieldTypeDefaultSummary
cluster_idstring (optional)
external_urlstring (optional)Canonical external URL (https://{subdomain}.mcpg.cloud/mcp). Operator-injected into governance.access.resource_metadata.resource; OVERWRITTEN at render — never trust a published value.
managed_bystring (optional)
namespacestring (optional)
provisioned_atstring (optional)

CloudTier

Billing tier the instance was provisioned under.

The variants mirror the licensing vocabulary (community | pro | team | enterprise) so this block and a license claim can be compared without a translation table. free is accepted as an alias for community, which is what this variant used to be called.

Variants:

  • community

  • unspecified — No tier asserted (self-host / unmanaged).

ClusterConfig

Top-level cluster config. The cluster plugin is the unified backbone for MCPG multi-instance state + coordination — it internally instantiates the four primitive impls (KeyValueStore, PubSub, Lease, Watch) and exposes them via accessor methods. kind is the discriminator; everything else is kind-specific config that flows straight to the plugin's factory as JSON.

yaml cluster: kind: redis # single_node | nats | redis url: ${env.REDIS_URL} # rest of the fields are kind-specific key_prefix: "mcpg:cluster:"

kind: single_node (the default when cluster: is omitted) installs the in-process built-in coordinator and ignores the rest of the block. Other kinds map to mcpg-plugin-cluster-<kind>; the cdylib must still be declared under plugins[] (the inline cluster.* fields override any config: block on the matching entry).

FieldTypeDefaultSummary
allow_degraded_bootbooleanfalseTolerate a coordinator that advertises kv/bus roles but fails the boot reachability probe (a live round-trip against the advertised primitives). Default false: for a clustered (non-single_node) coordinator the gateway probes each advertised primitive at boot and refuses to start if the round-trip fails or the accessor is absent, rather than silently de-clustering to per-replica in-process state. Set true ONLY when an operator knowingly wants the gateway to boot and run degraded (per-replica state) despite an unreachable coordinator — it logs a loud error and continues. Gateway-only named field — kept out of the flattened plugin config.
allow_insecure_transportbooleanfalsePermit a plaintext (non-TLS) coordinator transport for a non-single_node coordinator. Defaults to false: validate() refuses a plaintext redis/nats coordinator at boot, because the coordinator carries all shared state (sessions, credentials, delivery) in clear. Set true ONLY for local/dev/CI. Gateway-only — NOT forwarded to the plugin (it is a named field, so serde keeps it out of config).
allow_plaintext_statebooleanfalsePermit a clustered (non-single_node) coordinator WITHOUT state_encryption_key_env. Defaults to false: validate() refuses to boot clustered without a state key, because the coordinator would hold session/pipeline/credential-adjacent state in plaintext and cross-replica modern-session resume (whose key derives from the same material) stays off. Set true ONLY as a migration escape hatch — boot then logs a loud warning naming the degraded surfaces. Inert for single_node. Gateway-only named field — kept out of the flattened plugin config.
kindstring"single_node"
readiness_gateClusterReadinessGate (optional)Whether coordinator health gates /ready. Unset means a kind-dependent default: off for kind: single_node, degrade for every other kind; an explicitly configured value always wins. off is fail-open: a coordinator outage is surfaced only via the mcpg_cluster_backend_up gauge + its alert, never on readiness. degrade adds an informational not-ready check to the readiness body but keeps /ready green (no LB flapping). fail makes /ready return not-ready while the coordinator is unreachable (fail-closed). Gateway-only named field — kept out of the flattened plugin config.
state_encryption_allow_plaintext_readsbooleanfalseTolerate plaintext (non-envelope) reads while state_encryption_key_env is set — a bounded migration window for rolling a key in across replicas. Default false: once a key is configured a plaintext value on the coordinator KV/bus is rejected (fail closed), so an unkeyed peer or attacker cannot inject unauthenticated capability state. Set true only transiently during a rollout; turn it off once every replica writes envelopes. Inert without state_encryption_key_env. Gateway-only named field — kept out of the flattened plugin config.
state_encryption_key_envstring (optional)Application-layer AEAD (XChaCha20-Poly1305) of ALL coordinator-backed capability state — sessions (incl. SSE replay), delivery, cancellation, tasks, pipelines, idempotency, request-state, subscriptions, quota, and the approvals backstop — and the derivation source for the cross-replica modern-session resume key. Names the env var holding a URL-safe-base64 32-byte key (the key itself never sits in the config artifact); generate one with openssl rand -base64 32 | tr '+/' '-_'. Required for every non-single_node kind: validate() refuses a clustered coordinator without it unless allow_plaintext_state: true. Unset (single_node, or via the escape hatch) = plaintext serde on the wire/at-rest; confidentiality then rests on the transport guard. Values are sealed per-key/per-topic (swap-resistant); keys/topics stay cleartext for routing. Does NOT cover the credential cache — it has its own encryption_key under the credentials config. Gateway-only named field — kept out of the flattened plugin config.
state_encryption_key_idstring (optional)Key id (kid) stamped on state envelopes for rotation visibility. Defaults to mcpg-cluster-state when a key is configured. Inert without state_encryption_key_env. Gateway-only named field.
tenant_segmentstring (optional)Optional per-deployment tenant segment. When set, EVERY coordinator-backed capability KV key and bus topic is prefixed with t.<segment>/ (keys) / t.<segment>. (topics) so a single coordinator namespace can be fenced per-tenant by broker-native ACLs — NATS subject perms t.<segment>.>, redis key-pattern ACLs (~…t.<segment>/*). Unset = today's flat, un-prefixed keys/topics (one coordinator namespace == one trust domain). This is a deployment-level label, not a per-request tenant — the gateway process serves one tenant segment; the runtime carries no per-request tenant at key/topic-formation time, so per-request multi-tenancy remains future work. Turning it on is a key-namespace cutover (existing flat-keyed state goes invisible). Must be a single token (no . * > / : or whitespace). Gateway-only named field — kept out of the flattened plugin config.

ClusterReadinessGate

How coordinator health affects /ready. When cluster.readiness_gate is unset the effective value is kind-dependent — off for single_node, degrade for every other kind ([ClusterConfig::effective_readiness_gate]).

Variants:

  • off — Coordinator health never affects /ready (fail-open). The default for kind: single_node.

  • degrade — Surface a not-ready check in the readiness body when the coordinator is down, but keep the overall /ready status green. The default for every clustered (non-single_node) kind.

  • fail — /ready returns not-ready while the coordinator is unreachable.

ColumnFormat

How a cell value is rendered client-side.

Allowed values:

  • text
  • number
  • currency
  • date
  • badge
  • link

ConcurrencyPolicy

One named concurrency cap.

FieldTypeDefaultSummary
idstring
identity_claimstring (optional)
max_concurrentintegerMaximum simultaneous in-flight calls. In a fleet whose replicas share the quota store (cluster.kind other than single_node, store not memory or file) it counts the calls of every replica: each acquire costs one counter write and one listing of the scope's counters, and the permits of a replica that dies come back within 30 s. On a single node it is an in-process count.
on_exceededstring"deny"Action when the cap is hit. deny returns immediately; queue waits up to queue_timeout_ms.
queue_timeout_msinteger30000Timeout for queued callers when on_exceeded: queue. Default 30s.
scopestring"per_identity"Scope (typically per_tool, global, or per_identity).

ConfigWatchConfig

gateway.config_watch: — operator-tunable file-watch reload trigger. Same semantics as SIGHUP and the admin endpoint: full GatewayRuntime rebuild via ArcSwap; session store preserved; credential cache rebuilt fresh; list_changed emitted per category for operational sessions on inventory delta.

FieldTypeDefaultSummary
enabledbooleanfalseWhen true, the gateway watches its config files on disk and triggers a hot-reload when contents change. Polling- based — handles editor-write-via-rename (vim/emacs) and K8s ConfigMap atomic-symlink-swap transparently because the watcher reads through the symlink chain regardless of how the write landed. Defaults to disabled — operators must opt in.
poll_interval_msinteger5000Poll interval in milliseconds. Lower = faster reload after edit; higher = lower disk I/O. Default 5000 (5s) is imperceptible for config changes and trivial in I/O cost. Values below 1000 (1s) are clamped to 1000 at validate time with a warning — sub-second polling burns I/O for no human-perceivable benefit.

ConflictPolicy

Conflict policy on a body-hash mismatch with a stored record. Today only Reject is implemented; permissive_replay is not offered.

Variants:

  • reject — Same key + different body hash → JSON-RPC error -33010 IdempotencyConflict (HTTP 422).

ConsentConfig

The consent page (interactive.consent). It is shown to every dynamically registered client, to every metadata-document client and to a registered client whose redirect URIs are not all https (see clients[].consent), before the user is sent to the IdP.

FieldTypeDefaultSummary
remember_daysinteger30How long an approval is remembered in the approving browser, in days (0–365; 0 never remembers). The browser keeps it in a cookie sealed with the state key (__Host-mcpg_consent), so it cannot be read or forged, and a replaced state key forgets it. Only approvals for an https:// redirect URI of a registered (clients[]) or metadata-document client are remembered, for that exact client, redirect URI and set of scopes; a loopback redirect URI or a dynamically registered client asks every time. prompt=consent always asks.
scope_descriptionsmap<string, string>{}A description shown next to each scope, keyed by scope. Every key must be a scope this server grants (allowed_scopes, else resource_metadata.scopes_supported); each description is at most 200 characters.
service_namestring (optional)The heading of the consent and connect pages (1–80 characters). Defaults to the issuer's host.

ControlEnvelopeKind

A webhook control envelope the gateway may send.

Variants:

  • gap — Events were skipped; carries the cursor to persist.

  • terminated — The subscription ended on the gateway's side.

ControlPlaneAttachConfig

Control Plane attachment config. See apps/gateway/src/runtime/cp/attach.rs for the wiring logic.

FieldTypeDefaultSummary
bootstrap_ca_pemstring (optional)PEM-encoded CA bundle to trust on the very first connect (Register), before the agent has its own creds. Once Register completes, the CP-issued cert/key/ca_chain trio supplants this. Optional; only required when the CP gRPC listener is TLS and the operator hasn't pre-populated a previous run's agent-creds.json.
capture_payloadsbooleanfalseEnterprise opt-in. When true, the gateway captures the JSON-serialized request arguments + response of each tool call and ships them in the MetricsReport (Channel-encrypted; the CP further encrypts at-rest with a per-tenant KMS-derived key). The captured bytes can contain PII / secrets, so this is off by default; the CP also gates ingest on the active license carrying the payload_capture feature flag, so flipping this true without a matching license is a no-op (samples ship but CP drops the payload bytes).
enrollment_urlstring (optional)One-time enrollment URL minted by the CP UI. Required on first boot; subsequent boots reuse the cached creds in state_dir.
heartbeat_interval_msinteger (optional)Milliseconds between heartbeats. Default 30000 (30 s); held to 5000–60000, since each heartbeat reply carries the CP's quota status. A platform-provisioned gateway always runs the default.
instance_uidstring (optional)Stable per-instance id. Defaults to ${HOSTNAME}-${uuid7-prefix} when unset.
state_dirstring"./mcpg-cp-state"Where to persist agent creds (agent-creds.json) and the LKG cache. Defaults to ./mcpg-cp-state.
urlstringgRPC URL of the Control Plane (e.g. "https://cp.example.com:7844").

CorsConfig

gateway.server.cors — who may call this gateway from a browser page.

FieldTypeDefaultSummary
allow_credentialsbooleanfalseAllow cookies and TLS client certificates on cross-origin calls. MCP authenticates with a bearer token, which needs no credential mode, so this stays false unless a deployment front-ends the gateway with a cookie session.
allowed_headersarray<string>(see type)Request headers a page may send. Defaults to the set MCP needs — content negotiation, the session and protocol headers, the SSE resume cursor, bearer auth, the DPoP proof, idempotency and trace context. Add to it for a header a plugin reads.
allowed_originsarray<string>Exact origins (scheme://host[:port]) a page may call from. No wildcard: * with credentials is refused by every browser, and without credentials it would publish a gateway to every page on the web. Compared case-insensitively, like the rebinding guard.
expose_headersarray<string>(see type)Response headers a page may READ. A browser hides every other header from script, so the session id, the request id, the auth challenge and the DPoP nonce have to be named here to be usable.
max_age_secsinteger600How long a browser may cache the preflight answer, in seconds.

CredentialsClusterConfig

FieldTypeDefaultSummary
allow_plaintextbooleanfalseExplicit, INSECURE opt-in to publish credential events in plaintext (no encryption_key_env). Required to enable cluster credential pub/sub without a key — otherwise the gateway refuses to boot rather than silently broadcasting plaintext credentials. On this path published_by is forgeable, so plaintext mode also REQUIRES a non-empty allowed_publishers; that allowlist plus broker write-ACLs are the integrity boundary. AEAD via encryption_key_env is the only integrity-providing mode.
allowed_publishersarray<string> (optional)Optional allowlist of peer node ids whose credential-cache events this instance will apply. When Some, an Issued / Revoked event whose published_by is not in the list is dropped. This is a genuine control on the AEAD path (published_by is inside the sealed payload, so it is authenticated) — it bounds the blast radius of a compromised-but-keyed peer. On the plaintext path (allow_plaintext: true) it is best-effort only: published_by is attacker-forgeable there, so the allowlist raises the bar but is NOT a substitute for encryption_key_env. MANDATORY (non-empty) when allow_plaintext is set. Unset = accept events from any peer (only valid on the AEAD path).
enabledbooleanfalse
encryption_key_envstring (optional)Env var holding a base64-encoded 32-byte key for application-layer AEAD (XChaCha20-Poly1305) of the credential events published on the cluster topic. STRONGLY recommended: without it, per-caller credentials are published as plaintext JSON and confidentiality rests entirely on transport TLS . All peers sharing the topic must use the same key.
encryption_key_idstring (optional)Key id (kid) stamped on encrypted envelopes so operators can rotate keys. Defaults to mcpg-cred-cache when a key is set.
topicstring (optional)Override the default cluster topic (mcpg.credentials.events). Operators with multiple independent MCPG deployments sharing one cluster_backend MUST namespace per-deployment so peer caches don't pollute each other.

CredentialsConfig

Defaults are safe for single-node deploys; multi-instance deploys with per-caller dynamic credentials (e.g. Vault dynamic DB) MUST configure cluster.enabled: true to avoid cache divergence.

FieldTypeDefaultSummary
clusterCredentialsClusterConfig(see type)Optional cluster pub/sub wrapper. When enabled: true AND a cluster_backend is bound, the gateway wraps the L1 cache with ClusteredCredentialCache so every peer instance sees Issued / Revoked events. Drops to local-only behaviour with a warning when enabled: true but no coordinator is bound.
key_attributesarray<string>Identity attribute (token-claim) names folded into the credential-cache key so callers differing only by these claims (commonly the tenant claim) get separate cached credentials. The caller's subject_token (stamped by oauth_impersonation) is ALWAYS folded when present — impersonated credentials never share across bearers, with or without this setting. A subject token from the caller's stored IdP sign-in (subject_token: idp_refresh_token or idp_id_token) is folded as the sign-in it belongs to instead, so a token the IdP rotates keeps the cached credential. Empty (default) excludes other attributes from the key — set this to your tenant claim name(s) when a credential_issuer derives its principal from an attribute claim, otherwise those callers share one credential. In a clustered cache every peer MUST set the same key_attributes (the published event hash is computed with it) — divergence silently produces per-node cache misses, the same all-peers-agree constraint that already governs the hash algorithm.
max_cache_ttl_msinteger3600000Operator-side cap on per-entry TTL. Even if a plugin returns a 24-hour TTL, the cache evicts at this cap to limit blast radius from leaked / compromised credentials. Default 3600 (1 hour).
max_entriesinteger10000Maximum number of (identity, plugin, target) entries kept in the L1 cache. LRU eviction past this. Default 10000 — at ~500 bytes per entry that's ~5MB worst case.

DebugCommandToolConfig

FieldTypeDefaultSummary
argsarray<string>(see type)
commandstring"printf"
max_output_bytesinteger4096
timeout_msinteger2000

DebugConfig

Top-level debug: block — diagnostic tools surface only. The master switch lives at feature_flags.debug_tools_enabled. When that flag is false, every field below is ignored AND the mcpg.debug.* tools are stripped from the capability registry regardless of tools.exposure.

FieldTypeDefaultSummary
toolsDebugToolsConfig(see type)Operator-defined diagnostic tools surfaced as MCP tools when feature_flags.debug_tools_enabled: true. See [DebugToolsConfig] for the surface.

DebugNetworkToolConfig

FieldTypeDefaultSummary
expected_status_codesarray<integer>(see type)
headersmap<string, string>{}
max_response_bytesinteger4096
require_json_responsebooleanfalse
timeout_msinteger2000
urlstring"http://127.0.0.1:8787/health"

DebugToolBackendsConfig

FieldTypeDefaultSummary
command_probe_profilestring"default_command_probe"
network_json_call_profilestring"default_network_probe"
network_probe_profilestring"default_network_probe"

DebugToolExposureConfig

FieldTypeDefaultSummary
command_probebooleantrue
network_json_callbooleanfalse
network_probebooleantrue
operational_overview_promptbooleantrue
runtime_overview_resourcebooleantrue

DebugToolsConfig

FieldTypeDefaultSummary
bindingsDebugToolBackendsConfig(see type)
command_profilesmap<string, DebugCommandToolConfig>{}
exposureDebugToolExposureConfig(see type)
network_profilesmap<string, DebugNetworkToolConfig>{}

DeliveryAuditMode

Granularity of delivery audit records.

Variants:

  • summary — Counts per subscription per window.

  • every — One record per delivery request.

DeliveryConfig

delivery: config — delivery bus (the internal pub/sub that fans server-initiated messages out to the SSE stream owning each session) + per-capability bus: override. When bus is unset, the gateway inherits the cluster's pub_sub() primitive.

FieldTypeDefaultSummary
busBusOverrideConfig (optional)

DisclosureLevel

Allowed values:

  • summary
  • redacted
  • full

DpopConfig

DPoP (RFC 9449) at the embedded authorization server (governance.access.authorization_server.dpop). Off by default: the token endpoint then ignores DPoP headers and the dpop_jkt parameter, and an ID-JAG bound to a key (cnf) is refused. Turn it on only after every replica runs a build that knows this block: a replica without it drops the key binding of a sign-in's grant when it rotates the grant's refresh token. Turning it on (enabled: true) requires a license with the oauth.dpop feature; a block that leaves it off needs none.

FieldTypeDefaultSummary
allowed_algsarray<string>(see type)JWS algorithms a proof may be signed with, published as dpop_signing_alg_values_supported: ES256, ES384, EdDSA, PS256, PS384, PS512, RS256, RS384 and RS512, each at most once. HMAC algorithms are refused: a proof is verified with the public key it carries. An RSA proof key must have at least 2048 bits.
enabledbooleanfalseBind tokens to the client's key. The token endpoint reads one DPoP proof header per request: a token issued with a valid proof carries cnf.jkt and token_type: DPoP, and one issued without a proof stays a Bearer token. An ID-JAG with cnf.jkt is redeemed only with a proof of that key (invalid_grant otherwise). The authorization endpoint reads dpop_jkt (RFC 9449 §10), and the code is then redeemed only with a proof of that key. A public client's refresh token is bound to its proof key; a confidential client's is not. The resource (/mcp and every endpoint that takes this server's tokens) accepts a bound token with Authorization: DPoP and a DPoP proof of its key for that request (method, URL, ath, single use); a Bearer token keeps working with the Bearer scheme. The caller then carries the key's thumbprint as the dpop_jkt identity attribute, which a policy can require. The authorization server metadata and the protected resource metadata publish dpop_signing_alg_values_supported. A token bound to a key is never accepted with the Bearer scheme, even after DPoP is turned off. Requires a license with the oauth.dpop feature.
nonceDpopNonceMode"off"Server-provided nonces (RFC 9449 §8 and §9): off; token_endpoint, where a proof at POST /oauth/token must carry a current nonce or the request answers 400 use_dpop_nonce with a fresh DPoP-Nonce header, and every token response carries the current one; or always, which also covers the resource: a proof there without a current nonce is answered 401 WWW-Authenticate: DPoP error="use_dpop_nonce" with a fresh DPoP-Nonce. Nonces need no store: they are derived from the access-token signing keys, so every replica accepts every other replica's, and they follow a rotation of signing_keys.
nonce_lifetime_secsinteger300How long one nonce window lasts, in seconds (30–3600). A nonce is accepted in the window it was issued in and in the next one, both widened by clock_skew_secs, so replicas whose clocks differ by up to that accept each other's nonces.
proof_max_age_secsinteger60How old a proof may be, in seconds (1–300): it is accepted while its iat lies between proof_max_age_secs plus clock_skew_secs in the past and clock_skew_secs in the future. Each proof is accepted once: its jti is recorded in the replay ledger every replica shares for proof_max_age_secs plus twice clock_skew_secs.
requiredbooleanfalseIssue and accept only DPoP-bound tokens of this authorization server: a token request without a proof is refused, with invalid_grant for an ID-JAG (ID-JAG §9.8.1.2) and invalid_dpop_proof for an authorization code or a refresh token; the resource refuses a token of this server bound to no key, one issued before this was set included; and a 401 to a caller without a credential carries a second WWW-Authenticate: DPoP challenge. Credentials that another verifier accepts (an oidc_oauth provider, jwks or an identity plugin) are not affected, so the protected resource metadata publishes dpop_bound_access_tokens_required: true only while this server is the one verifier of access tokens and the only authorization server listed, and false otherwise. Requires enabled.

DpopNonceMode

Where a DPoP proof must carry a server-provided nonce.

Variants:

  • off — No nonce is required.

  • token_endpoint — At the token endpoint.

  • always — At the token endpoint and at the resource.

DynamicClientRegistrationConfig

Dynamic client registration (interactive.dynamic_client_registration, RFC 7591) at POST /oauth/register, for MCP clients that have neither a clients[] entry nor a Client ID Metadata Document. A registered client is public (token_endpoint_auth_method: none, PKCE), may use only authorization_code and, when it registers it, refresh_token, never redeems ID-JAGs, always sees the consent page (an approval is never remembered), and never satisfies a non-empty trusted_idps[].allowed_clients. What a client registers is its own claim (RFC 7591 §5): the gateway keeps its redirect URIs, grant types, client_name (shown on the consent page as unverified), application_type and, while dpop.enabled, dpop_bound_access_tokens (RFC 9449 §5.2), and ignores every other member, logos and URLs included. A registration is kept sealed like every sign-in record and removed after client_ttl_secs without use; the client is then unknown, and a grant of it is revoked when the client next presents one of that grant's tokens at the token or revocation endpoint.

FieldTypeDefaultSummary
allow_openbooleanfalseAccept registrations without an initial access token, from anyone who can reach the gateway.
allowed_redirect_hostsarray<string>[]Hosts an https:// redirect URI of a registration may use: an exact host, or a parent domain of it. Empty = loopback redirect URIs only (http://127.0.0.1, http://[::1], http://localhost, any port). An https:// URI on another host is left out of the registration, whose answer lists the URIs kept (RFC 7591 §3.2.1); a registration with none left, or one that lists a malformed URI, a private-use scheme or http:// off loopback, is refused (invalid_redirect_uri). A URI whose host is later removed from this list stops matching.
client_ttl_secsinteger2592000A registration unused for this long is removed, in seconds (86400–7776000). Only a successful use restarts it: a code issued to the client, a code or refresh token it redeemed, or a token of its own it revoked.
enabledbooleanfalseServe POST /oauth/register and advertise it as the registration_endpoint. Needs initial_access_tokens or allow_open.
initial_access_tokensarray<string>[]Bearer tokens that authorize a registration (RFC 7591 §3, Authorization: Bearer <token>), each at least 32 bytes. Supply via ${secret.NAME}; the server keeps only their SHA-256 digests. A request that presents a token not listed is refused, with allow_open too.
max_clientsinteger1000Most registrations kept at once, across replicas (1–100000). A registration beyond it is answered 503 until older ones are removed.
registrations_per_hour_per_ipinteger20Registrations accepted from one client IP address per clock hour, counted across replicas; the IP is found as for authorization_server.rate_limit_per_min. A registration beyond it is answered 429 with Retry-After. 0 = unlimited.

EnumSource

A dynamic-enum source: a sibling tool whose result supplies options.

FieldTypeDefaultSummary
argsmap<string, string> (optional)
label_fieldstring
toolstring
value_fieldstring

EventAudience

Whose credential reads an event source.

Variants:

  • shared — The gateway's own identity; one source serves every subscriber.

  • per_principal — Each subscriber's own credential. No source kind of this build reads with a subscriber's credential, so this is refused.

EventDeliveryMode

A delivery mode of an event type.

Variants:

  • poll — events/poll.

  • push — events/stream.

  • webhook — events/subscribe with a callback URL.

EventErrorCodes

The numbering of the MCP Events error codes on the wire.

Variants:

  • draft — -32011 to -32015.

  • sep_3415 — -32023 to -32027.

EventLogConfig

events.log: — the inbox that inbound routes write before an event reaches the log.

FieldTypeDefaultSummary
inbox_max_rowsinteger10000Most rows one source's inbox holds; past it an inbound route answers 503 with Retry-After. Default 10 000.
inbox_ttl_msinteger86400000How long an inbox row waits for the log writer, in milliseconds. An inbound route answers 503 with Retry-After once the inbox has not been drained for this long, so the sender keeps the event and retries. Default 86 400 000 (24 hours).

EventReplay

Whether a type's log serves replay.

Variants:

  • log — Cursors resume from the log within retention.

  • none — No cursor is issued; every subscription starts from now.

EventRetentionConfig

retention: — the bounds of one type's log. The oldest occurrences are evicted first; an append is never refused. On a gateway a control plane manages, the plan's retention caps are the most each bound can be.

FieldTypeDefaultSummary
max_age_msinteger86400000Oldest occurrence kept, in milliseconds. Default 86 400 000 (24 hours).
max_bytesinteger (optional)Most bytes kept. Default 8 388 608 (8 MiB). The sum over every type, imported types included, must fit the event-log store: half of the NATS coordinator's events bucket (jetstream.named_bucket_max_bytes, 256 MiB by default), or 64 MiB for an in-process memory store. Redis, a file store and a store plugin set no limit on the sum.
max_eventsinteger10000Most occurrences kept. Default 10 000.

EventSourceConfig

The source of an event type: exactly one of watch, inbound, resource or builtin.

FieldTypeDefaultSummary
builtinBuiltinEventSource (optional)An event the gateway itself raises.
inboundInboundSourceConfig (optional)Signed webhooks that third parties POST to /webhooks/events/{source_id}.
resourcestring (optional)The uri of the resources[] binding whose watch: block feeds the type. Every change is one occurrence with data: {uri, observedAt}.
watchEventWatchSourceConfig (optional)A plugin watch strategy. Every change it reports is one occurrence: with the plugin's payload when event_type names one of the event types the plugin declares, else data: {observedAt}.

EventSourcesConfig

events.sources: — when event sources run.

FieldTypeDefaultSummary
linger_msinteger300000How long a source keeps running after its last subscription ends, in milliseconds. Default 300 000 (5 minutes).

EventTransformConfig

An operator transform of occurrence data.

FieldTypeDefaultSummary
expressionstringThe expression handed to the plugin.
pluginstringId of a loaded transform plugin, for example dev.mcpg.transform.jsonata.

EventTypeAuditConfig

mcp.capabilities.events[].audit.

FieldTypeDefaultSummary
argumentsAuditArguments"hash"How a subscription's arguments appear in its audit records: hash (default, details.arguments_hash), raw (details.arguments; only for a type whose arguments are not sensitive) or none.
poll_heartbeat_secsinteger0Write a poll record at least this often (seconds) per poller, even for a poll that returns no events, so a poller's presence is visible. 0 (default): an empty poll from a cursor writes nothing.

EventTypeConfig

One event type the gateway offers (mcp.capabilities.events[]).

A type binds exactly one source. Subscription arguments are a filter applied by match, never a parameter of the source.

FieldTypeDefaultSummary
always_onbooleanfalseKeep the source running with no subscriber, so the log covers periods without one. Default false: a source starts with the first subscription and stops sources.linger_ms after the last.
audienceEventAudience"shared"Whose credential reads the source. shared (default) reads with the gateway's own identity; per-subscriber visibility is then expressed in match.
auditEventTypeAuditConfig(see type)What this type's mcpg.events.poll and mcpg.events.stream_opened audit records carry.
deliveryarray<EventDeliveryMode>(see type)Delivery modes offered, intersected with the modes mcp.configurations.events enables. Default every mode. The intersection must not be empty.
descriptionstring (optional)What an occurrence means, for the client.
input_schemaanyJSON Schema of the subscription arguments. Omitted, a subscription carries no arguments.
matchstring (optional)CEL expression over arguments, event (event.data, event.name, event.eventId, event.timestamp) and subscriber that selects the occurrences a subscription receives. An error or a non-boolean result delivers nothing.
namestringThe type's name, unique, at most 128 characters of [A-Za-z0-9._-]. Publish a new name instead of changing a schema in a way that is not additive.
payload_schemaanyJSON Schema of each occurrence's data. An occurrence that does not match it is dropped and counted. Omitted, any object is accepted. A watch or resource source sends {observedAt} (and uri for resource), which the schema must accept.
replayEventReplay"log"log (default) lets a client resume from a cursor within retention; none always answers cursor: null.
retentionEventRetentionConfig(see type)How long and how much of this type's log is kept.
sourceEventSourceConfigWhere occurrences come from: exactly one of watch, inbound, resource or builtin.
transformEventTransformConfig (optional)Operator transform applied to each occurrence's data before the payload schema check.

EventWatchSourceConfig

source.watch: — a plugin watch strategy.

FieldTypeDefaultSummary
event_typestring (optional)One of the event types the strategy declares in its plugin's plugin.yaml (watch_strategies[].event_types). The type then carries the plugin's payload, its schemas unless this type declares its own, and resumes where it stopped when the strategy can. A strategy that declares no types is refused at boot. Omitted, every change is a payload-free {observedAt} occurrence. Either way the source runs where the strategy places it.
strategyWatchStrategyConfigA plugin-backed strategy: nats_topic, kafka_topic (it reads with its own mcpg-events-{name} group unless group_id names one), sql_polling, postgres_listen_notify or plugin. poll and webhook are refused: poll reads a resource the event type does not have, and the webhook token route is not an event source (use inbound).

EventsAdvertiseLocation

A place in the server/discover capabilities where MCP Events is advertised.

Variants:

  • capability — The top-level capabilities.events object.

  • extension — The capabilities.extensions["io.modelcontextprotocol/events"] entry.

EventsAuditConfig

events.audit: — how delivery is audited.

FieldTypeDefaultSummary
deliveriesDeliveryAuditMode"summary"summary (default) writes one record per subscription per window with delivery counts; every writes one record per delivery request.

EventsConfig

events: config — the experimental MCP Events extension.

Off by default. With enabled: false the events/* methods answer -32601 and server/discover does not mention events. The extension is served on the 2026-07-28 wire over HTTP only; the 2025-11-25 wire and stdio never carry it. Event types are declared under mcp.capabilities.events.

FieldTypeDefaultSummary
advertisearray<EventsAdvertiseLocation>(see type)Where server/discover advertises the extension. The drafts disagree on the location, so both are on by default: capability is the top-level capabilities.events object (read by ChatGPT), extension is the capabilities.extensions["io.modelcontextprotocol/events"] entry. Must name at least one location when enabled is true.
allow_ephemeral_subscriptionsbooleanfalseAccept webhook subscriptions on a store that loses them on a restart. Every grant is then capped at webhook.ttl.min_ms, so a client refreshes before a restart can matter for long. Default false.
auditEventsAuditConfig(see type)Audit of event delivery.
delivery_pausedbooleanfalseHold every delivery, in every mode, while keeping every subscription and every event within retention: webhook deliveries wait, a poll answers no events and the cursor it was given, and a stream sends heartbeats only. Subscribe and refresh still answer, with deliveryStatus.active: false and lastError: "paused". Delivery resumes from each position when this is cleared. Default false.
enabledbooleanfalseServe the events/* methods of every delivery mode enabled below, and advertise the extension. Default false.
error_codesEventErrorCodes"draft"How the extension's error codes are numbered. draft (default) answers -32011 to -32015, the numbers the ChatGPT profile documents; sep_3415 answers -32023 to -32027, the numbers the upstream sketch assigns. The documentation names the draft numbers.
inbound_profilesarray<InboundProfileConfig>Signing profiles of inbound senders beyond the built-in ones (standard_webhooks, github, stripe, slack). A route names one in source.inbound.verify.profile.
logEventLogConfig(see type)Event log limits.
log_storeStoreOverrideConfig (optional)Store of the event log (store role event-log). Omitted, the cluster coordinator's store is used. The retention byte budgets of every event type must fit this store (see mcp.capabilities.events[].retention.max_bytes).
pollEventsPollConfig(see type)Poll delivery (events/poll).
pushEventsPushConfig(see type)Push delivery (events/stream).
reauthorize_interval_msinteger300000How often the authorization of a long-lived grant is checked again from its stored identity: each webhook subscription and each open stream. In milliseconds; default 300 000 (5 minutes).
sourcesEventSourcesConfig(see type)Lifecycle of event sources.
storeStoreOverrideConfig (optional)Store of the webhook subscription records (store role event-subscription). Omitted, the cluster coordinator's store is used, which is in memory on a single node. Webhook delivery refuses a store that does not survive a restart unless allow_ephemeral_subscriptions is set; a single node keeps subscriptions with {kind: file, dir: …}.
unsubscribe_unknownUnsubscribeUnknown"ok"The answer to events/unsubscribe for a subscription that does not exist. ok (default) answers {}, as an idempotent unsubscribe; not_found answers -32011 with data.kind: "subscription".
webhookEventsWebhookConfig(see type)Webhook delivery (events/subscribe).

EventsPollConfig

events.poll: — events/poll: a client reads a type's log itself, on any replica, with the cursor of its previous poll. Off by default.

FieldTypeDefaultSummary
enabledbooleanfalseServe events/poll, and offer poll on every type whose delivery lists it. Default false.
lease_multiplierinteger3A poll lease lasts this many next_poll_ms after the last poll. While a type has a lease, its source runs. Default 3.
max_eventsinteger100Events per poll when the client sets no maxEvents. Default 100.
max_events_capinteger1000Most events per poll. Default 1 000.
max_leases_per_principalinteger100Poll leases one principal may hold: one per event type and arguments it polls. Default 100.
min_next_poll_msinteger1000Shortest wait the gateway asks for, in milliseconds. Default 1 000.
next_poll_msinteger5000Suggested wait before the next poll, in milliseconds. Default 5 000.
require_trustSubscriberTrust"verified"The lowest caller trust that may poll. verified (default) requires a cryptographically verified principal; header_asserted also admits a principal named by a request header.

EventsPushConfig

events.push: — events/stream: one long-lived response per subscription, on the replica the client reaches. Off by default.

FieldTypeDefaultSummary
enabledbooleanfalseServe events/stream, and offer push on every type whose delivery lists it. Default false.
heartbeat_interval_msinteger20000Interval of heartbeat frames, in milliseconds, from 1 000 to 30 000. Default 20 000. A client that hears nothing for twice this treats the stream as dead and reconnects with its cursor.
max_streams_per_principalinteger16Streams one principal may hold open across the cluster. Default 16. These do not count against the server-push stream cap of a session.
require_trustSubscriberTrust"verified"The lowest caller trust that may open a stream. verified (default) requires a cryptographically verified principal; header_asserted also admits a principal named by a request header.

EventsWebhookConfig

events.webhook: — the webhook delivery profile.

FieldTypeDefaultSummary
control_envelopesarray<ControlEnvelopeKind>(see type)The control envelopes sent besides events. Default both gap and terminated; a verification envelope is always sent.
egressWebhookEgressConfig(see type)The outbound client that reaches callback URLs.
enabledbooleantrueAccept events/subscribe and deliver to callback URLs. Default true. Requires a store that survives a restart (or allow_ephemeral_subscriptions) and a key that every replica shares: cluster.state_encryption_key_env or mcp.configurations.sessions.synthetic_session_key.
limitsWebhookLimitsConfig(see type)Subscription and delivery limits.
max_payload_bytesinteger245760Largest serialized occurrence, in bytes; a larger occurrence is not delivered. At most 245 760 (the default), so the whole request body stays within 262 144 bytes.
require_trustSubscriberTrust"verified"The lowest caller trust that may subscribe. verified (default) requires a cryptographically verified principal; header_asserted also admits a principal named by a request header and is for self-hosted deployments that trust their front proxy.
retryWebhookRetryConfig(see type)Retries of one delivery.
secret_rotation_grace_msinteger600000How long the previous secret keeps signing after a subscribe replaces it, in milliseconds. Default 600 000 (10 minutes).
suspensionWebhookSuspensionConfig(see type)When failing deliveries suspend a subscription.
ttlWebhookTtlConfig(see type)Lifetime of a subscription grant.
verificationWebhookVerificationConfig(see type)Proof that a callback endpoint wants the deliveries.

FailureRate

A share between 0 and 1.

Type: number

FeatureFlagsConfig

Operator-controlled strictness / compat flags.

Every field defaults to the safe / standards-compliant value; flipping a flag is an explicit acknowledgement that the operator is taking on the risk the default protects against.

FieldTypeDefaultSummary
allow_header_passthroughbooleanfalseForward credential-shaped inbound HTTP headers (Authorization, Cookie, X-API-Key, …) through to outbound bindings. The gateway strips these by default to avoid leaking client tokens to upstreams. Flip to true only for deployments that intentionally proxy bearer tokens to the binding (e.g., a pure-router deployment in a trusted network).
debug_tools_enabledbooleanfalseMaster switch for the operator-defined diagnostic tools (mcpg.command.* / mcpg.network.*). When false, every field under the top-level debug: block is ignored AND the debug tools are stripped from the capability registry regardless of debug.tools.exposure. Production deploys keep this off; flip on for CI / dev only.
sep2260_panic_on_orphanbooleanfalseUpgrade SEP-2260 violations (server-initiated request emitted without an originating client request id) from a warning + metric counter to a process panic. Useful in CI / dev where the violation indicates a bug; should stay false in production so a single misrouted code path does not take the gateway down.

FederationCacheConfig

Capability-cache behaviour.

FieldTypeDefaultSummary
capability_ttl_secsinteger300Re-list the upstream's capabilities every N seconds even without a list_changed notification.

FederationConfig

One federated upstream MCP server.

FieldTypeDefaultSummary
cacheFederationCacheConfig(see type)Capability-cache behaviour (TTL refresh).
eventsFederationEventsConfig(see type)How imported event types are read from the upstream.
filterFilterConfig(see type)Allow/deny filtering of imported tool and event type names.
governanceBackendGovernanceConfig(see type)Governance inherited by every capability imported from this upstream — identical block to a native binding's, so the gate chain treats a federated call exactly like a native one.
importImportConfig(see type)Which capability surfaces to import.
namestringSource id. Also the default capability-prefix namespace and the federated_from label on synthetic capabilities.
namingNamingConfig{}Prefixes applied to imported capability names / URIs.
responseResponseConfig(see type)Per-call response limits enforced gateway-side.
retryRetryConfig (optional)Per-call retry policy for upstream dispatch.
sessionSessionConfig(see type)Upstream-session behaviour.
synthesizeSynthesizeConfig(see type)Change-notification synthesis for upstreams that cannot push.
upstreamUpstreamConfigUpstream connection (url, transport, auth, safety).

FederationEventsConfig

How imported MCP Events types are read from the upstream.

FieldTypeDefaultSummary
deliveryUpstreamEventDelivery"auto"The upstream delivery mode each upstream subscription uses.
retentionEventRetentionConfig(see type)What the gateway keeps of each imported type's events, as retention does for a configured type. Imported types count against the event-log store's byte budget after the configured ones; a type that does not fit is not served.
sharingUpstreamEventSharing"fan_in"Whose upstream subscriptions serve the subscribers.

FilterConfig

Allow/deny filtering of imported tool and event type names (glob * suffix).

FieldTypeDefaultSummary
exclude_eventsarray<string>[]
exclude_toolsarray<string>[]
include_eventsarray<string>(see type)
include_toolsarray<string>(see type)

GatewayAppConfig

One gateway-authored templated app. Minted as ui://mcpg/<id>.

FieldTypeDefaultSummary
allow_credential_valuesbooleanfalseGate that must be set for cred:// to appear in public_values.
app_toolsarray<AppProvidedTool>Read-only App-Provided Tools the app exposes to the agent.
columnsarray<AppColumn> (optional)Explicit columns. None ⇒ derived from data_tool.outputSchema.
cspAppCspDecl (optional)Per-app author CSP declaration (still intersected by the egress csp_policy — declaring an axis never widens it).
data_argsmap<string, string> (optional)Static argument template passed to data_tool on the initial load (named string args; richer typing is a later phase).
data_toolstring (optional)Data-source tool. Required for form; optional for static kinds (e.g. a signature_pad). Its outputSchema/inputSchema seeds the columns/fields when those are omitted.
descriptionstring (optional)
fieldsarray<AppField> (optional)Explicit detail/form fields. None ⇒ derived from the schema.
idstringUnique id; becomes the ui://mcpg/<id> authority path segment. [a-z0-9] then [a-z0-9-]*.
id_fieldstring (optional)JSON-path to a row's stable id. Default $.id.
kindGatewayAppKindWhich shipped shell renders this app.
mapMapAppConfig (optional)Map binding; present iff kind == map.
page_sizeinteger (optional)
permissionsarray<AppsPermission>iframe permissions the app requests (clamped by allowed_permissions).
prefers_borderboolean (optional)
primary_actionstring (optional)row_actions[].id fired on a row click / primary interaction.
public_valuesmap<string, string>Opt-in non-secret config values injected into the data island. cred:// here requires allow_credential_values.
row_actionsarray<AppRowAction>Per-row actions, each re-entering the gateway via tools/call.
rows_pathstring (optional)JSON-path to the row array inside the tool's structuredContent. Default introspected from the output schema.
themeAppTheme (optional)
titlestringHuman title shown in the resource descriptor.
ui_schemaUiSchema (optional)Widget/layout overlay (highest precedence over columns/fields).

GatewayAppKind

The shipped shells a templated app can select.

Variants:

  • table

  • key_value — Dense label/value spec sheet (a compact detail).

  • code_viewer — Read-only syntax-highlight-free code/text viewer.

  • chart — Numeric series chart (bar/line) over the result columns.

  • signature_pad — Canvas to draw a signature/sketch; submits a PNG data URL.

  • audio_recorder — Record audio (microphone) and submit the clip.

  • camera_capture — Capture a still photo (camera) and submit it.

  • media_player — Play an audio/video asset referenced by the result.

  • image_gallery — Browse a set of images from the result.

  • file_upload — Select/drag files and submit their contents.

GatewayConfig

FieldTypeDefaultSummary
adminAdminConfig(see type)Admin HTTP surface — /admin/* routes, mutual-TLS or bearer-token auth, the operator-facing disclosure_level gate that controls how much detail diagnostic endpoints expose. Defaults to disabled; production deploys mount it behind an internal-only listener.
config_overlayarray<string>Ordered list of config_provider URIs to snapshot at gateway boot + deep-merge into an overlay value (spec §9.16). Lives here (rather than per-plugin) because it's gateway-process bootstrap config. Each URI must use a scheme bound by a registered config-provider plugin.
config_watchConfigWatchConfig(see type)File-watch config-reload trigger (third trigger, alongside SIGHUP and POST /admin/v1/config:reload). Background task polls the MCPG_CONFIG source set on disk and triggers a hot-reload when contents change. Default disabled. See [ConfigWatchConfig] for tuning. Useful for bare-metal systemd deployments and K8s deployments without the MCPG operator (which already does cluster-level config propagation via mcpg.dev/config-hash annotation forcing rolling restart).
control_planeControlPlaneAttachConfig (optional)Optional Control Plane attachment. When set AND the cp-attached Cargo feature is built in, the gateway registers with the CP at boot, opens an agent Channel, and ships per-tool-call samples for centralized observability. When the feature isn't built in, this block is silently ignored.
inspectorInspectorSidecarConfig(see type)Supervised inspector sidecar (mcpg --inspector, or enabled: true here): the gateway spawns a sibling mcpg-inspector serve pre-wired against this gateway with a per-boot loopback credential.
plugin_registryPluginRegistryConfig(see type)OCI plugin-registry configuration. Lives here (rather than per-plugin) because it's gateway-process tuning — where to fetch plugin artifacts from — not per-plugin config. Per-plugin source auth/tls live inline in each plugin entry's source.{auth,tls}:. Only consulted when at least one plugin entry uses source: { oci: ... }.
secretsSecretsConfig(see type)Directory-backed ${secret.NAME} values, hot-reloaded on change. ${secret.NAME} resolves to the bytes of <dir>/<NAME> in the same config-load pass as ${env.X}; a background poller reloads the gateway when any file in dir changes. See [SecretsConfig]. Managed gateways get this block from the platform (a mounted Secret volume); self-hosted gateways point it at any directory.
serverServerConfig(see type)Listener configuration — bind address, transport mode (HTTP / stdio / SSE), TLS, allowed origins, and per-request timeouts. The block is mandatory in practice (the listener won't bind without bind_address) but defaults to a localhost dev-mode shape so out-of-the-box mcpg boots without any config.

GovernanceConfig

Tool-call governance lifecycle: who → allowed? → extra gate → recorded → within limits.

Every child defaults to its own zero-value: an empty governance: block is valid YAML and produces a fully-default configuration (anonymous identity, untrusted-by-default policy, no human-gate signing key, audit channel disabled, no quotas declared).

FieldTypeDefaultSummary
accessAccessConfig(see type)Inbound identity establishment — JWKS-backed JWT verification or OIDC discovery + introspection. When unset the gateway accepts unauthenticated callers and stamps every request with identity_kind: anonymous so the policy gate can deny them. jwks and oidc_oauth are mutually exclusive.
approvalsApprovalsConfig(see type)Tool-gate human approval — signing key + callback base url + grace window. When unset, the runtime defaults to a random per-process signing key + empty callback base url (suitable for tests + dev only — production deploys must supply a stable signing key).
auditAuditConfig(see type)Compliance-grade event sink fan-out (spec §9.12). Lives under governance: (rather than observability:) so the audit-as-evidence-of-governance story reads alongside access / policy / approvals.
child_invokeChildInvokeConfig(see type)Authorization for agentic child tool calls — the backend-to-backend invoke_tool path an LLM Generator drives when it emits tool_calls. Off by default.
policyPolicyConfig(see type)Tool-access policy — default minimum trust level + per-tool override rules. Operators can also point at a Cedar / Casbin / OPA bundle plugin under plugins[] to delegate the actual decision; this block stays useful for the gateway-internal default-trust gate every tool flows through before any plugin policy fires.
quotasQuotasConfig(see type)Registry of named rate-limit / budget / concurrency policies. Bindings opt into specific policies by id via their per-binding quotas: block. Storage backend is a kind: slot under governance.quotas.store:.

HealthCheckConfig

FieldTypeDefaultSummary
degraded_latency_threshold_msinteger1000
enabledbooleantrue
interval_msinteger30000Milliseconds between probe cycles. Default: 30000 (30 s).
timeout_msinteger2000
unhealthy_thresholdinteger3

HealthProbeConfig

Periodic health-probe configuration. The probe is the only writer of PluginState::Degraded; without it plugins stay perpetually Active regardless of whether they're actually responding.

FieldTypeDefaultSummary
enabledbooleantrueProbe plugins periodically. Default: true. Set false to turn off the prober entirely (the Degraded state then never flips — test-only or historical deployments).
failure_thresholdinteger3Consecutive failures before flipping Active → Degraded. Default: 3.
interval_msinteger30000Milliseconds between probe cycles. Default: 30000 (30s).
probe_timeout_msinteger5000Per-probe deadline in milliseconds. A plugin whose FFI call exceeds this is counted as a failure. Default: 5000 (5s).

HmacAlgorithm

The hash of an HMAC signature.

Variants:

  • sha1 — HMAC-SHA1, 20-byte digests.

  • sha256 — HMAC-SHA256, 32-byte digests.

  • sha512 — HMAC-SHA512, 64-byte digests.

IdempotencyConfig

idempotency: config — opt-in dedupe for tools/call and tasks/create. When enabled: false (the default), the gateway omits the dev.mcpg/idempotency extension from its initialize capability advertisement and silently ignores any _meta["dev.mcpg/idempotency-key"] the caller sets.

FieldTypeDefaultSummary
conflict_policyConflictPolicy"reject"Conflict policy — only reject for v1.
default_ttl_msinteger86400000Default TTL applied to any reservation. Default 86_400_000 (24 hours) — matches Stripe's window.
enabledbooleanfalseMaster switch. Default false (opt-in, like governance.quotas was before it became cargo-feature-gated).
max_ttl_msinteger604800000Hard upper bound on per-record TTL. Default 604_800_000 (7 days). Future per-binding idempotency.ttl_ms overrides saturate at this cap.
replay_revalidationbooleanfalseWhen true, a completed-replay hit re-runs the full pre-dispatch authz stack (external policy chain + tool_gate plugins) before serving the cached envelope, so authorization revoked since the original call is honored within the record TTL. Default false: only the built-in trust-floor + CEL allow_if is re-checked on replay (the cheap, side-effect-free layer).
scopeIdempotencyScopeKind"per_identity"Scope strategy — per_identity (default), per_session, or per_tenant.
storeStoreOverrideConfig (optional)Per-capability store: override. Same shape as tasks.store / sessions.store. When unset, the idempotency KV inherits from the cluster coordinator's key_value_store() primitive.
supported_methodsarray<string>(see type)JSON-RPC methods this extension applies to. Default ["tools/call", "tasks/create"] — read-only methods (resources/read, prompts/get, completion/complete) are intentionally excluded as they're idempotent by nature.

IdempotencyScopeKind

Default scope strategy for idempotency records. Operator can widen to per_tenant (service-account dedupe) or narrow to per_session (ephemeral test harnesses).

Note: global is intentionally NOT a variant — cross-tenant replay is a known anti-pattern, so we don't expose the footgun.

Variants:

  • per_session — All requests sharing one MCP session share the namespace. Useful for ephemeral test harnesses; resets on every re-initialize.

  • per_identity — All requests sharing one resolved identity (OIDC subject + auth provider) share the namespace. The default — matches Stripe / Square idempotency semantics.

  • per_tenant — All requests sharing one tenant id share the namespace. Useful for service-to-service workloads where multiple service accounts retry the same operation.

IdpSessionsConfig

The stored IdP sign-in (interactive.idp_sessions): one per user, shared by every MCP client of that user, used only toward the IdP token endpoint that issued it (to check the sign-in, and as the RFC 8693 subject token of an idp_refresh_token or idp_id_token federation). It is never given to an MCP client.

FieldTypeDefaultSummary
connect_pagebooleantrueServe /oauth/connect, where a user who reaches the gateway with an ID-JAG signs in once so federations can use their IdP sign-in. It also lets a federated tool call of a caller with no stored sign-in answer with a link to it (MCP URL-mode elicitation), when the client declares elicitation.url: the link is for that caller only, and a sign-in as anyone else through it stores nothing.
max_age_secsinteger (optional)Longest a stored sign-in is kept, in seconds (3600–31536000); each successful refresh at the IdP restarts it. Defaults to refresh_tokens.absolute_ttl_secs.
revoke_supersededbooleantrueRevoke at the IdP (RFC 7009) a replaced IdP refresh token of another IdP subject the claim mappings name the same user, when the IdP has a revocation endpoint. A replaced token of the same subject is never revoked: some IdPs (Keycloak, PingFederate) revoke per user and client, which would end the new sign-in too.

ImportConfig

Which capability surfaces to import.

FieldTypeDefaultSummary
eventsbooleanfalseImport the upstream's MCP Events types. Each serves downstream from the gateway's own log, in every delivery mode the gateway enables, and is fed by one upstream subscription per distinct set of arguments in demand, held with this federation's catalogue credential. Needs an upstream on the 2026-07-28 wire over streamable_http that advertises events; any other imports none.
promptsbooleanfalse
resource_templatesbooleanfalse
resourcesbooleanfalse
toolsbooleantrue

InboundHandshakeConfig

An entry of handshakes: — a request the route answers itself.

FieldTypeDefaultSummary
replymap<string, string>The JSON object of a 200 reply: each key takes the string at a JSON pointer into the body, as a URL challenge is echoed. Omitted, the route acknowledges with 202.
whenInboundWhenConfigThe requests it answers. Every listed header and body field must match.

InboundProfileConfig

An entry of inbound_profiles: — how one kind of sender signs its webhooks: an HMAC of a message built from the raw body, a signed timestamp and headers. Routes name it in verify.profile.

FieldTypeDefaultSummary
algorithmHmacAlgorithm"sha256"The HMAC hash. Default sha256.
event_idInboundValueConfig (optional)Where the sender puts its event id. A route's own event_id takes its place.
event_timestring (optional)JSON pointer to the event time in the body, RFC 3339 or Unix seconds. A route's own timestamp takes its place.
handshakesarray<InboundHandshakeConfig>Requests the route answers itself, never as events: a sender's ping or URL challenge. The first match applies.
namestringThe name routes give in verify.profile. At most 64 characters of [a-z0-9_-]; a built-in name cannot be declared again.
secretSecretFormatConfig(see type)How a configured secret becomes the HMAC key.
signatureSignatureFormatConfigWhere the request carries its signatures.
signed_payloadstring"{body}"The signed message. {body} is the raw body and must appear once; {timestamp} is the signed timestamp; {header:NAME} is the value of request header NAME. Default {body}.
signed_timestampSignedTimestampConfig (optional)The signed Unix-seconds timestamp, for a sender that signs one. A request outside the tolerance is refused as stale.

InboundRateLimitConfig

Request rate of one inbound source on each replica.

FieldTypeDefaultSummary
burstinteger100Requests accepted in a burst. Default 100.
per_secinteger50Sustained requests per second. Default 50.

InboundSourceConfig

source.inbound: — a signed inbound webhook route.

FieldTypeDefaultSummary
allow_unsignedbooleanfalseAccept requests nobody signed. Anyone who can reach the route can then inject events. Default false.
event_idstring (optional)JSON pointer to the sender's event id in the body (for example /id). Omitted, the scheme's id is used: the webhook-id header of Standard Webhooks, X-GitHub-Delivery of GitHub, /id of Stripe, /event_id of Slack; with none, the gateway mints an id. A repeated id is accepted once.
rate_limitInboundRateLimitConfig(see type)Requests accepted per second on each replica; past it the route answers 429 with Retry-After.
source_idstringThe route segment: senders POST to /webhooks/events/{source_id}. At most 64 characters of [A-Za-z0-9_-]. Types that share a route each take the requests their when keeps, and declare the same verify, allow_unsigned and rate_limit.
timestampstring (optional)JSON pointer to the event time in the body, RFC 3339 or Unix seconds. Omitted, the scheme's time (/created of Stripe, /event_time of Slack), else the time the request arrived.
verifyInboundVerifyConfig (optional)How a request proves its sender. Required unless allow_unsigned is set.
whenInboundWhenConfig (optional)Requests to keep; any other request is acknowledged and dropped.

InboundValueConfig

A value of the request. Name exactly one of header and pointer.

FieldTypeDefaultSummary
headerstring (optional)A request header.
pointerstring (optional)A JSON pointer into the body.

InboundVerifyConfig

verify: — how an inbound request proves its sender: a signing profile and this route's secrets.

FieldTypeDefaultSummary
profilestringThe signing profile: a built-in one (standard_webhooks, github, stripe, slack) or the name of an entry of mcp.configurations.events.inbound_profiles.
secretsarray<string>Shared secrets, usually ${secret.NAME}. Several are accepted at once during a rotation.
tolerance_secsinteger (optional)Largest age or skew of the signed timestamp, in seconds, in place of the profile's. Only for a profile that signs a timestamp.

InboundWhenConfig

when: — the requests an inbound source keeps. Every listed header and every listed body field must match.

FieldTypeDefaultSummary
bodymap<string, any>{}JSON pointer into the body to the exact JSON value it must hold.
headermap<string, string>{}Header name to the exact value it must carry.

InspectorSidecarConfig

FieldTypeDefaultSummary
bindstring (optional)host:port of the inspector's web UI + API (single origin). Defaults to the inspector's own 127.0.0.1:7846.
enabledbooleanfalseSupervise an mcpg-inspector sidecar. --inspector flips this on for a single run.

InstanceId

Server-assigned stable instance id. Carried as an opaque string so the gateway never needs to parse it; the CP mints it (UUIDv7 today).

Type: string

InteractiveLoginConfig

Settings of interactive sign-in (governance.access.authorization_server.interactive). Every key has a default, so the block is needed only to change one; it is refused without a trusted_idps[].login entry. Requires a license with the sso.interactive_login feature.

FieldTypeDefaultSummary
access_token_ttl_secsinteger900Lifetime of the access tokens issued through sign-in, in seconds (60–3600, and at most refresh_tokens.idle_ttl_secs). ID-JAG redemptions keep authorization_server.access_token_ttl_secs. A revoked grant's access tokens are refused within revocation_check_interval_secs; a short lifetime also bounds how long a stolen one works.
authorization_code_ttl_secsinteger60Lifetime of an authorization code, in seconds (10–600). A code is redeemed once; a second redemption revokes the grant it issued.
consentConsentConfig(see type)The consent page shown before the user is sent to the IdP.
dynamic_client_registrationDynamicClientRegistrationConfig(see type)RFC 7591 dynamic client registration at POST /oauth/register.
idp_sessionsIdpSessionsConfig(see type)The stored IdP sign-in of each user.
rate_limit_per_mininteger120Requests allowed per minute from one client IP address across the browser endpoints (/oauth/authorize, /oauth/consent, /oauth/callback, /oauth/connect); the IP is found as for authorization_server.rate_limit_per_min, and each replica counts its own requests. 0 disables the limit.
refresh_tokensRefreshTokensConfig(see type)Refresh tokens issued to MCP clients that signed in.
revocation_check_interval_secsinteger10How often each replica reads the revoked grants other replicas wrote, in seconds (2–60): a revoked grant's access tokens are refused everywhere within this long.
state_keysarray<StateKeyConfig>[]Keys that seal every stored record (XChaCha20-Poly1305), newest first: the first seals, every entry opens, so a rotation prepends the new key and keeps the old one for refresh_tokens.absolute_ttl_secs. Unset, the key derives from cluster.state_encryption_key_env when that is set. Otherwise a file store generates a key on first start and keeps it at state.key inside the store directory, readable by the gateway's user only (mode 0600) and never logged: back it up with the store, since the stored sign-ins cannot be opened without it. To move that key here, list it with kid generated and the file's contents as its secret. A memory store uses a key that lives as long as the process. The cluster store refuses to start without a key from here or the cluster.
storeInteractiveStoreConfig (optional)Where grants, refresh tokens and stored IdP sign-ins live. Unset: the cluster coordinator's key-value store when cluster.kind is not single_node; on a single node, a file store in the default store.dir, so users stay signed in across restarts. Every record is sealed with the state key (state_keys).
transaction_ttl_secsinteger600Time a user has to approve consent and sign in at the IdP, in seconds (60–1800). Also the lifetime of the sign-in state.

InteractiveStoreConfig

Where interactive sign-in state lives (interactive.store).

FieldTypeDefaultSummary
dirstring (optional)file only: the directory. Defaults to $MCPG_STATE_DIR/oauth; else /var/lib/mcpg/oauth where /var/lib/mcpg exists (the container image; the operator's runtime volume, which lasts as long as the pod; the Helm chart's volume with persistence.enabled); else ~/.mcpg/oauth. A gateway that cannot open it refuses to start. It is created readable by the gateway's user only, and one gateway process at a time opens it (a second one refuses to start). Every write is flushed to disk before it counts, expired records are removed every minute, and at most 200000 records (256 MiB) are kept: sign-ins beyond that answer 503.
kindInteractiveStoreKindcluster (the cluster coordinator's key-value store; on single_node that is process memory), memory (process memory: every restart signs every user out) or file (a directory on local disk). A memory or file store holds one replica's state only, so it is refused when cluster.kind is not single_node.

InteractiveStoreKind

Kind of interactive.store.

Allowed values:

  • cluster
  • memory
  • file

JwksConfig

FieldTypeDefaultSummary
allow_missing_audiencebooleanfalseDev escape-hatch: allow tokens without audience binding. Production MUST set an audience.
audiencestring (optional)
audiencesarray<string>[]Further accepted audiences, alongside audience — one per extra resource identifier the gateway is reached at, so a token bound to any bound hostname verifies. A token passes when its aud names at least one accepted audience.
header_namestring"authorization"
header_prefixstring"Bearer "
issuerstring (optional)
keys_jsonstring (optional)
urlstring""

KindRef

Discriminator + config payload at every consumer slot. Operators write { kind: <value>, ...config } in YAML; the gateway parses it as this type and resolves via [resolve_kind].

The config: field is a free-form JSON object passed to the resolved handle (built-in or plugin). The slot's resolver validates kind; the implementation validates config.

FieldTypeDefaultSummary
configanyInline config forwarded to the resolved handle. Empty by default.
kindstringDiscriminator. One of: built-in keyword, full plugin id, short alias, or cluster.

LicenseConfig

The licence of a gateway that mcpg.cloud does not license. A gateway mcpg.cloud delivers a licence to — the delivery carries a proof, signed with an MCPG key, that mcpg.cloud made it on that very connection — ignores the whole block (token, token_file and non_production_use) and runs on that licence, so a config never chooses its own licence there. Under a customer's own control plane, and until a delivery arrives, the block counts, and takes precedence over a self-host licence that control plane relays.

FieldTypeDefaultSummary
non_production_usebooleanfalseDeclares this deployment non-production. Entitlement-gated plugins and feature-gated config blocks (interactive sign-in, DPoP and rich authorization requests at the embedded authorization server) then load without a token under their license's free non-production grant (development, testing, evaluation, staging). The declaration is visible: a boot warning repeated daily, the admin health endpoint's license.non_production, and the non_production label of the mcpg_license_info metric. Production use still requires an entitling token. A gateway mcpg.cloud delivers a licence to ignores it.
pubkey_pemstring (optional)Deprecated and ignored. A license verifies only against the MCPG issuer keys compiled into the binary, never a key from configuration; setting this logs a warning.
tokenstring (optional)The MCPG-issued self-host license JWT, inline. Supply via ${env.X} (commonly ${env.MCPG_LICENSE}) or ${secret.NAME}. A secret-provider reference (cred://, or a vault:// path) is not resolved here: the licence is read before any plugin loads, because it decides which plugins may load, and the Vault provider is itself a licensed plugin. Exactly one of token / token_file may be set. It verifies against the MCPG issuer keys compiled into the binary. A token that does not verify refuses boot when the config has an entitlement-gated plugin or a licensed authorization-server block, rather than silently degrading them to community; with neither, the gateway logs a warning and runs the community tier. An expired token keeps its plan for a 14-day grace period, with warnings from 30 days before expiry.
token_filestring (optional)Path to a file holding the MCPG-issued self-host license JWT (e.g. a mounted secret), read verbatim. Exactly one of token / token_file may be set.

LoginAssertionAudience

The aud of the client assertion.

Variants:

  • token_endpoint — The URL of the IdP endpoint the assertion is posted to: the token endpoint, or the revocation endpoint when revoking.

  • issuer — The IdP's issuer identifier.

LoginClientAuth

How the gateway authenticates at the IdP token endpoint.

Allowed values:

  • client_secret_basic
  • client_secret_post
  • private_key_jwt

LoginSigningAlg

JWS algorithm of the client assertion the gateway signs.

Allowed values:

  • RS256
  • PS256
  • ES256
  • EdDSA

LogsConfig

observability.logs: — the logs signal.

Gateway internals (every tracing::info!() / warn!() / error!() call) AND plugin-emitted log events both flow through the configured sink list. Default sinks ship one stderr JSON emitter — production deployments add file, otlp, or plugin sinks (Loki, Splunk, …) by appending entries to sinks:.

FieldTypeDefaultSummary
enabledbooleantrueMaster enable for the logs signal. When false, no log events are emitted regardless of sinks: content.
levelstring"info"Signal-level severity floor (trace / debug / info / warn / error). Per-sink level: can raise it further but can't lower it below this floor. Default: info.
sinksarray<SinkConfig>(see type)Sink fan-out. Each entry's kind: resolves to a built-in factory (stderr / stdout / file / otlp) or plugin id. Default: one stderr sink with JSON format.

MapAppConfig

Map binding for kind == map.

FieldTypeDefaultSummary
geojson_pathstring (optional)JSON-path to a GeoJSON FeatureCollection (alternative to lat/lng).
label_fieldstring (optional)
lat_fieldstring (optional)
lng_fieldstring (optional)
modeMapRenderMode"plot"Render mode. plot (default) needs no network; raster_tiles fetches from tile_url and requires a CSP allowance.
popup_fieldstring (optional)
select_actionstring (optional)Tool a region-draw selection invokes (geometry passed as args).
tile_urlstring (optional)Raster tile template URL (raster mode only).

MapRenderMode

Map rendering mode.

Variants:

  • plot — Coordinate plot on a canvas — zero network, no CSP delta.

  • raster_tiles — Raster basemap tiles — needs tile_url + a CSP allowance.

McpCapabilitiesConfig

mcp.capabilities: — the MCP protocol-advertised surface.

Matches the protocol's initialize handshake's capabilities vocabulary: tools / prompts / resources / resource_templates, plus the gateway-side feature configs that govern protocol behaviour (tasks, elicitation, sampling, roots).

FieldTypeDefaultSummary
elicitationMcpElicitationConfig(see type)MCP elicitation — server-initiated prompt requests the gateway emits during pipeline execution.
eventsarray<EventTypeConfig>MCP Events (experimental) event types — what events/list offers and the delivery modes deliver. Each type binds one source: a plugin watch strategy, a signed inbound webhook route, a resource binding's watch, or an event the gateway raises itself. Served only while mcp.configurations.events.enabled is true.
promptsarray<BackendConfig>[]Operator-declared prompts — the bindings that surface via prompts/list and prompts/get. Carry prompt_arguments.
resource_templatesarray<BackendConfig>[]Operator-declared resource templates — the bindings that surface via resources/templates/list and match resources/read URIs by template. Carry uri_template instead of uri.
resourcesarray<BackendConfig>[]Operator-declared resources — the bindings that surface via resources/list and resources/read. Carry uri, mime_type, mcp_app_url, watch config.
rootsMcpRootsConfig(see type)MCP roots-list — gateway requests roots from the client (e.g. for resource scoping).
samplingMcpSamplingConfig(see type)MCP sampling — server-initiated LLM completion requests forwarded back to the client.
tasksTasksConfig(see type)MCP Tasks — task-augmented tool-call semantics. Carries the task store override, TTL/reaper tuning, and the task-supported tools list.
toolsarray<BackendConfig>[]Operator-declared tools — the bindings that surface via tools/list and tools/call. Each entry has its own implementation backend: (HTTP / SQL / NATS / LLM / pipeline / …) plus tool-specific MCP fields (annotations, task_support).

McpConfig

Top-level mcp: block — the MCP protocol surface.

Two children that mirror MCP's own vocabulary: - capabilities: — what the server advertises in the initialize handshake (tools, prompts, resources, resource_templates, tasks, elicitation, sampling, roots). - configurations: — runtime-emergent state handling (sessions, pipelines, subscriptions, delivery, cancellation). Operator only tunes persistence + constraints; the items themselves are created by clients at runtime.

FieldTypeDefaultSummary
capabilitiesMcpCapabilitiesConfig(see type)What this MCP server advertises in the initialize handshake. Mirrors the protocol spec's capabilities vocabulary one-to-one.
configurationsMcpConfigurationsConfig(see type)Runtime-emergent state handling. Operator only tunes persistence + constraints; the items themselves are created by clients at runtime.
federationsarray<FederationConfig>[]Upstream MCP servers federated through this gateway. Each entry is a capability source (1:N): MCPG connects to the upstream, imports its capabilities, and re-serves them under a prefix. Default empty — existing configs are unaffected.
instructionsstring (optional)The instructions a client receives in server/discover and in the initialize result, typically shown to the model: what this server is for and how to use its tools. At most 64 KiB. Unset keeps MCPG's own text.
list_page_sizeinteger (optional)How many entries one page of tools/list, prompts/list, resources/list and resources/templates/list holds, on every wire; a client follows nextCursor for the rest. 1 to 1 000; unset is 100. A client that reads only the first page sees only this many.
realtimeRealtimeConfigRealtime delivery (MCP Events streams, polls and webhook subscriptions; resource subscriptions): an admission tool the gateway asks before, during and after a caller holds it. Unset admits every caller the policy allows.
registriesarray<McpRegistryConfig>[]MCP registries whose listed servers MCPG auto-federates: a background syncer crawls each registry's /v0.1 API and materializes one federation per usable server, kept in sync as the registry changes. Default empty.
registryServedRegistryConfig(see type)The registry MCPG serves (contrast registries, the registries MCPG consumes): a v0.1 MCP-Registry view of this gateway — one entry describing the governed catalog — so registry-driven clients (e.g. Copilot's allowed-registry policy) can discover MCPG as their approved server. Off by default.
server_infoMcpServerInfoConfig{}How the server presents itself to clients: serverInfo in server/discover and in the initialize result. Every field is optional; an unset field keeps MCPG's own value. version is always the gateway's.
skillsSkillsConfigAgent Skills served over the MCP Skills extension (io.modelcontextprotocol/skills): folders with a SKILL.md, listed with skills/list and read with resources/read. Unset serves none.

McpConfigurationsConfig

mcp.configurations: — runtime-emergent state handling.

FieldTypeDefaultSummary
appsAppsConfig(see type)io.modelcontextprotocol/ui (SEP-1865 MCP Apps) extension config. Off by default; enabled: true lights up the capability advertisement (downstream + upstream) and the tighten-only CSP/permission egress policy.
cancellationCancellationConfig(see type)
deliveryDeliveryConfig{}
eventsEventsConfig(see type)io.modelcontextprotocol/events (MCP Events, experimental) extension config. Off by default; enabled: true serves the events/* methods on the 2026-07-28 wire and advertises them in server/discover.
idempotencyIdempotencyConfig(see type)dev.mcpg/idempotency extension config. Off by default; flipping enabled: true lights up the SEP-2133 capability advertisement and the dispatcher dedupe path.
pipelinesPipelinesConfig{}
request_stateRequestStateConfig(see type)MRTR requestState codec configuration (2026-07-28 modern wire). Inert when no modern client connects; absent encryption_key the codec uses an ephemeral key at boot.
sessionsSessionsConfig(see type)
subscriptionsSubscriptionsConfig(see type)

McpElicitationConfig

mcp.elicitation: — gateway behavior for server-initiated elicitation prompts. Today carries only timeout_ms; future per-elicitation-type config can grow here.

FieldTypeDefaultSummary
timeout_msinteger60000Maximum time the gateway waits for the client's response to an elicitation prompt before giving up. Default 60 000.

McpRegistryConfig

One MCP registry to auto-federate.

FieldTypeDefaultSummary
authRegistryAuthConfig(see type)How MCPG authenticates to the registry (consumer side).
defaultsRegistryDefaultsConfig(see type)Applied to every synthesized federation.
filterRegistryFilterConfig(see type)Which registry servers are eligible for federation.
namestringRegistry id. Prefixes every synthesized federation name (<registry>--<server>), so it must be stable and unique. Lowercase alphanumeric + -.
on_deprecatedOnDeprecated"serve_and_warn"What happens to servers the registry marks deprecated. (deleted servers are always removed.)
registry_safetyRegistrySafetyConfig(see type)Network posture for the REGISTRY endpoint itself. Distinct from the per-server defaults.upstream_safety: a private registry URL is normal for enterprises, but stays an explicit opt-in.
serversmap<string, RegistryServerOverride>{}Per-server overrides, keyed by the server's registry name (e.g. com.acme/crm).
syncRegistrySyncConfig(see type)Sync cadence + size bounds.
urlstringRegistry base URL; the syncer appends the standard API paths (/v0.1/servers, …).

McpRootsConfig

mcp.roots: — gateway behavior for server-initiated roots-list requests forwarded to the client.

FieldTypeDefaultSummary
timeout_msinteger30000Maximum time the gateway waits for the client's roots-list response. Default 30 000.

McpSamplingConfig

mcp.sampling: — gateway behavior for server-initiated sampling (LLM completion) requests forwarded to the client.

FieldTypeDefaultSummary
timeout_msinteger60000Maximum time the gateway waits for the client's sampling response. Default 60 000.

McpServerInfoConfig

mcp.server_info: — the operator's serverInfo fields.

FieldTypeDefaultSummary
descriptionstring (optional)One-paragraph description shown next to the title.
iconsarray<BackendIconConfig> (optional)Branding icons (src, mime_type, sizes, theme).
namestring (optional)Machine-readable server name, e.g. com.example.docs. Default mcpg.
titlestring (optional)Human-readable display name.
website_urlstring (optional)The server's website.

MetricsConfig

observability.metrics: — the metrics signal.

Gateway internals (every metrics::counter!() / gauge!() / histogram!()) flow through the configured sink list. The canonical Prometheus exporter is a plugin: operators wire kind: dev.mcpg.observability.prometheus. The sinks: [] list otherwise carries plugin ids (dev.acme.observability.datadog, etc.) — there are no built-in factory kinds for metrics.

FieldTypeDefaultSummary
enabledbooleantrueMaster enable for the metrics signal. When false, no metric recorders are installed.
sinksarray<SinkConfig>[]Sink fan-out. Every entry's kind: is a plugin id (there is no kind: prometheus / kind: otlp shorthand), loaded from a plugins[] row. Default: none, so /metrics is not served until a dev.mcpg.observability.prometheus sink is listed.

NamingConfig

Prefixes applied to imported capability names / URIs.

FieldTypeDefaultSummary
event_prefixstring (optional)Prepended to every imported event type name (e.g. "github."). The result must still be a valid event name.
prompt_prefixstring (optional)Prepended to every imported prompt name.
resource_uri_prefixstring (optional)Prepended to every imported resource URI (e.g. "mcp://notion/").
tool_prefixstring (optional)Prepended to every imported tool name (e.g. "notion.").

NotificationFilterConfig

Scoping filter for resource change notifications. Determines which subscribers receive notifications/resources/updated when the watch engine detects a change.

Variants:

  • (unnamed variant) — Fan-out to all subscribers (default, no filter).

  • (unnamed variant) — Only notify subscribers whose principal_id matches the event's user context.

  • (unnamed variant) — Only notify the originating session.

  • (unnamed variant) — CEL expression evaluated per subscriber. Variables: subscriber.principal_id, subscriber.trust_level, subscriber.roles, subscriber.groups, subscriber.scopes, subscriber.attributes, event.uri.

    • expression: string
    • scope: string

OAuthResourceMetadataConfig

Configuration for the OAuth Protected Resource Metadata endpoint (RFC 9728).

FieldTypeDefaultSummary
additional_resourcesarray<string>[]Further resource identifiers this gateway is reached at — one per extra hostname (a custom domain in front of the same instance). A client compares the published resource with the URL it connected to (RFC 9728 §3.3), so the metadata document and the WWW-Authenticate challenge name whichever of resource and these matches the request's Host; a request for an unlisted host gets the canonical resource. Each entry is validated like resource.
allow_loopback_resourcebooleanfalseLocal-development escape hatch: permit a loopback resource (localhost / 127.0.0.1 / [::1]). A wildcard host (0.0.0.0 / [::]) is NEVER a valid resource identifier and is refused even with this set. Production deployments leave this false and configure the canonical public URL.
authorization_serversarray<string>[]Authorization server issuer URLs published in the metadata. When empty, derived from the configured verifiers: the authorization_server issuer first, then the oidc_oauth provider issuers, then jwks.issuer; with interactive sign-in (a trusted_idps[].login entry) the authorization_server issuer alone, since a client signs in at the first one listed. An explicit list is published as written, so with authorization_server it must name that issuer exactly as configured there, a trailing / included, or EMA clients cannot discover it.
bearer_methods_supportedarray<string>(see type)Bearer token presentation methods. Defaults to ["header"].
resourcestringThe protected resource's canonical resource identifier (RFC 8707 resource / RFC 9728 resource). MUST be the real external, absolute URL clients reach the gateway at — the same value the authorization server binds tokens to as aud. A wildcard (0.0.0.0), bare loopback (localhost/127.0.0.1/[::1]), or derived bind_address value is refused at boot: it would publish a resource that does not match the audience the tokens carry, so audience-bound validation silently fails. Set the canonical public URL explicitly, or opt into the loopback form for local development with allow_loopback_resource: true.
scopes_supportedarray<string>[]Scopes supported by this resource. Also sent as the scope parameter of the WWW-Authenticate challenge on an unauthenticated 401, as the scopes a client requests first; one that is not an RFC 6749 scope token (a space, ", \, a control or non-ASCII character) is left out of the challenge.

ObservabilityConfig

Top-level observability: block — the OpenTelemetry signal triad (logs / metrics / traces) plus audit fan-out.

Each signal carries a sinks: [...] list of [SinkConfig] entries. Each entry's kind: field dispatches to either a built-in sink factory (stderr / stdout / file / otlp / prometheus) or a plugin id resolved against the gateway's plugin registry at boot.

Master switch. enabled: false (default true) silences every child regardless of their own enabled: flags — useful for embedded use cases where the host process owns observability or for minimal-footprint test runs. Each child also has its own enabled: for finer-grained control. The accessor helpers below (is_logs_on(), is_metrics_on(), is_traces_on(), is_audit_on()) implement the AND-fold so call sites can't forget either flag.

FieldTypeDefaultSummary
enabledbooleantrueMaster kill switch. When false, every child is treated as disabled regardless of its own enabled: field — no logs emitted, no metrics endpoint registered, no traces pipeline started, no audit fan-out wired. Default true.
logsLogsConfig(see type)Logs signal — gateway internals + plugin-emitted log events fanned to the configured sink list.
metricsMetricsConfig(see type)Metrics signal — gateway internals + plugin-emitted metric events fanned to the configured sink list.
plugin_call_sampling_ratenumber (optional)Per-call span sampling rate for native-plugin host-side spans. Range [0.0, 1.0]; None inherits the global subscriber sampler (no extra dampening).
plugin_health_probeHealthProbeConfig(see type)Plugin health probe. Lives under observability: because the probe is observability-shaped (it watches plugin liveness and writes PluginState::Degraded for monitoring consumers).
tracesTracesConfig(see type)Traces signal — span lifecycle events fanned to the configured sink list.

OidcOAuthConfig

FieldTypeDefaultSummary
providersarray<OidcProviderConfig>One or more identity providers. At least one is required.
token_sourceTokenSourceConfig(see type)How to extract the bearer token from the request.

OidcProviderConfig

FieldTypeDefaultSummary
allow_any_audiencebooleanfalseExplicit opt-in to SKIP audience (aud) validation. When false (the default) an empty audiences is a hard config error at boot — otherwise a mistyped audiences key would silently disable audience binding and accept tokens minted for any gateway. Production MUST leave this false and set audiences; only the rare provider that genuinely issues no aud claim should opt in.
allow_private_issuerbooleanfalseDev escape hatch: permit private/loopback ranges in OIDC URLs. Production MUST leave this false.
allowed_issuer_hostsarray<string>[]Optional hostname allowlist for OIDC discovery and JWKS fetches. Empty means only the private-range blocklist applies.
audiencesarray<string>[]
claim_mappingsClaimMappingConfig(see type)
clock_skew_secsinteger60
discovery_uristring (optional)
issuerstring
verificationVerificationConfig

OnDeprecated

Policy for deprecated servers.

Variants:

  • serve_and_warn — Keep federating; log + count the deprecation.

  • exclude — Drop the federation (clients see list_changed).

OperationToolConfig

One tool that proposes an operation.

FieldTypeDefaultSummary
action_keystring (optional)The same action proposed again with this key, while its operation is still proposed, revises that operation instead of proposing a new one: equal terms return it as it is, other terms make the next revision. A template over the arguments; unset, every call proposes a new operation.
expires_after_secsinteger900How long a revision can be confirmed, in seconds (60–86400).
summarystringThe terms in plain language, so the User can decide from them alone: what will happen, to what, and any cost, date or condition. A template over the call's arguments (${arguments.order_id}). The operation's terms are the validated arguments.
toolstringThe MCP tool name.
urlstring (optional)A page of the Company's website that shows the operation, a template over the arguments.
user_approval_requiredbooleanfalseAccept only the User's approval of each revision, not a standing permission.

OperationsConfig

governance.access.personal_agent.operations.

FieldTypeDefaultSummary
confirm_wait_secsinteger5How long a confirm waits for the action to finish before it answers in_progress with Retry-After, in seconds (1–30).
endpointstringThe operations endpoint's public URL, published in poppy.json as extensions.operations.endpoint: an https URL on the origin of a resource_metadata resource, with the path /poppy/operations, where the gateway serves it. It takes DPoP Session Tokens only, so governance.access.dpop (or the embedded server's dpop) must be on.
retain_final_secsinteger604800How long a final operation stays readable, in seconds (3600–2592000).
toolsarray<OperationToolConfig>The tools that propose an operation.
without_extensionOperationsWithoutExtension"perform"What a tool call by a Personal Agent that does not list the extension does: perform runs the tool as without the extension; refuse_agent refuses such a Personal Agent on every request with extension_required, as require_extensions does. Plain MCP clients always run the tool.

OperationsWithoutExtension

See [OperationsConfig::without_extension].

Allowed values:

  • perform
  • refuse_agent

PersonalAgentAttributes

Identity attribute names of the PAP values of a Session Token.

FieldTypeDefaultSummary
accountstring"account"The account the Session is signed in to, set only while it is.
client_idstring"client_id"The Personal Agent's client_id: the URL of its client metadata document.
session_idstring"session_id"The Session the token was issued for.
signed_instring"signed_in"true while the Session is signed in. Without this attribute, a caller is signed in when it has the account attribute.
user_idstring"user_id"The User ID the Personal Agent gave the User at this Company. A plain MCP client's Session has none.

PersonalAgentConfig

The Personal Agent Protocol profile of the resource.

FieldTypeDefaultSummary
agent_extensionsAgentExtensionsConfig(see type)Where the gateway reads the extensions a Personal Agent supports.
attributesPersonalAgentAttributes(see type)The identity attributes that carry the Session Token's PAP values, filled by the verifier's claim mappings (for oidc_oauth, claim_mappings.attribute_claim_mappings). A caller with a client_id attribute is a Personal Agent; one without is a plain MCP client, which signed in with MCP's own flow.
discoveryPoppyDiscoveryConfig (optional)The Company's poppy.json, served at GET /.well-known/poppy.json.
operationsOperationsConfig (optional)The operations extension, version 1: the listed tools propose an operation instead of acting, and the Personal Agent confirms it once at the operations endpoint.
require_extensionsarray<string>[]Extensions a Personal Agent must list to use this server at all: one that does not is refused on every request with 403 and WWW-Authenticate: <scheme> error="extension_required", extension="<name>". Plain MCP clients are not refused. A plain name (operations) is one the protocol defines; any other starts with a domain (example.com/gift-wrap).

PipelinesConfig

pipelines: config — pipeline state store + per-capability store: override. When store is unset, the pipeline KV inherits from the cluster's key_value_store() primitive.

FieldTypeDefaultSummary
storeStoreOverrideConfig (optional)

PluginEntryConfig

Configuration for a single plugin entry.

FieldTypeDefaultSummary
classstring"tool_gate"Plugin class — the snake_case PluginClass variant the plugin implements. Must match the class: field in the plugin's own plugin.yaml manifest. Determines which plugin chain / slot the plugin is registered into. Valid values: tool_gate, transform, identity_provider, backend, watch_strategy, http_route, audit_sink, store, cache, telemetry_sink, log_sink, metrics_sink, secret_provider, config_provider, transport, policy_engine, cluster, catalog_provider, credential_issuer, approval_notifier, content_store.
configanyPlugin-specific configuration passed to the plugin instance.
disabledbooleanfalseWhen true, the plugin entry is parsed + validated but not loaded at boot. Useful for keeping a plugin's config in source control while temporarily turning it off without removing the entry. Default false.
enforcebooleantrueWhen false, the plugin runs in shadow mode: evaluate and log, but override Deny/Challenge → Allow. Defaults to true (enforce).
ffi_limitsPluginFfiLimitsConfig (optional)Per-plugin FFI hardening overrides for native cdylib plugins. None = inherit the spec defaults (1s lifecycle / 5s control / 30s data / 256 KiB payload). Ignored for Wasm plugins (those use limits.timeout_ms).
granted_capabilitiesarray<any>Per-plugin typed host capability grants. Each entry is one of [mcpg_plugin_protocol::capability::Capability]'s known variants. Two equivalent YAML shapes accepted:
http_routePluginHttpRouteConfig (optional)http_route-specific operator tuning. Ignored for non-http_route plugins. Absent = all defaults (enabled, namespaced mount, spec's own body cap + identity policy).
idstringOperator alias for this entry — unique within the gateway's plugins[] array. Used as the registry key, audit attribution, and per-plugin observability target. When ref is omitted, the alias doubles as the artifact's manifest id (the simple, single-instance case). When ref is set, the alias is a separate operator-chosen label (multi-instance pattern).
inline_dispatchbooleanfalseInline fast-slot dispatch. When true, this plugin's hot-path slots are called inline — without the spawn_blocking ferry or per-call timeout: the typed/borrowed *_fast vtable path for Tier-1 slots (tool_gate, cutting dispatch ~33×), and the synchronous execute slot for backend plugins (which also lets the sync tool dispatch bridge resolve the call on its first poll and skip block_in_place). This is an explicit operator-trust decision: the plugin's slots MUST be fast, non-blocking, and bounded, because a hung/blocking slot now wedges a runtime worker with no backstop. Defaults to false (the safe, ferried path). Only enable for trusted, pure-compute / in-process first-party plugins.
kindstring"native"Plugin tier: "native" or "wasm".
limitsPluginResourceLimitsConfig (optional)Resource limits for Wasm plugins (ignored for native).
observabilityPluginObservabilityToggle (optional)Per-plugin observability triad override. inherit (default), replace, or tee semantics for each signal independently. Absent = all signals inherit the global observability.{logs,metrics,traces} config. Routing is keyed by module_path_prefix from the plugin manifest — events from this plugin's crate get the override; events from gateway code about a plugin call stay on the global path.
refstring (optional)Manifest id (artifact identity) — reverse-DNS, e.g. dev.mcpg.policy.cedar. Optional; defaults to id when absent.
signatureSignatureConfig (optional)Plugin signature checks. Consolidates the content hash, the per-entry-overridable verification policy, and the trusted Ed25519 keys this plugin's artifact must verify against (per-entry, no global trust pool).
sourcePluginSourceConfig(see type)Source path or reference for the plugin artifact.

PluginFfiLimitsConfig

Per-plugin FFI hardening overrides for native cdylib plugins.

Native plugin calls are wrapped by the host in spawn_blocking + tokio::time::timeout and bounded RString returns. Defaults are the spec-level constants in mcpg_plugin_protocol::abi (FFI_{LIFECYCLE,CONTROL,DATA}_TIMEOUT_DEFAULT_MS, FFI_MAX_PAYLOAD_BYTES). Operators set per-plugin overrides here to widen the budget for a known-slow plugin (e.g. a backend that proxies an upstream multi-second API) or to tighten the cap on a plugin that has a stricter SLO.

None on any field means "inherit the spec default".

FieldTypeDefaultSummary
control_timeout_msinteger (optional)Control slot timeout override (ms). Applies to config-set, snapshot, version, register-profile, refresh, describe, list-peers, list-catalog, etc. Default: FFI_CONTROL_TIMEOUT_DEFAULT_MS = 5_000.
data_timeout_msinteger (optional)Data slot timeout override (ms). Applies to execute, evaluate, transform, dispatch, http_route, sink-emit, etc. Default: FFI_DATA_TIMEOUT_DEFAULT_MS = 30_000.
lifecycle_timeout_msinteger (optional)Lifecycle slot timeout override (ms). Applies to make, manifest, shutdown, drop_instance, health probes. Default: FFI_LIFECYCLE_TIMEOUT_DEFAULT_MS = 1_000.
max_payload_bytesinteger (optional)Max byte-length of any single RString returned by this plugin to the host. Overflow rejected with a slot-appropriate fallback + bumps mcpg_plugin_payload_oversize_total. Default: FFI_MAX_PAYLOAD_BYTES = 262144 (256 KiB).

PluginHttpRouteConfig

Operator-side tuning for an http_route plugin entry. Every field is optional; the struct is omitted entirely for plugins that don't need any override.

FieldTypeDefaultSummary
allow_path_overridebooleanfalseWhen true, plugin routes mount at the top-level paths the plugin declared (override mode), instead of the namespaced /plugins/{id}/{entity}/ mount (the false default). The gate is this operator-set flag alone; the gateway refuses two plugins that claim the same top-level path. Override-mode dispatch is not yet wired — this field is the gate the dispatcher will consult once that support lands.
disabledbooleanfalseWhen true, the plugin is not registered at all. Operators use this to swap out a gateway built-in (e.g. the built-in dev.mcpg.builtin.http.status) for a custom implementation without patching the gateway. The gateway logs a warning if a disabled plugin also appears elsewhere in the plugins list with conflicting settings — disable is authoritative.
max_body_bytesinteger (optional)Per-entity override for RouteSpec.max_body_bytes. When set, the dispatcher uses this value instead of the plugin's declared cap — operator tightens (or relaxes) the plugin's spec without a plugin rebuild. None = use the plugin's declared value.
requires_identityboolean (optional)Per-entity override for RouteSpec.requires_identity. When set, the dispatcher enforces this instead of the plugin's declared value. Typical use: operator tightens an endpoint the plugin declared anonymous. None = use the plugin's declared value.

PluginObservabilityToggle

Per-plugin observability toggle. Each signal is independent — operators can disable metrics for one plugin while leaving its logs and traces flowing. Events still route through the GLOBAL observability.{logs,metrics,traces}.sinks list when admitted; this struct only controls whether a plugin's events make it that far + at what level.

FieldTypeDefaultSummary
logsSignalToggle (optional)Logs toggle. None = inherit globals.
metricsSignalToggle (optional)Metrics toggle. None = inherit globals. Note: metrics has no level (metrics-rs has no levels) — the field is accepted in YAML for forward compat but ignored today.
tracesSignalToggle (optional)Traces toggle. None = inherit globals.

PluginRegistryAuthConfig

Registry authentication configuration. At most one source of credentials is consulted at push/pull time: an explicit username+password pair (or env-interpolated variants), otherwise the docker config.json at docker_config_path, otherwise anonymous.

FieldTypeDefaultSummary
docker_config_pathstring (optional)Path to a docker config.json for credential helpers. Defaults to ~/.docker/config.json when unset.
passwordstring (optional)Literal password / bearer token (or $VAR / env:VAR). Wrapped in [mcpg_sensitive::Sensitive] so a stray ?config log renders this field as *** instead of the literal token.
usernamestring (optional)Literal username (or $VAR / env:VAR for env-var interpolation).

PluginRegistryConfig

Configuration for resolving plugin artifacts from OCI registries. Covers default registry, local cache, auth, TLS, and signature policy.

This section is only consulted when at least one plugin entry has source.oci set. For purely local deployments (every plugin loaded from source.path), all defaults apply and the registry subsystem does nothing.

FieldTypeDefaultSummary
authPluginRegistryAuthConfig(see type)Registry authentication strategy.
cache_dirstring (optional)Cache root for plugin artefacts: pulled OCI blobs land under <dir>/oci (keyed by manifest digest, so digest-pinned references skip the network on subsequent boots) and packaged zips unpack under <dir>/unpack. When unset, blobs default to $XDG_CACHE_HOME/mcpg/plugins/oci (or /var/cache/mcpg/plugins/oci for system deployments) and the unpack cache to the process temp dir. On a pod with a read-only root filesystem SET THIS to a writable mount — otherwise the first OCI pull fails creating the default under $HOME, which crash-loops a fresh pod while a hot-reload on a running one fails closed and keeps serving the prior config.
default_registrystring"ghcr.io/mcpg-dev/plugins"Default registry when an oci: reference has no registry prefix. Example: ghcr.io/mcpg-dev/plugins.
default_signature_policySignaturePolicy"enforce"Default signature verification policy applied to every plugins[*] entry that doesn't carry its own signature.policy: override. Defaults to Warn (log but don't fail) for first-rollout safety; flip to Enforce once trusted keys are wired up across all entries.
insecure_registriesarray<string>[]Hostnames (optionally host:port) that the OCI client should reach over plain HTTP instead of HTTPS. localhost, 127.0.0.1, and ::1 are always implicit — operators only need to list this for other dev / air-gap registries.
mirrorsarray<PluginRegistryMirrorConfig>[]Mirror registries tried in order before the reference's source registry. Supports air-gap / pull-through caches.
require_integrity_anchorbooleanfalseWhen set, every oci:-sourced plugin entry must carry an integrity anchor the gateway can enforce independently of the transport: a digest-pinned reference (…@sha256:<hex>), a signature.sha256 artifact-hash pin, or signature.trusted_keys. An entry pulled by bare tag with no anchor is refused at boot. Recommended whenever mirrors or insecure_registries are configured, since a tag pulled over a mirror / plain-HTTP hop is otherwise trusted on the registry's word alone. Defaults to false (every entry accepted; configured anchors are still enforced downstream).
revocation_list_pathstring (optional)Optional path to a JSON revocation list. When set, the gateway loads the file at startup, indexes the revoked artefact SHA-256s, and refuses to load any plugin whose hash matches an entry — even if its Ed25519 signature is valid. Format documented in [mcpg_plugin_host::revocation::RevocationListFile]. Absent means "no revocation list" — every signed plugin is allowed.
tlsPluginRegistryTlsConfig(see type)TLS knobs for registry connections.
trusted_keysarray<TrustedKeyConfig>Gateway-wide Ed25519 trust anchors. An entry whose signature.trusted_keys is empty verifies against these (plus the built-in official mcpg release key); an entry that carries its own keys verifies against exactly those, so third-party vendors never pool into the global anchor set.

PluginRegistryMirrorConfig

A mirror registry entry. Mirrors are consulted in order before the reference's source registry, matching the common pull-through cache / air-gap deployment pattern.

FieldTypeDefaultSummary
authPluginRegistryAuthConfig (optional)Optional auth override for this mirror. When absent, inherits the top-level plugin_registry.auth.
urlstringMirror URL — a prefix that replaces the source registry in resolved pull URLs. Example: harbor.internal.corp/mcpg-plugins.

PluginRegistryTlsConfig

TLS configuration for registry HTTPS connections.

FieldTypeDefaultSummary
ca_certstring (optional)Path to a PEM bundle with extra trusted root CAs. Useful for internal registries with private CAs.
insecurebooleanfalseSkip all TLS certificate verification. DANGEROUS — development-only escape hatch, emits a WARN at boot.

PluginResourceLimitsConfig

Resource limits for Wasm plugins.

These limits constrain the sandbox resources available to a Wasm plugin. If not specified, system defaults are used (64 MiB memory, 10M fuel, 100ms timeout).

FieldTypeDefaultSummary
fuelinteger (optional)Maximum fuel (instruction budget) per invocation (default: 10_000_000).
memory_mbinteger (optional)Maximum linear memory in megabytes (default: 64).
timeout_msinteger (optional)Wall-clock timeout per invocation in milliseconds (default: 100).

PluginSourceConfig

Plugin artifact source configuration.

Exactly one of path / oci must be set. Both unset is invalid; both set is invalid. The source type determines how the gateway resolves the artifact at boot time.

FieldTypeDefaultSummary
ocistring (optional)OCI reference (e.g. ghcr.io/mcpg-dev/plugins/audit:protocol-1 or plugins/audit@sha256:…). At boot the gateway pulls the artifact, verifies the manifest digest, caches it to gateway.plugin_registry.cache_dir, and loads it through the same sidecar / packaged-zip path path would have taken. When the reference is missing a registry prefix, the plugin_registry.default_registry value is prepended — which is itself repointable per deployment via the MCPG_DEFAULT_PLUGIN_REGISTRY environment variable.
pathstring (optional)Path to the plugin artifact on the local filesystem. Accepts a raw .so / .wasm (with a sidecar plugin.yaml) or a packaged .zip.

PolicyCacheConfig

Configuration for the policy decision cache (L1 process-local).

FieldTypeDefaultSummary
enabledbooleanfalse
max_entriesinteger10000
ttl_msinteger60000

PolicyConfig

FieldTypeDefaultSummary
cachePolicyCacheConfig(see type)
enginearray<KindRef>Ordered chain of policy engines consulted at every decision point (tool.call.pre, plugin.lifecycle.register, etc.). Each entry is a [KindRef] — kind: resolves to a built-in keyword (yaml-rules), a short alias (cedar, opa, casbin → dev.mcpg.policy.<alias>), or a full reverse-domain plugin id. Chain semantics: the host walks the list in order, short-circuiting on the first Allow / Deny; NotApplicable falls through to the next engine. An empty chain is equivalent to NotApplicable everywhere — callers (e.g. enforce_plugin_registration_policy) decide whether that means "allow" (default-allow gateway) or "fail-closed" per their own policy posture.
tool_accessToolAccessPolicyConfig(see type)

PoppyDiscoveryConfig

The poppy.json document the gateway serves.

FieldTypeDefaultSummary
documentanyThe document, served as it is with Cache-Control: max-age=300. It needs protocol_version 0.x, organization.name and organization.domain, and https URLs for auth.issuer and every apis[].url. PAP binds the document to the host that serves it, so publish it on the Company's own domain only.

PostgresUriMatch

Maps a postgres_listen_notify payload to the URIs it changes.

FieldTypeDefaultSummary
payload_fieldstringA top-level field of the JSON payload (string or number).
urisarray<string>URI patterns; {value} is the payload field's value. A NOTIFY changes a watched URI equal to one of them.

PromptArgumentConfig

FieldTypeDefaultSummary
completionsarray<string> (optional)
descriptionstring (optional)
namestring
requiredbooleanfalse

QuotasConfig

governance.quotas: registry.

Three named-policy lists (rate_limits / budgets / concurrency) plus the storage backend that holds the runtime counters. Bindings opt into specific policies by id via their own per-binding quotas: block.

FieldTypeDefaultSummary
budgetsarray<BudgetPolicy>Named cost / call-count / token-count budgets. Bindings reference by id.
concurrencyarray<ConcurrencyPolicy>Named concurrency caps. Bindings reference by id.
on_errorstring"deny"Posture when the quota gate itself errors (e.g. a quota-store read/write failure): deny (default, fail-closed — refuse the call so a storage outage cannot silently disable rate-limit / budget / concurrency enforcement) or allow (fail-open — proceed without a permit). An empty value is treated as deny.
rate_limitsarray<RateLimitPolicy>Named rate-limit policies. Bindings reference by id.
storeKindRef(see type)Storage backend for quota counters / token-buckets / in-flight concurrency. Uses the standard KindRef discriminator. kind: cluster (default) routes through the cluster coordinator's KV role; kind: memory resets on restart (dev-only); kind: <plugin-id> pins to a loaded KV plugin.

RateLimitPolicy

One named rate-limit policy.

FieldTypeDefaultSummary
burstinteger (optional)Bucket burst capacity — number of calls a caller can spend in quick succession before refill rate kicks in. Defaults to the per-second equivalent of rate (i.e., one second's worth).
idstringOperator-chosen id. Bindings reference this via tools[].quotas.rate_limit: <id>.
identity_claimstring (optional)JWT claim path used to key the bucket when scope: per_identity. Required for that scope; rejected for others. Common values: sub, org_id, email.
kindstring"token_bucket"Algorithm. v1.0 ships token_bucket only; leaky_bucket and sliding_window are not implemented.
on_exceededstring"deny"Action when the bucket runs dry. deny returns a 429-style error; queue is reserved and currently aliases to deny for rate limits; shed_load drops silently.
rateRateLimitRateRefill rate. Today only calls_per_minute is supported; future variants will land here.
scopestring"per_identity"Scope discriminator: how the bucket is keyed. per_identity keys by the JWT claim path in identity_claim; global shares one bucket across all callers; per_session, per_tool are also valid.

RateLimitRate

FieldTypeDefaultSummary
calls_per_minuteintegerCalls allowed per minute. Bucket refills at this rate.

RealtimeAdmissionConfig

mcp.realtime.admission.

The gateway calls tool as the caller, through the whole tool-call path (policy, gates, the backend), with these arguments:

  • op: admit (a caller opens realtime delivery), renew (it still holds it) or release (it let go; best effort). - lease_id: one id per held delivery, the same in all three calls and on every replica. - kind: events or resource. - delivery: stream, poll or webhook for events; listen or subscribe for a resource. - event: {name, arguments} for events; resource: {uri} for a resource. - client: {name, version} of the MCP client, when it named itself.

The tool answers with an object that has a boolean allow. With allow: true, renew_after_seconds (or renewAfterSeconds) may shorten the renew interval. With allow: false, error: {code, message, data} reaches the caller: a numeric code is the JSON-RPC error code and data (or details) its data; any other code gives -32013 with the whole error object as data.

The tool is internal: tools/list does not show it and a caller's own tools/call of it is refused.

FieldTypeDefaultSummary
on_errorAdmissionOnError"deny"What a call that fails or answers no decision means: deny (default) refuses the caller, allow admits it.
renew_interval_msinteger60000How often a held stream or subscription is renewed. A poll lease and a webhook subscription are renewed when the caller polls or refreshes, at most this often. Set the lease lifetime on the tool's side to at least twice this.
toolstringThe tool binding to call.

RealtimeConfig

mcp.realtime.

FieldTypeDefaultSummary
admissionRealtimeAdmissionConfig (optional)Ask a tool of this gateway before a caller gets realtime delivery, while it holds it, and when it lets go.

RedirectUriPolicy

How the https:// redirect URIs of a Client ID Metadata Document are admitted.

Variants:

  • same_host — On exactly the host of the document's client_id URL.

  • allowed_hosts — On any host allowed_hosts admits.

RefreshTokensConfig

Refresh tokens (interactive.refresh_tokens). Every refresh rotates the token; presenting a spent one revokes the whole grant.

FieldTypeDefaultSummary
absolute_ttl_secsinteger2592000A grant expires this long after sign-in however it is used, in seconds (idle_ttl_secs–7776000; default 30 days).
enabledbooleantrueIssue refresh tokens to clients whose grant types include refresh_token.
idle_ttl_secsinteger1209600A grant unused for this long expires, in seconds (3600–7776000; default 14 days).
idp_unavailable_grace_secsinteger3600How long refreshes keep working while the IdP cannot be reached, in seconds past the last successful check (0–86400).
max_grants_per_principalinteger50Grants one user may hold at once (1–1000); a new grant beyond it revokes the user's oldest.
reuse_grace_secsinteger0How long a spent refresh token may be presented again and receive the same successor, in seconds (0–60). 0 treats any second use as theft and revokes the grant.
revalidate_interval_secsinteger (optional)How often a refresh checks the IdP sign-in, in seconds (60–86400). Defaults to access_token_ttl_secs.
revalidate_with_idpbooleantrueCheck the user's IdP sign-in on refresh: the stored IdP refresh token is redeemed at the IdP at most every revalidate_interval_secs, and a user the IdP no longer accepts loses every grant. While on, a sign-in without an IdP refresh token gets no gateway refresh token.

RegistryAuthConfig

Consumer auth presented to the registry API.

FieldTypeDefaultSummary
credentialstring (optional)Credential-issuer reference for cred: a standard cred://<plugin_id>/<target> URI. The issuer mints + refreshes the registry bearer under the gateway's machine identity; no static token lives in the config.
headersmap<string, string>Arbitrary request headers for headers (e.g. X-API-Key); values support ${env.X}.
modeRegistryAuthMode"none"
tokenstring (optional)Bearer token for bearer (supports ${env.X}).

RegistryAuthMode

Variants:

  • none — Anonymous reads (the generic spec's default).

  • bearer — Authorization: Bearer <token>.

  • headers — Arbitrary static headers (API-key style sub-registries).

  • cred — Bearer minted by a credential-issuer plugin (credential is a cred:// URI) under the gateway's machine identity.

RegistryDefaultsConfig

Defaults applied to every synthesized federation.

FieldTypeDefaultSummary
authAuthConfig(see type)Upstream auth for synthesized federations (same modes as a hand-written federation; per-server servers.<name>.auth overrides win).
cacheFederationCacheConfig(see type)Capability-cache TTL refresh.
governanceBackendGovernanceConfig(see type)Governance inherited by every synthesized federation (trust floor + CEL), exactly like a hand-written federation's block.
importImportConfig(see type)Surfaces to import. Defaults to everything — the point of auto-federation is the full surface; narrow it here if not.
oauth_discoveryRegistryOauthDiscoveryConfig(see type)Sync-time OAuth discovery (RFC 9728 protected-resource metadata → RFC 8414 AS metadata) for synthesized federations whose auth uses an OAuth credential mode: derives each server's audience + token endpoint and injects them as the issuer's per-call config (auth.credential_config). Off by default.
synthesizeSynthesizeConfig(see type)Change-notification synthesis for push-less servers.
upstream_safetyRegistryUpstreamSafetyConfig(see type)Per-server upstream network posture. Only allow_private_backends is honored — stdio and insecure HTTP stay denied for registry-driven servers regardless.

RegistryFilterConfig

Which registry servers are eligible.

FieldTypeDefaultSummary
excludearray<string>[]Server-name globs to exclude (exclude wins).
includearray<string>(see type)Server-name globs to include (exact or trailing-*).
namespacesarray<string>[]Publisher-namespace allowlist (the part before /, e.g. com.acme). Empty = all namespaces. The anti-typosquatting rail: registry namespace ownership is publisher-verified, so pinning namespaces pins trust.

RegistryOauthDiscoveryConfig

Sync-time OAuth discovery for registry servers.

FieldTypeDefaultSummary
enabledbooleanfalseFetch each OAuth-mode server's RFC 9728 + RFC 8414 metadata at sync time. Servers whose discovery fails (and that have no prior discovered snapshot) are skipped — a server that cannot be authenticated against would only fail at dispatch.

RegistrySafetyConfig

Network posture for the registry endpoint.

FieldTypeDefaultSummary
allow_insecure_httpbooleanfalsePermit http:// (non-TLS) registry endpoints.
allow_private_registrybooleanfalsePermit a private / loopback registry address (normal for enterprise sub-registries; still an explicit opt-in).

RegistryServerOverride

Per-server override, keyed by registry server name.

FieldTypeDefaultSummary
authAuthConfig (optional)Upstream auth override for this server.
enabledbooleantrueSet false to exclude this server regardless of filters.
headersmap<string, string>Values for the remote's declared request headers (secrets via ${env.X}). Required-header declarations without a value here (or a registry-provided default) skip the server.
variablesmap<string, string>Values for {variable} templates in the server's remote URL.
versionstring (optional)Pin an exact registry version (default: track the registry's latest).

RegistrySyncConfig

Sync cadence + bounds.

FieldTypeDefaultSummary
full_resync_hoursinteger24Hours between full crawls when incremental is on. Floor 1.
incrementalbooleanfalseCrawl with updated_since=<watermark> between periodic full crawls instead of listing everything each tick. Status flips (including deletions) bump updatedAt, so deltas carry tombstones too; the periodic full crawl is the backstop for anything missed. Engages only once the registry has yielded updatedAt timestamps.
interval_secsinteger300Seconds between crawls. Each crawl lists the registry's latest server versions in full (cursor-paginated), so deletions are observed without a separate backstop. Floor 30.
max_serversinteger100Hard cap on federated servers from this registry. Servers beyond the cap (name-sorted) are skipped and reported — back-pressure against unbounded task/connection growth.

RegistryUpstreamSafetyConfig

Upstream network posture defaults for synthesized federations. Deliberately narrower than a hand-written federation's upstream_safety: the registry chooses which servers exist, so the dangerous knobs are not registry-reachable.

FieldTypeDefaultSummary
allow_private_backendsbooleanfalsePermit private / loopback server addresses (internal remotes — the common enterprise case; still an explicit opt-in).

RequestStateConfig

request_state: config — the MRTR requestState codec used by the modern wire's suspending tools/call arm to encrypt pipeline-resumption blobs.

Inert until a modern client connects. Lives under mcp.configurations because the codec manages runtime-emergent resumption state alongside sessions / pipelines / subscriptions / delivery / cancellation / idempotency — operator tunes the encryption key, the runtime mints + serves the rest.

yaml mcp: configurations: request_state: # 32-byte ChaCha20-Poly1305 key, base64-encoded. # Generate via: head -c 32 /dev/urandom | base64 encryption_key: "<base64-32-byte-secret>"

Absent the key the gateway mints an ephemeral one at boot (with a WARN log) — pending resumptions issued before a gateway restart become undecodable after restart.

FieldTypeDefaultSummary
encryption_keystring (optional)Base64-encoded 32-byte ChaCha20-Poly1305 secret. None ⇒ ephemeral key (random per process; resumptions lost on restart).
strict_encryptionbooleanfalseFail-closed guard for clustered modern resume. When true and the deployment is clustered (cluster.kind != single_node), the gateway REFUSES to boot if the requestState codec would fall back to an ephemeral per-process key — i.e. neither encryption_key nor a derivable cluster.state_encryption_key_env is available. An ephemeral key is undecodable on a peer, so a clustered modern (≤8 KiB inline) resume on another replica silently fails; this turns that silent fail-open into a loud boot error. Default false to keep existing clustered (e.g. legacy-only) deployments booting unchanged.

ResourceWatchConfig

Per-binding resource watch configuration. Defines how changes to a resource are detected for notifications/resources/updated.

FieldTypeDefaultSummary
notification_filterNotificationFilterConfig (optional)Notification filter — controls which subscribers receive the notifications/resources/updated message when a change is detected. Defaults to fan-out to all subscribers when absent.
strategyWatchStrategyConfig(see type)Strategy for detecting resource changes.

ResponseCacheConfig

Operator-facing config for the gateway-managed LLM response cache. The cache backs BackendHost::cache_get / cache_put; chat + embedding bindings opt in per-binding via their own cache.enabled: true knob, the cache only exists at all if this config is non-disabled.

kind: in_process is the default — content-addressed BLAKE3 LRU cache with 64 MiB byte cap, lost on restart. kind: disabled turns the cache off gateway-wide; per-binding cache.enabled becomes a no-op.

Variants:

  • (unnamed variant)

    • kind: string
    • max_bytes: integer
  • (unnamed variant)

ResponseConfig

Per-call response limits.

FieldTypeDefaultSummary
max_response_bytesinteger2097152Cap on a single upstream call's response, enforced gateway-side.

RetryConfig

Per-binding retry configuration. Only applies to HTTP, gRPC, GraphQL, NATS, and Kafka bindings.

FieldTypeDefaultSummary
initial_backoff_msinteger200Initial backoff delay in milliseconds. Doubled on each subsequent retry.
max_attemptsinteger3Maximum number of retry attempts (not counting the initial attempt).
retry_on_status_codesarray<integer>(see type)HTTP status codes that trigger a retry (only applicable to HTTP/gRPC/GraphQL bindings).
retry_on_transport_errorbooleantrueWhether to retry on transport/connection errors.

SchemaEntry

A named schema entry in the registry. Exactly one source must be provided.

FieldTypeDefaultSummary
filestring (optional)Path to a local JSON Schema file (relative to the config file).
inlineanyInline JSON Schema definition.
urlstring (optional)URL to fetch the JSON Schema from at startup.

SecretFormatConfig

secret: — how a configured secret becomes the HMAC key.

FieldTypeDefaultSummary
encodingTextEncoding"utf8"How the rest of the secret encodes the key. Default utf8.
max_bytesinteger (optional)Longest key, in decoded bytes; a longer secret is refused at boot. Unset, no limit.
min_bytesinteger (optional)Shortest key, in decoded bytes; a shorter secret is refused at boot. At least 1. Unset, any non-empty key.
prefixstring (optional)Text every secret starts with, removed before decoding; a secret without it is refused at boot.

SecretsConfig

gateway.secrets: — directory-backed ${secret.NAME} values, hot-reloaded on change. Every ${secret.NAME} in the config resolves to the exact bytes of <dir>/<NAME> at config load. With dir unset, any ${secret.*} reference is a config error.

FieldTypeDefaultSummary
dirstring (optional)Directory holding one file per secret; ${secret.NAME} reads <dir>/<NAME> (symlinks followed, so a mounted Kubernetes Secret volume works as-is). NAME must match ^[A-Za-z_][A-Za-z0-9_]{0,63}$. Unset: the config may not reference any ${secret.*}.
poll_interval_msinteger5000Poll interval in milliseconds for watch. Values below 1000 are clamped to 1000 at spawn time (a warning is logged at validate time) — sub-second polling burns I/O for no operator-visible benefit.
watchbooleantruePoll dir and hot-reload the gateway when any file in it changes (added, removed, or rewritten). The reload re-resolves every ${secret.*} reference, so a rotated value is live within one poll interval — no restart, no config publish. Only meaningful when dir is set.

ServedRegistryConfig

mcp.registry: — serve a v0.1 MCP-Registry view of this gateway. The catalog exposed is exactly one server entry: the gateway itself (its whole governed surface hangs off one MCP endpoint), so pointing a registry-driven client policy here yields "the approved server is MCPG".

FieldTypeDefaultSummary
descriptionstring (optional)Human description shown by registry clients.
enabledbooleanfalseServe GET /v0.1/servers (+ per-version fetches). Default off.
namestring""Published server name, reverse-DNS namespaced (com.acme/gateway). Required when enabled.
urlstring (optional)Canonical external MCP endpoint published in the entry's remotes[]. Defaults to governance.access.resource_metadata.resource when unset.

ServerConfig

FieldTypeDefaultSummary
aauth_resource_metadataAauthResourceMetadataConfig (optional)AAuth resource metadata. When set, the gateway serves the document at /.well-known/aauth-resource.json and attaches the AAuth-Requirement: requirement=agent-token challenge (plus Accept-Signature-Scheme / Accept-Signature-Alg) to authentication-required 401 responses, so AAuth-capable agents can discover that signing with an agent token would succeed. Pair with the dev.mcpg.identity.aauth identity plugin, which does the verifying.
access_logbooleantrueEmit the per-request access log (request received / request completed INFO events, one pair per request). Default true (the gateway logs every request's lifecycle). Set false to suppress the access log on latency/throughput-sensitive deployments: it removes two structured-log events — and their field formatting + sink write — from every request. Audit events, error/warn logs, metrics, and traces are unaffected. Leave true unless request-level access logging is provided elsewhere (an ingress/sidecar) or not required.
allow_private_backendsbooleanfalseAllow outbound connections to private/loopback/link-local IPs. Default false enables the DNS rebinding guard. Set true for container-network deployments where backends live on RFC 1918.
allowed_originsarray<string>[]
anonymous_rate_limit_burstinteger100Burst allowance for anonymous_rate_limit_per_min.
anonymous_rate_limit_per_mininteger600Per-IP request-rate cap on the MCP endpoint for requests below cryptographically-verified trust — i.e. anonymous AND header-asserted identities (sustained requests/minute, with anonymous_rate_limit_burst headroom). A self-asserted x-mcpg-subject-id does NOT buy an exemption. Only Verified traffic (a real OIDC/JWKS/identity-plugin credential) skips this — it is attributable and metered per tenant. Defaults generous (600/min = 10 rps sustained per client IP), far above interactive agent use; 0 disables (e.g. when an upstream WAF throttles, or for single-IP load testing). Each replica counts its own requests, with no store call on unauthenticated traffic: behind a balancer that spreads one client over N replicas the client gets up to N x this rate, behind one that pins a client IP to a replica, this rate.
bind_addressstring"127.0.0.1:8787"
browser_redirect_urlstring (optional)Browser hand-off for the MCP endpoint. When set, a plain browser NAVIGATION to the MCP path — method GET or HEAD, Accept includes text/html and does not include text/event-stream — is answered with 303 See Other to exactly this URL, before identity or any other processing. No conformant MCP client matches: JSON-RPC rides POST, and SSE GETs send text/event-stream, so the redirect only claims requests that today dead-end in a 405/406. The value is served literally — request data never enters the Location header. Typical target: a hosted inspector pre-filled with this gateway's URL. Must be http/https.
canonical_urlstring (optional)The one URL this gateway is addressed by. When set, a request arriving on any OTHER host is answered 308 Permanent Redirect to the same path and query under this URL's origin — the MCP endpoint and the OAuth metadata alike — so the deployment advertises exactly one resource identity no matter how many names resolve to it.
completion_rate_limit_per_secinteger (optional)Per-session rate limit on completion/complete requests (cap per second). None disables. Guards against broken autocomplete UIs. Counted by the replica that serves the request.
corsCorsConfig (optional)Cross-origin access for browser clients. Unset (the default) means the gateway answers no preflight, so a browser cannot call it directly at all — allowed_origins alone is the DNS-rebinding guard, which checks the Origin of requests that arrive rather than granting a browser permission to send them. A page must then reach the gateway through a same-origin proxy.
enforce_modern_request_metabooleanfalseEnforce the SEP-2575 per-request _meta identity triple (io.modelcontextprotocol/{protocolVersion, clientInfo, clientCapabilities}) on EVERY id-bearing modern (2026-07-28) request, not just server/discover. When false (the default), only server/discover requires the triple and other modern methods may carry minimal _meta. Has no effect on the 2025-11-25 wire. Opt-in so existing modern clients are unaffected until they adopt the triple.
extra_resource_uri_schemesarray<string>[]Extra resource-URI schemes (beyond the built-in allow-list) treated as first-class by the resource normalizer. Matched case-insensitively.
health_checkHealthCheckConfig(see type)Periodic prober for every binding's backend (SQL server reachability, gRPC endpoint, REST upstream, ...). Distinct from health_path: above — that's the gateway's own liveness endpoint for load balancers; this prober actively pings each binding's underlying service and updates PluginState::{Active, Degraded} based on results.
health_pathstring"/health"
legacy_wirebooleantrueServe the session wire of the 2025-xx revisions: initialize, Mcp-Session-Id, and the GET stream and DELETE on the MCP path. Default true. With false the gateway serves only the stateless 2026-07-28 revision, over HTTP: a POST that negotiates a 2025-xx revision (by header, by initialize, or by sending neither header nor _meta version) is refused with HTTP 400 and JSON-RPC -32022 (data.supported is ["2026-07-28"]), a POST that carries Mcp-Session-Id is refused with HTTP 400 and -32600, GET and DELETE answer 405, and server/discover lists only 2026-07-28. The stdio transport and messages-wire entries of transports speak 2025-11-25, so they refuse to start with false; http-wire entries go through the HTTP router and follow this setting.
max_request_body_mbinteger4Maximum POST body accepted on the MCP endpoint, in MiB. Defaults to 4 MiB. 0 falls back to the default — an unbounded body is never acceptable on a public endpoint.
max_sessions_per_tenantinteger0Per-tenant session quota. 0 = unlimited. The stricter of this and the global cap wins.
mcp_pathstring"/mcp"
relax_request_id_uniquenessbooleanfalseRelax the per-session JSON-RPC request-id uniqueness rule. When false (default) a client-supplied id that has already been used on the same MCP session is rejected with -32600 (JSON-RPC forbids id reuse). Set true only for load generators that replay a fixed request body (e.g. the fortio proxy-overhead benchmark in tools/bench/fortio/), where every request carries the same id. Never enable in production — it removes a duplicate-delivery / replay guard.
replay_window_limitinteger16
request_timeout_msinteger30000
revalidate_mutated_tool_argumentsbooleanfalseRe-validate tool arguments against the tool's inputSchema after a tool_gate / transform plugin rewrites them. When false (default) only the caller's original arguments are validated. Opt-in defense-in-depth — plugins are operator-signed, so a rewrite that diverges from the published schema is normally trusted.
scrub_process_env_after_bootbooleanfalseAfter boot (plugins loaded, config-origin env:// / ${env.X} secrets already captured by the env secret provider's snapshot), remove those referenced env vars from the live process environment so a loaded cdylib can no longer read them via std::env::var / shared-process env. Opt-in defense-in-depth, default off. NOTE: this is NOT a hard boundary — it does not clear /proc/self/environ (the exec-time copy), so a hostile in-process plugin can still recover the original values there; it raises the bar against accidental/casual exposure. Enable only once every plugin resolves its secrets via the host (cred:// / env://) rather than reading env directly.
server_ping_interval_msinteger (optional)Emit a server-initiated ping to each active session's SSE stream on this cadence. None or 0 disables. Reasonable value: 30s.
session_idle_timeout_msinteger900000How long a session of the 2025-xx revisions lives without use, in milliseconds. A session ends when it was not used for this long; its open streams end with it, and the client's next request gets 404.
shutdown_timeout_msinteger30000
tlsTlsConfig (optional)
transportTransportMode"http"
transportsarray<KindRef>Additional plugin-supplied transports started at boot alongside the primary HTTP / stdio listener (which continues to be governed by transport: and bind_address:). Each entry is a [KindRef] — kind: resolves to either a built-in transport keyword (today only dev.mcpg.builtin.transport.memory is wired; builtin-http / builtin-stdio map to the in-tree HTTP / stdio paths and don't need a list entry) or a registered Transport plugin id. Each entry starts once, with its config; transports that fail to start halt the boot. Empty list = no extra transports beyond the primary listener — today's default.
trust_proxy_ipbooleanfalseTrust the headers a fronting proxy sets: the first X-Forwarded-For hop is the client IP of the anonymous rate limit and of the embedded authorization server's per-IP budgets (/oauth/token, /oauth/revoke, /oauth/register and the sign-in pages), and the first X-Forwarded-Host is the host that selects the protected resource identifier in the resource metadata and in the WWW-Authenticate challenge. Set ONLY when a trusted reverse proxy / edge fronts this gateway (the managed-cloud Envoy edge does) — the headers are spoofable otherwise. When false (default) the TCP peer address and the Host header are used.
trust_subject_headerbooleanfalseTrust the x-mcpg-subject-id request header as a header-asserted identity. The header carries no proof of who the caller is, so when false (default) it is IGNORED and such requests resolve to Anonymous — only a verified credential (OIDC/JWKS/identity plugin) yields a non-anonymous principal. Set true ONLY behind a trusted upstream that authenticates the caller and injects this header.
tunnelTunnelConfig (optional)Reverse-tunnel egress: dial out to an MCPG-Cloud relay and serve this gateway's MCP surface through the tunnel. mcpg --tunnel populates this. Absent / enabled: false = no tunnel.
tunnel_federationTunnelFederationConfig (optional)Reverse-federation ingress: how this gateway reaches same-org tunnel://<name> federation upstreams through the relay's federation ingress. Independent of tunnel (egress) — a gateway can federate other gateways' tunnels without dialing one of its own.
unary_json_fast_pathbooleanfalseOn the legacy (2025-11-25) wire, answer a unary request whose result is immediately available and that emitted NO server→client notifications (log / progress) with a single application/json response instead of a one-frame text/event-stream reply. This is spec-permitted (Streamable HTTP lets the server pick JSON or SSE) and mirrors what the modern (2026-07-28) wire already does; it skips the per-request SSE stream bookkeeping (replay-window append, priming + logging frames, session snapshot) that otherwise runs under the session lock, which materially raises tool-call throughput. Default false (unchanged SSE behaviour). A request that DOES emit notifications, or suspends (MRTR), still streams regardless of this flag.

SessionConfig

Upstream-session behaviour.

FieldTypeDefaultSummary
idle_timeout_secsinteger600

SessionsConfig

sessions: config — session lifecycle store + per-capability store: override. When store is unset, the session KV inherits from the cluster's key_value_store() primitive; when set, the override pins to an in-process backend (memory / file).

FieldTypeDefaultSummary
bind_session_ownerbooleanfalseBind each session to the principal that created it. When true, session-scoped operations (GET→SSE stream, DELETE→terminate, subscriptions, POST→SSE continuation) require the caller's resolved principal to match the session's creator; a mismatch is refused as if the session did not exist (no existence leak). Default false (today's possession-only behaviour). An anonymous session (no creating principal) can only be driven anonymously.
optionalbooleanfalseMake sessions optional on the legacy (2025-11-25) wire. When false (default), a legacy request without an Mcp-Session-Id header is rejected (-32600, HTTP 400) — a legacy client MUST initialize first. When true, such a request is instead served through an ephemeral, row-less session (the same lane the modern wire uses for anonymous stateless calls): the gateway does not issue a session, so it does not demand one. Spec-permitted (a server chooses whether to issue sessions), and lets fixed-tool-set proxy deployments skip the handshake round-trip. Features that inherently need a durable session — SSE resume cursors, server-initiated requests, cross-request task/subscription continuity — still require a real session; a session-less request that would need one gets a clear error. initialize continues to mint real sessions regardless.
storeStoreOverrideConfig (optional)
synthetic_session_keystring (optional)Base64-encoded 32-byte secret used to derive the per-principal synthetic session id minted for modern (2026-07-28) stateless requests that arrive without a session header.

SignalToggle

Per-plugin per-signal toggle. Four knobs:

  • enabled (default true): when false, every event from this plugin's crate is dropped at the bridge before it reaches the sink fan-out — the "silence this noisy plugin" pattern. - level (logs / traces only): minimum severity an event must clear to be emitted. Composed into the bridge layer's permissive filter so per-plugin verbosity boosts AND suppressions both work. Accepted: trace / debug / info / warn / error (case-insensitive). - mode (default inherit): how to route events that pass the gate. inherit flows through the global sink list (the default behaviour). replace routes ONLY to the plugins listed under sinks — used for compliance carve-outs ("audit logs go to my SIEM, never to stdout"). tee fans out to BOTH the global sink list AND the per-plugin sinks. - sinks: plugin ids of the sink plugins to use under mode: replace | tee. Each id MUST match a registered sink plugin for the corresponding signal — log sink for logs.sinks, metrics sink for metrics.sinks, span sink for traces.sinks. If any id is unknown to the matching signal, the gateway refuses to boot (validated post-registration in app::validate_per_plugin_sink_ids). Listing a real log sink id under metrics.sinks is rejected — sink-kind crossover is a typo, not a feature.
FieldTypeDefaultSummary
enabledbooleantrue
levelstring (optional)
modeSinkMode
sinksarray<string>

SignatureConfig

Per-plugin signature configuration.

Consolidates three signature concerns into one per-plugin block: - The content-hash pin. - The verification policy — per-plugin overridable, with the global default in gateway.plugin_registry.default_signature_policy:. - The Ed25519 trusted keys this artifact must verify against — per-plugin so plugins from different vendors can carry different keys without pooling them in one trust anchor.

FieldTypeDefaultSummary
policySignaturePolicy (optional)Verification policy for this plugin. None = inherit gateway.plugin_registry.default_signature_policy:.
sha256string (optional)SHA-256 content hash to pin (hex-encoded). When set, the gateway refuses to load the artifact if its computed hash doesn't match.
trusted_keysarray<TrustedKeyConfig>Ed25519 verification keys this artifact's signature must verify against. Empty = inherit gateway-wide defaults.

SignatureFormatConfig

signature: — where a request carries its signatures.

FieldTypeDefaultSummary
encodingTextEncoding"hex"How a digest is written: hex (default) or base64.
headerstringThe header with the signatures. It must appear once.
keystring (optional)Items are key=value pairs, and the signatures are the values of this key. Needs separator.
prefixstring""Text before each digest, removed before decoding. An item without it is skipped.
separatorstring (optional)The header holds several items split by this text. Omitted, the whole value is one item.

SignaturePolicy

Signature verification policy for native plugin artefacts. The Ed25519 signature attached to the artefact (<artifact>.sig or the packaged plugin.sig) is the primary check; this policy governs behaviour when the signature is missing or invalid. Set per-plugin via plugins[*].signature.policy:, or as a gateway-wide default via gateway.plugin_registry.default_signature_policy:.

Variants:

  • disabled — Signature checks are skipped entirely. Development only; gateway emits a governance.plugin.signature_policy_disabled audit event for any entry that resolves to this policy so the choice is visible in the compliance trail.

  • warn — Log a warning for missing or invalid signatures but proceed with the load — ONLY while no trusted keys are configured. The built-in official key means an inheriting entry always has keys, so this behaves like enforce unless a config empties the trust set.

  • enforce — Refuse to load any artefact whose signature is missing or does not verify against the configured trusted keys. The default: a stock gateway loads only signed plugins.

SignedTimestampConfig

signed_timestamp: — where the signed timestamp is. Name exactly one of header and key.

FieldTypeDefaultSummary
headerstring (optional)The header that carries it.
keystring (optional)The key=value item of the signature header that carries it.
tolerance_secsinteger300Largest age or skew, in seconds. Default 300.

SigningAlgorithm

JWS algorithm of an access-token signing key.

Allowed values:

  • HS256
  • ES256
  • EdDSA
  • RS256

SigningKeyConfig

One key of authorization_server.signing_keys.

FieldTypeDefaultSummary
algSigningAlgorithmJWS algorithm: HS256 (shared secret), ES256 (P-256), EdDSA (Ed25519) or RS256 (RSA, at least 2048 bits).
kidstring (optional)Key identifier, stamped as the kid header of the tokens this key signs and published with its public key. Defaults to the RFC 7638 thumbprint of an asymmetric key, and to an identifier derived from the secret of an HS256 key (the same one signing_secret uses).
private_keystring (optional)ES256, EdDSA and RS256: the PEM-encoded private key, PKCS#8 (-----BEGIN PRIVATE KEY-----, as openssl genpkey writes it; an RSA key may also be PKCS#1). Supply via ${secret.NAME}, which reads the file verbatim, or ${env.X}.
secretstring (optional)HS256 only: the shared secret, at least 32 bytes. Supply via ${env.X} or ${secret.NAME}.

SinkConfig

One sink in an observability signal's sinks: [...] list. The kind: field dispatches to a built-in factory (stderr, stdout, file, otlp, prometheus) or to a plugin id (any other value is looked up in the plugin registry at boot).

config: is the sink-kind-specific config object. Built-in kinds validate their own config: shape at boot; for plugin sinks, the plugin's own config schema applies.

level: is an optional per-sink severity floor. When None, the sink inherits the signal's level:. Useful for stderr: warn, file: debug setups where the console is quiet but a file captures everything. Per-sink level overrides are parsed but not yet enforced; today signal-level level: is the only enforced floor.

FieldTypeDefaultSummary
configanySink-specific config object. Schema depends on kind:.
kindstringSink kind. Built-in keywords: stderr, stdout, file, otlp, prometheus. Anything else is resolved as a plugin id at boot.
levelstring (optional)Per-sink severity floor. None = inherit signal-level level:.

SinkMode

How to route events for a per-plugin signal toggle. Operator schema: mode: inherit | replace | tee.

Variants:

  • inherit — Inherit the global sink list — the same routing every other plugin's events use. Default.

  • replace — Route admitted events ONLY to the per-plugin sinks list. Skips the global sink fan-out entirely. Used for compliance carve-outs (audit logs stay inside the SIEM).

  • tee — Tee — admitted events flow to BOTH the global sink list AND the per-plugin sinks list. Useful when an operator wants to keep default routing but additionally mirror a noisy plugin's events to a debugging sink.

SkillSourceConfig

One folder of skills under mcp.skills.sources.

FieldTypeDefaultSummary
pathstringThe folder. A relative path resolves against the folder of the last config file. A config that comes from no file (inline, from a URL, pushed by a control plane) must give an absolute path; a relative one is refused at boot. The gateway logs the absolute folder of each source. Files and folders whose names start with . are not served. Symbolic links are followed (a Kubernetes ConfigMap mount is made of them); a broken link or a link loop is refused.
prefixstring (optional)A path the skills of this source are served under, such as acme/billing: its folder refunds/ is then skill://acme/billing/refunds/SKILL.md. Segments are separated by / and use letters, digits, -, ., _ and ~. Unset serves the skills at the root of the skill:// namespace.

SkillsConfig

mcp.skills: — Agent Skills the gateway serves to clients over the MCP Skills extension (io.modelcontextprotocol/skills).

A skill is a folder with a SKILL.md at its root, whose YAML frontmatter holds at least name (equal to the folder's name) and description. Each folder of a source that holds a SKILL.md is one skill, served as skill://<prefix>/<folder path>/SKILL.md together with every other file of that folder. Clients find the skills with skills/list and skills/get, read each file with resources/read, and list a folder with resources/directory/read.

The files are read once, when the gateway starts or reloads its config, and served from memory, so each file's digest always matches its bytes. Reads go through the same access rules as any other resource.

FieldTypeDefaultSummary
directory_readbooleantrueServe resources/directory/read and declare directoryRead: true. Default true.
governanceBackendGovernanceConfigWho may list and read the skills: the same block a binding takes. Applies to every skill file and folder, beside any governance.policy.tool_access rule filed under its URI. Unset minimum_trust takes default_minimum_trust.
list_as_resourcesbooleantrueAlso list each skill's SKILL.md in resources/list, for clients that do not speak the extension. Default true.
sourcesarray<SkillSourceConfig>[]The folders skills are served from. Empty (the default) serves no skills and does not declare the extension.

StateKeyConfig

One sealing key of interactive.state_keys.

FieldTypeDefaultSummary
kidstringKey identifier recorded with every record it seals ([A-Za-z0-9._-], 1–64 characters), unique in the list.
secretstringThe key material: 32 bytes, URL-safe base64 (openssl rand -base64 32 | tr '+/' '-_'). Supply via ${secret.NAME} or ${env.X}.

StorageConfig

Top-level storage: block. Holds the gateway's content-store providers + the LLM response cache.

FieldTypeDefaultSummary
defaultstring (optional)Provider id that bindings without an explicit content_storage: field route to. When unset, the gateway falls back to a provider with the conventional id default. Validated at boot — an unknown id fails fast.
providersarray<StorageProviderConfig>[]Content-store provider entries. Each entry produces an Arc<dyn ContentStore> registered under id in the gateway's runtime registry. Bindings reference providers by id via their own content_storage: field.
response_cacheResponseCacheConfig(see type)Gateway-managed LLM response cache. Lives here (rather than under plugins:) so all "where bytes go to live" config shares one home.

StorageProviderConfig

One operator-declared content-store provider, an entry in storage.providers: [...]. The kind field selects the storage plugin (in_process / file_system / s3 / future plugins); config is the per-plugin configuration object whose schema is owned by the plugin (see each plugin's plugin.yaml).

FieldTypeDefaultSummary
configanyPlugin-specific configuration JSON. Validated by the plugin at build_profile time; gateway boot fails fast if the shape doesn't match.
idstringOperator-chosen id. Bindings reference providers via this id (content_storage: <id> on the binding entry). The conventional default id is the fallback when a binding doesn't specify its own AND storage.default is unset.
kindstringStorage plugin kind (e.g. in_process, file_system, s3). Resolved against the gateway's content-store plugin registry at boot.

StoreOverrideConfig

<capability>.store: { kind, … } — produces an Arc<dyn mcpg_cluster_api::KeyValueStore> at boot.

Recognised kind values: cluster, memory, file. (redis and nats are not accepted here — set cluster.kind: redis | nats and use kind: cluster here, or omit the override entirely.)

FieldTypeDefaultSummary
kindstring

SubjectToken

The subject token an oauth_impersonation credential issuer exchanges.

Variants:

  • caller_bearer — The caller's bearer token, as presented to the gateway.

  • idp_refresh_token — The refresh token of the caller's stored enterprise IdP sign-in.

  • idp_id_token — An ID token of the caller's stored enterprise IdP sign-in.

SubscriberTrust

Lowest trust tier allowed to hold a webhook subscription.

Variants:

  • verified — A cryptographically verified principal.

  • header_asserted — Also a principal named by a trusted request header.

SubscriptionsConfig

subscriptions: config (resource subscriptions) — subscription store + per-capability store: override + per-session quota. When store is unset, the subscription KV inherits from the cluster's key_value_store() primitive.

FieldTypeDefaultSummary
max_per_sessioninteger100Maximum subscriptions per session (0 = unlimited).
storeStoreOverrideConfig (optional)

SynthesizeConfig

Change-notification synthesis: when the upstream has no server→client push channel, the gateway can manufacture notifications/resources/updated for subscribed federated resources by polling them through the normal read path and hash-diffing the content (the watch engine's poll strategy). list_changed synthesis rides the existing capability TTL refresh (cache.capability_ttl_secs) and needs no knob here.

FieldTypeDefaultSummary
poll_interval_msinteger30000Poll cadence for synthesized resource updates. Watchers are subscriber-gated: no subscribers, no polling.
resources_updatedSynthesizeMode"auto"When to poll-synthesize resources/updated for subscribed federated resources.

SynthesizeMode

Gate on synthesized change notifications.

Variants:

  • auto — Synthesize only when the upstream demonstrably cannot push resource updates: the modern (2026-07-28) wire and the stdio transport. A legacy streamable-http upstream keeps its GET-SSE push path and is not polled.

  • poll — Always poll, even for upstreams with a push channel (covers legacy servers that only emit resources/updated for subscriptions the gateway does not place upstream).

  • off — Never synthesize.

TaggedVariableCompletionSource

Variants:

  • (unnamed variant) — Static list of completion values. Same shape as the shorthand bare-list but with explicit kind tag.

    • kind: string
    • values: array<string>
  • (unnamed variant) — Dynamic dispatch: at completion time, the gateway calls [crate::backends::CapabilityRegistry::complete_argument], which routes to the named backend's BackendPlugin::complete_template_variable(binding_name, variable_name, prefix, &config). The backend returns up to 100 completions matching the prefix.

    • backend: string
    • config: any
    • kind: string

TasksConfig

tasks: config (MCP 2025-11-25 tasks system) — task store + per-capability store: override + retention tuning. When store is unset, the task KV inherits from the cluster's key_value_store() primitive. Tuning fields (default_ttl_ms, reaper_interval_ms, max_tasks_per_session, result_wait_ms) are orthogonal and apply to whichever backend resolves.

FieldTypeDefaultSummary
default_ttl_msinteger1800000Default TTL applied to any task created without an explicit task.ttl from the client. Used by tasks/create and the reaper.
max_tasks_per_sessioninteger256Maximum concurrent tasks per session. Creation above this quota is rejected with JSON-RPC -32603 Internal error rather than silently succeeding. 0 disables the quota.
reaper_interval_msinteger60000Background reaper sweep interval. The reaper deletes records whose created_at + ttl has elapsed.
result_wait_msinteger30000Upper bound on a single tasks/result HTTP blocking wait. Clients that need longer-running tasks reconnect via GET SSE and Last-Event-Id until the task goes terminal.
storeStoreOverrideConfig (optional)

TextEncoding

How bytes are written as text.

Variants:

  • utf8 — The text's own UTF-8 bytes.

  • hex — Hexadecimal.

  • base64 — Standard base64, with or without padding.

TlsConfig

TLS configuration for the HTTP transport.

FieldTypeDefaultSummary
cert_pathstring
client_ca_certs_pathstring (optional)Optional path to a PEM bundle of CA certs that gate-keep client cert acceptance for mTLS. Required whenever client_cert_required is "optional" or "mandatory"; must be empty / absent when "none".
client_cert_requiredClientCertMode"none"Client cert acceptance mode for mTLS connections:
key_pathstring
min_tls_versionstring"1.2"Minimum TLS version: "1.2" or "1.3". Default "1.2".

TokenSourceConfig

FieldTypeDefaultSummary
header_namestring (optional)
header_prefixstring (optional)
kindTokenSourceKind"authorization_bearer"

TokenSourceKind

Allowed values:

  • authorization_bearer
  • custom_header

ToolAccessPolicyConfig

FieldTypeDefaultSummary
cel_allow_ifstring (optional)
default_minimum_trustTrustLevelConfig"header_asserted"
rulesarray<ToolTrustRuleConfig>[]

ToolTrustRuleConfig

FieldTypeDefaultSummary
cel_allow_ifstring (optional)
event_namestring (optional)The MCP Events type the rule governs: who may see it in events/list and subscribe to it. A rule here never governs a tool of the same name. Set this or tool_name.
minimum_trustTrustLevelConfig
required_scopesarray<string>OAuth scopes the caller's token MUST carry to invoke this tool (SEP-2350). A caller authenticated but lacking any of these is denied with HTTP 403 + a WWW-Authenticate: Bearer error="insufficient_scope", scope="…" step-up challenge naming the missing scopes, rather than a bare 403 — so a capability-aware client can request the additional scopes and retry. Empty (the default) means no scope requirement.
tool_namestringThe tool, prompt or resource the rule governs. Set this or event_name.

TracesConfig

observability.traces: — the traces signal.

Span lifecycle events (every tracing::info_span!() / debug_span!() and every plugin-emitted span) flow through the configured sink list. The canonical sink is otlp (exports to an OpenTelemetry Collector). Default: traces disabled (operators opt in by setting enabled: true and adding sinks).

FieldTypeDefaultSummary
enabledbooleanfalseMaster enable for the traces signal. Default false — tracing has non-trivial overhead so operators opt in.
propagate_contextbooleantruePropagate W3C trace context (traceparent / tracestate) headers to outbound binding calls. Defaults to true — downstream services join the same trace.
service_namestring"mcpg"Service name advertised to OTel collectors. Default "mcpg".
sinksarray<SinkConfig>[]Sink fan-out. Each entry's kind: resolves to a built-in factory (otlp) or plugin id. Default: empty — operators add an otlp sink to ship to a collector.

TransportMode

Transport mode determines whether MCPG runs as an HTTP server or a stdio JSON-RPC process.

Allowed values:

  • http
  • stdio

TrustLevelConfig

A caller trust level, lowest first.

Allowed values:

  • unauthenticated
  • header_asserted
  • verified

TrustedIdpClaimMappingConfig

Claim mapping of one trusted IdP's ID-JAGs (trusted_idps[].claim_mappings): the oidc_oauth claim_mappings shape without scope_claim_paths, because a minted token's scopes are the ones the grant allows. Paths are dotted (realm_access.roles); a list claim is an array of strings or a space-separated string.

FieldTypeDefaultSummary
attribute_claim_mappingsmap<string, string>{}String claims copied into identity.attributes, as {claim path: attribute name}, for example {acr: acr}. The attributes the gateway sets itself (client_id, idp, token_issuer, email, actor, tenant, amr, the grant_type, grant_id and auth_time of interactive sign-in, dpop_jkt of a token presented with a DPoP proof, authorization_details and authorization_details_types of a token limited to authorization details, and every name starting with subject_token, which it reserves for a credential issuer) cannot be mapped to.
group_claim_pathsarray<string>[]Claims whose values become identity.groups, in order, without duplicates.
role_claim_pathsarray<string>[]Claims whose values become identity.roles, before any client_roles of the client.
subject_claimstring"sub"The string claim that identifies the user: the minted token's sub and the principal's subject. An ID-JAG without it is refused (invalid_grant). act (the actor, never the user) and claims that identify no user (client_id, iss, aud, jti) are refused. A claim other than sub, such as email, can be reassigned to another person by the IdP.

TrustedIdpConfig

One enterprise IdP trusted to issue ID-JAGs.

FieldTypeDefaultSummary
allow_private_networkbooleanfalseLocal-development escape hatch: permit http:// and private/loopback IdP addresses, including a host name that resolves to one. Production deployments leave this false.
allowed_algsarray<string>(see type)JWS algorithms accepted on this IdP's ID-JAGs: any of RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384, EdDSA, and by default all of them. An assertion signed with another algorithm is refused (invalid_grant). HMAC algorithms are never accepted. List only the IdP's own algorithm to refuse any other (Okta signs with RS256). With login, the same list applies to the ID tokens of sign-in.
allowed_clientsarray<string>[]The clients this IdP may issue ID-JAGs for, and with login the clients whose users may sign in through it: clients[].client_id values, or metadata document URLs that client_id_metadata_documents.allowed_hosts admits. An assertion from this IdP presented by another client is refused (invalid_grant). A dynamically registered client never satisfies a non-empty list. Empty = every client.
allowed_hostsarray<string>[]Optional host allowlist for discovery/JWKS fetches (exact or subdomain match). Empty = any public host.
claim_mappingsTrustedIdpClaimMappingConfig(see type)Which ID-JAG claims (and, with login, ID token claims) name the user and become their groups, roles and attributes. The mapped values travel in the minted access token, so identity.groups, identity.roles and identity.attributes in governance.policy see them on every MCP request made with it. Okta's published ID-JAGs carry no group claim, so there is nothing to map groups from; policy can match Okta callers on identity.attributes['client_id'], and on ['email'] when the ID-JAG carries one.
issuerstringThe IdP's issuer identifier, compared exactly (a trailing / included) against the ID-JAG iss claim and against the issuer of the IdP's discovery document. Okta's org authorization server issues ID-JAGs as https://{yourOktaDomain}, without a trailing /.
jwksanyThe IdP's JSON Web Key Set (RFC 7517), inline: an object with a keys array, or the same document as a JSON string (for example ${env.IDP_JWKS}). For an IdP the gateway cannot reach: no discovery or JWKS request is made, and a key rotation at the IdP needs a config change. Mutually exclusive with jwks_uri.
jwks_uristring (optional)JWKS endpoint override. When neither this nor jwks is set, the JWKS URI is taken from the IdP's OIDC discovery document ({issuer}/.well-known/openid-configuration) or, when the IdP serves none, from its RFC 8414 metadata (/.well-known/oauth-authorization-server); the document's issuer must equal issuer. Fetched keys are reused for 5 minutes; while the IdP cannot be reached or answers with a server error, a timeout or a rate limit, the last key set that was fetched keeps verifying for up to an hour, after which redemption answers temporarily_unavailable. Any other client error is a configuration problem, which redemption names (invalid_grant). Redirects and responses above 1 MiB are refused.
loginTrustedIdpLoginConfig (optional)The gateway's OIDC client at this IdP, which turns on interactive sign-in through it (at most one entry). Its users are the same principals as this IdP's ID-JAG users, and the IdP keeps accepting ID-JAGs.
principal_issuerstring (optional)Make this IdP's users the same principals as the SSO users of the OIDC provider with this issuer (governance.access.oidc_oauth or the dev.mcpg.identity.oidc plugin), such as the Okta custom authorization server that signs the enterprise's SSO access tokens. An EMA caller is then reported with identity.issuer set to this value and identity.auth_provider set to oidc_oauth:<this value>, exactly as that provider reports its own users, so sessions, tasks, idempotency and quotas are shared between the two ways in, provided both name the user with the same subject (see claim_mappings.subject_claim, and the provider's own subject_claim; for Okta, uid there and the default sub here). Tell EMA callers apart by identity.attributes['token_issuer']. Unset: principals are namespaced by this IdP's issuer, with auth_provider ema. No two IdPs may share a value.
required_tenantstring (optional)For a multi-tenant IdP: the tenant claim every ID-JAG (and, with login, every ID token) from it must carry, compared exactly. An assertion without it or with another value is refused (invalid_grant). Unset, a tenant claim is accepted as it comes and joins the principal namespace, since a multi-tenant IdP's sub is unique only within a tenant: the caller is reported with identity.issuer set to {issuer}#{tenant} (unless principal_issuer is set).

TrustedIdpLoginConfig

The gateway's own OIDC client at a trusted IdP (governance.access.authorization_server.trusted_idps[].login). Its presence turns interactive sign-in on through that IdP; at most one entry may have it. The IdP's issuer, allowed_hosts, allow_private_network, allowed_algs (the ID token signature), claim_mappings, allowed_clients, required_tenant and principal_issuer apply to sign-in exactly as to ID-JAGs, so a user who signs in and the same user arriving with an ID-JAG are one principal. The IdP keeps accepting ID-JAGs. Register {authorization_server.issuer}/oauth/callback as the sign-in redirect URI of this client at the IdP (mcpg config check prints it). For Okta Cross App Access the issuer is the org authorization server (https://{org}.okta.com) and this client is the OIDC app linked to the AI agent. Requires a license with the sso.interactive_login feature.

FieldTypeDefaultSummary
assertion_audienceLoginAssertionAudience (optional)private_key_jwt: the assertion's aud: token_endpoint (the default: the URL of the endpoint the assertion is posted to, the token endpoint or, when a stored sign-in is revoked, the revocation endpoint, as Okta requires) or issuer (the IdP's issuer identifier).
authorization_endpointstring (optional)The IdP authorization endpoint. Unless this, token_endpoint and revocation_endpoint are all set, the endpoints are read from the IdP's OIDC discovery document ({issuer}/.well-known/ openid-configuration, then the RFC 8414 form), whose issuer must equal the configured one exactly. Each endpoint must be https:// on the entry's allowed_hosts (http:// and private addresses only with allow_private_network).
authorize_paramsmap<string, string>{}Extra parameters for the IdP authorization request, such as acr_values or domain_hint. The parameters the gateway sets itself (response_type, client_id, redirect_uri, scope, state, nonce, code_challenge, code_challenge_method, request, request_uri, response_mode, prompt, max_age, login_hint) are refused.
client_authLoginClientAuth (optional)How the gateway authenticates at the IdP token endpoint: client_secret_basic, client_secret_post or private_key_jwt. Defaults to private_key_jwt when private_key is set and to client_secret_basic when client_secret is set. The gateway is always a confidential client here, so a stolen IdP refresh token is useless without this credential.
client_idstringThe client identifier the IdP issued to the gateway (1–255 characters).
client_secretstring (optional)Shared secret for the two secret methods, at least 16 bytes. Supply via ${secret.NAME} or ${env.X}. Set this or private_key, not both.
display_namestring (optional)The IdP's name on the consent and connect pages (1–60 characters). Defaults to the IdP's host.
key_idstring (optional)private_key_jwt: the kid header of the client assertion (at most 128 characters). Unset, the assertion carries no kid.
max_age_secsinteger (optional)Longest time since the user last authenticated at the IdP that a sign-in accepts, in seconds (0–86400). Sent as max_age; the ID token must then carry auth_time. Unset, any session is accepted.
private_keystring (optional)private_key_jwt: the PEM private key the client assertion is signed with (PKCS#8, or PKCS#1 for RSA), parsed and trial-signed at load. Supply via ${secret.NAME}, which reads the file verbatim, or ${env.X}.
revocation_endpointstring (optional)The IdP's RFC 7009 revocation endpoint, where a replaced or abandoned IdP refresh token is revoked. See authorization_endpoint.
scopesarray<string>(see type)Scopes requested at the IdP. Must include openid; at most 20, each once. Without offline_access the IdP issues no refresh token, so the gateway issues none while refresh_tokens. revalidate_with_idp is on and idp_refresh_token federations cannot work.
signing_algLoginSigningAlg (optional)private_key_jwt: the assertion's JWS algorithm, RS256, PS256, ES256 or EdDSA. Defaults to the one the key type implies (RSA: RS256, P-256: ES256, Ed25519: EdDSA); a value the key cannot sign with is refused at load.
timeout_msinteger5000Timeout of each request to the IdP, in milliseconds (500–10000).
token_endpointstring (optional)The IdP token endpoint, where authorization codes are redeemed and the stored sign-in is refreshed. See authorization_endpoint.

TrustedKeyConfig

One trusted-key entry inside SignatureConfig.trusted_keys.

FieldTypeDefaultSummary
idstringOperator-chosen id for the key (audit-trail label).
pemstringPEM-encoded public key. Multi-line literal in YAML.

TunnelConfig

Reverse-tunnel egress config. The gateway dials out to a relay and answers tunnelled MCP traffic through its own request path.

FieldTypeDefaultSummary
enabledbooleanfalseMaster switch. false (default) dials no tunnel.
exposureTunnelExposure"public"public allocates a <id>.tunnels.mcpg.cloud URL (dev preview, third-party MCP clients); private (federation-only) allocates no public address and is reachable only as a tunnel:// federation upstream from the same org.
modeTunnelTrustMode"relay_terminated"relay_terminated (the relay sees plaintext) or e2ee (relay splices ciphertext — requires private exposure, mcpg-to-mcpg only).
namestring (optional)Optional stable tunnel name, held by one gateway at a time within the org. When unset, the tunnel is named after this gateway's instance uid, which changes on every restart. A public tunnel's name is its host label (<name>.tunnels.mcpg.cloud), so it must be one DNS label: 1 to 63 letters, digits and -, neither starting nor ending with -, compared lowercase. The relay refuses any other public name with tunnel_name_invalid.
relay_urlstring"wss://relay.tunnels.mcpg.cloud"Relay endpoint to dial (e.g. wss://relay.tunnels.mcpg.cloud).

TunnelExposure

Whether a tunnel gets a public hostname.

Allowed values:

  • public
  • private

TunnelFederationConfig

Reverse-federation ingress config. A tunnel://<name>/<path> federation upstream resolves through the relay's federation ingress to <relay_ingress_url>/federate/<name>/<path>. This gateway authenticates its ORG to the relay with the token field below (carried in the X-MCPG-Tunnel-Token header, which the relay consumes and never forwards); the end-user's Authorization bearer flows through, untouched, to the tunnelled gateway as the MCP caller identity.

FieldTypeDefaultSummary
relay_ingress_urlstringRelay federation-ingress base URL, e.g. https://relay.tunnels.mcpg.cloud. Must be http(s).
tokenstring (optional)Org token presented to the relay in X-MCPG-Tunnel-Token. When unset, the gateway falls back to the MCPG_TUNNEL_TOKEN environment variable (the same org token used for egress dial), so a gateway that both dials and federates needs the token in one place. Supports ${env.X}.

TunnelTrustMode

Who can read tunnelled payloads.

Allowed values:

  • relay_terminated
  • e2ee

UiGroup

A labelled group of fields in a form/detail layout.

FieldTypeDefaultSummary
fieldsarray<string>
labelstring

UiSchema

Widget/layout overlay — the tight mcpg-native uiSchema subset.

FieldTypeDefaultSummary
groupsarray<UiGroup> (optional)Labelled field groups / sections.
orderarray<string> (optional)Explicit field render order.
widgetsmap<string, UiWidget>field-path → widget specification.

UiWidget

How a single field renders, plus its client-evaluated rules.

FieldTypeDefaultSummary
enum_fromEnumSource (optional)Options sourced from a sibling tool result (via the action proxy).
helpstring (optional)
labelstring (optional)
placeholderstring (optional)
required_ifstring (optional)Client-evaluated conditional-required expression.
visible_ifstring (optional)Client-evaluated visibility expression.
widgetWidgetKind

UnsubscribeUnknown

The answer to an events/unsubscribe naming no live subscription.

Variants:

  • ok — {}, the same answer as for a subscription that existed.

  • not_found — -32011 with data.kind: "subscription".

UpstreamConfig

Upstream connection details.

FieldTypeDefaultSummary
argsarray<string>[]Arguments passed to the stdio command.
authAuthConfig(see type)How MCPG authenticates to the upstream.
commandstring (optional)Command to spawn for the stdio transport (ignored otherwise).
envmap<string, string>{}Extra environment for the stdio command.
headersmap<string, string>Static request headers sent on every upstream call (API-key style upstreams, e.g. X-API-Key); values support ${env.X}. Reserved protocol headers (authorization, mcp-*, content-type, accept) are rejected — auth goes through auth, the wire headers stay MCPG's.
protocol_versionUpstreamProtocolVersion"auto"MCP wire revision MCPG speaks to this upstream as a client.
transportUpstreamTransport"streamable_http"Wire transport.
upstream_safetyUpstreamSafetyConfig(see type)SSRF / DNS-rebinding posture (http) + local-exec posture (stdio).
urlstring""Base MCP endpoint URL (the upstream's /mcp). Required for the streamable_http transport; unused (empty) for stdio.

UpstreamEventDelivery

The upstream delivery mode of an imported event type.

Variants:

  • auto — webhook when the type offers it and the gateway has a public address (gateway.server.canonical_url, or a public tunnel), else push when offered, else poll.

  • webhook — An upstream webhook subscription per upstream subscription, delivering to /webhooks/mcp-events/{federation}/{id} on this gateway.

  • push — One events/stream per upstream subscription.

  • poll — An events/poll loop per upstream subscription.

UpstreamEventSharing

Whose upstream subscriptions serve the subscribers of an imported type.

Variants:

  • fan_in — One upstream subscription per set of arguments, held with the federation's catalogue credential, shared by every subscriber of those arguments.

  • per_principal — One upstream subscription per subscriber and set of arguments, held with that subscriber's own upstream credential, minted from their stored sign-in. Needs auth.mode: oauth_impersonation with subject_token: idp_refresh_token or idp_id_token.

UpstreamProtocolVersion

MCP wire revision the federation client speaks to an upstream.

Variants:

  • auto — Detect the upstream's wire at connect time (the default): attempt the modern server/discover, and fall back to the legacy initialize handshake when the peer rejects it (the SEP-2575 backward-compatibility probe). The detected wire is cached per federation for the engine's lifetime. Pin one of the dated revisions to skip probing. The stdio transport never probes (it is legacy-only).

  • 2025-11-25 — Session-bound 2025-11-25 wire (initialize handshake, Mcp-Session-Id, no SEP-2243 headers) — byte-identical to the legacy federation client.

  • 2026-07-28 — Stateless 2026-07-28 wire (no handshake / session, per-request _meta identity, SEP-2243 routing headers).

UpstreamSafetyConfig

SSRF / DNS-rebinding posture for the upstream URL. Mirrors the HTTP binding's guard (runtime/safe_dns.rs).

FieldTypeDefaultSummary
allow_insecure_httpbooleanfalsePermit http:// (non-TLS) upstreams.
allow_private_backendsbooleanfalsePermit private / loopback upstream addresses.
allow_stdiobooleanfalsePermit the stdio transport, which spawns a local child process (arbitrary local execution — default-deny).

UpstreamTransport

Upstream wire transport.

Variants:

  • streamable_http — MCP Streamable HTTP (POST + SSE).

  • stdio — Local stdio child process.

UsageReportingConfig

FieldTypeDefaultSummary
enabledbooleanfalseSend the anonymous adoption ping. Defaults to false. DO_NOT_TRACK=1 / MCPG_TELEMETRY=off disable regardless.
endpointstring"https://telemetry.mcpg.dev/v1/usage"Ingest endpoint (HTTPS). Self-hostable — point it at your own collector.

VariableCompletionSource

Per-template-variable completion source.

The bare-list shorthand stays valid (post-launch shipping shape): variable_completions: { region: ["us-east-1", "us-west-2"] } is read by the BareList arm and treated as if the operator had written { kind: static, values: [...] }. The tagged form is the new shape — kind: dynamic introduces backend dispatch.

Type: array<string> | TaggedVariableCompletionSource

VerificationConfig

Variants:

  • (unnamed variant)

    • allow_hmac: boolean
    • allowed_algs: array<string>
    • kind: string
    • max_staleness_secs: integer
    • refresh_interval_secs: integer
    • timeout_ms: integer
  • (unnamed variant)

    • client_id: string
    • client_secret_ref: string
    • introspection_url: string
    • kind: string
    • timeout_ms: integer
  • (unnamed variant) — JWTs are verified against the JWKS; opaque tokens are introspected.

    • allow_hmac: boolean
    • allowed_algs: array<string>
    • client_id: string
    • client_secret_ref: string
    • introspection_timeout_ms: integer
    • introspection_url: string
    • kind: string
    • max_staleness_secs: integer
    • refresh_interval_secs: integer
    • timeout_ms: integer

VerificationMode

A proof of intent a callback endpoint may give.

Variants:

  • challenge — A signed challenge the endpoint echoes in a 2xx response.

  • allowlist — The callback host is on verification.allowlist.

  • well_known — The callback's origin publishes /.well-known/mcp-webhook-receiver.json naming it: {"callbacks": [...]}, each entry a URL or a prefix ending in /. Provisional: the upstream sketch has not fixed the document's shape.

WatchStrategyConfig

Variants:

  • poll — Poll the resource periodically and compare SHA-256 hash.

    • interval_ms: integer
  • nats_topic — Subscribe to a NATS subject — any message means the resource changed.

    • subject: string
  • kafka_topic — Subscribe to a Kafka topic — any message means the resource changed.

    • group_id: string (optional)
    • topic: string
  • webhook — Receive webhook POSTs from 3rd-party systems. MCPG exposes /webhooks/resource-updated/{token} and triggers notifications/resources/updated when a POST is received.

    • previous_tokens: array<string>
    • token: string
  • sql_polling — SQL polling watch — dev.mcpg.watch.sql_polling plugin runs a scalar tracking query on a cadence and emits an event when the returned scalar advances. Spec mirrors the [bindings.sql] shape (driver, url, optional pool / session_vars, required query block, interval_ms); see the SQL binding plugin docs for the full field list. Pass-through here keeps the spec the single source of truth in the plugin crate.

  • postgres_listen_notify — Postgres LISTEN/NOTIFY watch — dev.mcpg.watch.postgres_listen_notify plugin holds one dedicated connection per watch and re-emits NOTIFY payloads. Far lower overhead than polling for change-feed-style sources.

  • plugin — Generic escape hatch — delegate to ANY loaded watch_strategy plugin by its kind() discriminator. Use this for custom watch plugins that have no dedicated typed variant above (e.g. the Twilio plugin's twilio_inbound strategy). The remaining fields flatten into the spec passed to the plugin's watch(), so the plugin owns and validates its own spec schema. As in every strategy, ${env.X}, ${secret.X} and secret provider URIs in its strings are resolved first.

    • kind: string

WebhookEgressConfig

events.webhook.egress: — the client that reaches callback URLs. It resolves the host on every attempt, refuses a host when any address is not globally routable, connects to the checked address, and never follows a redirect.

FieldTypeDefaultSummary
allow_private_backendsbooleanfalseAdmit callback hosts outside the public internet. For self-hosted deployments that deliver inside their own network. Default false.
allowed_portsarray<integer>[]Ports a callback URL may name. Empty (default) allows any port.
attempt_timeout_msinteger10000Time allowed for one whole request, in milliseconds. Default 10 000.
connect_timeout_msinteger3000Time allowed to open a connection, in milliseconds. Default 3 000.
deny_hostsarray<string>[]Hosts never contacted: a domain (with every name under it) or an IP address. Applies to existing subscriptions from their next delivery.
probe_urlstring (optional)A URL fetched with GET, through this egress policy, when webhook delivery starts and whenever the egress settings change. Any HTTP answer shows callbacks are reachable and sets mcpg_events_egress_probe_ok to 1; no answer sets it to 0 and logs a warning. Must pass the policy above. Omitted, no probe.

WebhookLimitsConfig

events.webhook.limits: — subscription and delivery limits. A subscribe past a subscription limit answers -32013 naming the limit.

FieldTypeDefaultSummary
per_host_concurrencyinteger8Concurrent delivery requests to one callback host. Default 8.
per_principalinteger100Subscriptions one principal may hold. Default 100.
per_principal_callback_hostsinteger5Distinct callback hosts one principal may name. Default 5.
per_subscription_in_flightinteger4Deliveries of one subscription in flight at once. Default 4.
per_typeinteger5000Subscriptions on one event type. Default 5 000.
totalinteger10000Subscriptions on this gateway. Default 10 000.

WebhookRetryConfig

events.webhook.retry: — retries of one delivery. A 410 or 413 answer is never retried.

FieldTypeDefaultSummary
honor_retry_afterbooleantrueWait as long as a Retry-After answer asks, inside the window. Default true.
max_attemptsinteger5Attempts per delivery, the first included. Default 5.
window_msinteger900000Time after the first attempt within which retries happen, in milliseconds. Default 900 000 (15 minutes). On a gateway a control plane manages, the plan's retry window is the most it can be.

WebhookSuspensionConfig

events.webhook.suspension: — when failing deliveries suspend a subscription. A refresh reactivates it.

FieldTypeDefaultSummary
drop_no_expiry_after_msinteger604800000How long a suspended subscription with no expiry is kept before it is dropped, in milliseconds. Default 604 800 000 (7 days).
failure_rateFailureRate0.95Share of failed attempts within window_ms that suspends delivery, greater than 0 and at most 1. Default 0.95.
min_attemptsinteger100Fewest attempts within the window before the rate counts. Default 100.
window_msinteger3600000The window the failure rate is measured over, in milliseconds. Default 3 600 000 (1 hour).

WebhookTtlConfig

events.webhook.ttl: — subscription grant lifetimes.

FieldTypeDefaultSummary
allow_no_expirybooleanfalseGrant a subscription with no expiry when the client sends ttlMs: null. Off by default, when such a request receives a max_ms grant instead.
clamp_to_token_expirybooleantrueNever grant past the expiry of the subscriber's access token, when it is known (never below min_ms). Default true.
default_msinteger3600000Grant for a subscribe that sends no ttlMs, in milliseconds. Default 3 600 000 (1 hour).
max_msinteger86400000Longest grant; a longer ttlMs is lowered to it. Default 86 400 000 (24 hours).
min_msinteger300000Shortest grant; a shorter ttlMs is raised to it. Default 300 000 (5 minutes).

WebhookVerificationConfig

events.webhook.verification: — how a callback endpoint proves it wants the deliveries before the first one.

FieldTypeDefaultSummary
allowlistarray<string>[]Callback hosts admitted without a challenge when modes lists allowlist.
cache_ttl_msinteger86400000How long a passed verification is reused for the same principal and callback URL, in milliseconds. Default 86 400 000 (24 hours).
modesarray<VerificationMode>(see type)Accepted proofs. challenge (default, and always required) POSTs a signed challenge that the endpoint echoes; allowlist also admits the hosts in allowlist without one; well_known admits a callback its origin's receiver document names.
per_host_in_flightinteger3Challenge requests in flight to one callback host. Default 3.
per_host_per_mininteger6Challenge requests per callback host per minute, across the cluster. Default 6.
timeout_msinteger10000Time allowed for a challenge request, in milliseconds. Default 10 000.

WidgetKind

The closed widget vocabulary.

Variants:

  • text

  • array — Repeatable list of scalar inputs → a JSON array value.