> ## Documentation Index
> Fetch the complete documentation index at: https://bifrost-backport-semantic-cache-scope.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Schema Reference

> All top-level keys available in config.json, their types, and where each is documented

<Note>
  The live schema is published at [`https://www.getbifrost.ai/schema`](https://www.getbifrost.ai/schema). Add `"$schema": "https://www.getbifrost.ai/schema"` to your `config.json` for IDE autocomplete and inline validation, or point it to a mirrored HTTP(S) URL, `file://` URL, or filesystem path in isolated deployments. You can also set the `BIFROST_SCHEMA_URL` environment variable, which takes precedence over the `$schema` value. When mirroring, snapshot a schema published by a Bifrost release that supports custom `$schema` values; older schema copies pin `$schema` to the public URL and will flag a mirrored location as invalid in IDEs.
</Note>

This page is a concise reference for every top-level key in `config.json`. Click the **Guide** links for full field-by-field documentation.

***

## Top-Level Keys

| Key | Type | Description | Guide |
| - | - | - | - |
| `$schema` | string | Schema location for IDE validation. Defaults to `"https://www.getbifrost.ai/schema"`; isolated deployments can use a mirrored URL, `file://` URL, or filesystem path. | - |
| `version` | integer | Compatibility switch for empty allow-list arrays. Omit for current v2 semantics. | [`version`](#version) |
| `source_of_truth` | string | Startup reconciliation mode for DB-backed `config.json`: `"split"` or `"config.json"` | [Source of Truth](/deployment-guides/config-json/source-of-truth) |
| `encryption_key` | string | Optional AES-256 key (derived via Argon2id). Accepts `env.VAR` prefix and is also read from `BIFROST_ENCRYPTION_KEY`. If omitted, data is stored in plaintext. | [Client](/deployment-guides/config-json/client#encryption-key) |
| `client` | object | Worker pool, logging, CORS, auth enforcement, header filtering, MCP, compat shims | [Client](/deployment-guides/config-json/client) |
| `providers` | object | LLM provider API keys, network settings, concurrency | [Providers](/deployment-guides/config-json/providers) |
| `governance` | object | Admin auth, virtual keys, budgets, rate limits, routing rules, customers, teams, roles and business units | [Governance](/deployment-guides/config-json/governance) |
| `alerting` | object | Alert channels, CEL-based rules, history retention, and webhook network controls *(enterprise only)* | [Alerting](/deployment-guides/config-json/alerting) |
| `guardrails_config` | object | Content moderation providers and CEL-based rules *(enterprise only)* | [Guardrails](/deployment-guides/config-json/guardrails) |
| `access_profiles` | array | Access profile templates for enterprise RBAC/governance controls *(enterprise only)* | [Access Profiles](/enterprise/access-profiles) |
| `cluster_config` | object | Cluster mode settings: gossip, peers, and auto-discovery backends *(enterprise only)* | [Cluster](/deployment-guides/config-json/cluster) |
| `config_store` | object | Configuration database backend - SQLite, PostgreSQL, or disabled (file-only mode) | [Storage](/deployment-guides/config-json/storage#config_store) |
| `logs_store` | object | Request/response log database - SQLite, PostgreSQL, ClickHouse + optional S3/GCS offload | [Storage](/deployment-guides/config-json/storage#logs_store) |
| `vector_store` | object | Vector database for semantic cache - Weaviate, Redis, Qdrant, Pinecone, Valkey | [Storage](/deployment-guides/config-json/storage#vector_store) |
| `plugins` | array | Opt-in plugins: `semantic_cache`, `otel`, `maxim`, `datadog`, custom | [Plugins](/deployment-guides/config-json/plugins) |
| `framework` | object | Model pricing catalog URL and sync interval | [Framework](#framework) |
| `mcp` | object | MCP server and tool configuration | [MCP](#mcp) |
| `websocket` | object | WebSocket / Realtime API connection pool tuning | [WebSocket](#websocket) |
| `auth_config` | object | **Deprecated** - use `governance.auth_config` | [Client](/deployment-guides/config-json/client#authentication) |

***

## `version`

Controls how empty arrays in the allow-list fields of provider keys (`models`) and virtual keys (`allowed_models`, `key_ids`, `tools_to_execute`) are interpreted:

| Value | Behaviour |
| - | - |
| `2` *(default, v1.5.0+)* | Empty array = **deny all**; `["*"]` = allow all |
| `1` *(v1.4.x compat)* | Empty array = **allow all** |

Omitting `version` uses v2 semantics. Set `"version": 1` only if you are migrating from v1.4.x and need the old behaviour temporarily.

Projects declared under `governance.projects` are not covered by this switch: their `key_ids` and `tools_to_execute` always treat an empty or omitted array as granting nothing, under either value.

***

## `source_of_truth`

Controls how `config.json` is reconciled with the config store at startup.

| Value | Behaviour |
| - | - |
| `"split"` *(default)* | File-backed rows seed or update the config store by hash, while unchanged file-backed rows preserve UI/API edits |
| `"config.json"` | Explicitly present file sections are authoritative and replace matching DB state on startup |

Missing and empty sections behave differently when `source_of_truth` is `"config.json"`. A missing section leaves DB rows untouched; a present empty section is authoritative and can prune matching DB rows.

```json theme={null}
{
  "source_of_truth": "config.json",
  "plugins": []
}
```

The example above makes the `plugins` section present and empty, so stored plugins are removed on startup. See [Source of Truth & Reconciliation](/deployment-guides/config-json/source-of-truth) for section-by-section behavior.

***

## `client`

Controls the worker pool, logging pipeline, security, and SDK shims. All fields are optional.

| Field | Type | Default | Description |
| - | - | - | - |
| `initial_pool_size` | integer | `300` | Pre-allocated goroutines per provider queue |
| `drop_excess_requests` | boolean | `false` | Return HTTP 429 when queue is full |
| `enable_logging` | boolean | `true`\* | Persist request/response logs (`*` auto-enabled when `logs_store` is set) |
| `disable_content_logging` | boolean | `false` | Strip message content from logs |
| `log_retention_days` | integer | `365` | Days to retain log entries |
| `logging_headers` | array | `[]` | HTTP headers to capture in log metadata |
| `hidden_request_types` | array | `[]` | Request types hidden from Logs and Dashboard reads; logs are still stored |
| `enforce_auth_on_inference` | boolean | `false` | Require a virtual key on every `/v1/*` request |
| `allowed_origins` | array | `["*"]` | CORS allowed origins |
| `allow_direct_keys` | boolean | `false` | Let callers bypass the key pool with `x-bf-direct-key: true` + a raw provider key |
| `max_request_body_size_mb` | integer | `100` | Maximum request body in MB |
| `whitelisted_routes` | array | `[]` | Routes that bypass auth middleware |
| `allowed_headers` | array | `[]` | Additional headers permitted for CORS/WebSocket |
| `required_headers` | array | `[]` | Headers that must be present on every request |
| `header_filter_config` | object | - | `allowlist` / `denylist` for `x-bf-eh-*` forwarded headers |
| `prometheus_labels` | array | `[]` | Custom labels for all Prometheus metrics |
| `compat` | object | - | SDK compatibility shims (`should_drop_params`, `convert_text_to_chat`, etc.) |
| `mcp_agent_depth` | integer | `10` | Max tool-call recursion depth |
| `mcp_tool_execution_timeout` | integer or string | `30` | Per-tool execution timeout in seconds (integer = seconds, string = Go duration like "30s", "2m") |
| `mcp_tool_sync_interval` | integer | `10` | Tool sync interval in minutes (`0` = default of 10 minutes) |
| `mcp_disable_auto_tool_inject` | boolean | `false` | Disable automatic MCP tool injection |
| `async_job_result_ttl` | integer | `3600` | TTL for async job results in seconds |
| `disable_db_pings_in_health` | boolean | `false` | Exclude DB connectivity from `/health` |
| `routing_chain_max_depth` | integer | `10` | Max routing rule chain evaluation depth |
| `mcp_external_client_url` | string \| EnvVar | - | Public base URL used as `redirect_uri` against upstream MCP OAuth providers; supports `"env.MY_VAR"` |

Full documentation: [Client Configuration](/deployment-guides/config-json/client).

***

## `providers`

Keyed by provider name. Each entry contains a `keys` array and optional `network_config`, `concurrency_and_buffer_size`, `proxy_config`.

Supported provider keys: `anthropic`, `azure`, `bedrock`, `bedrock_mantle`, `cerebras`, `cohere`, `deepseek`, `gemini`, `groq`, `mistral`, `ollama`, `opencode-go`, `opencode-zen`, `openai`, `parasail`, `perplexity`, `sgl`, `vertex`, `openrouter`, `elevenlabs`, `huggingface`, `nebius`, `xai`, `replicate`, `vllm`, `runway`, `runware`, `fireworks`, `sarvam`, `wafer`, `databricks`.

Full documentation: [Provider Setup](/deployment-guides/config-json/providers).

***

## `governance`

Seeds governance resources at startup. All sub-keys are optional arrays.

| Sub-key | Description |
| - | - |
| `auth_config` | Admin username/password auth for the dashboard |
| `virtual_keys` | Scoped API tokens with provider/model allowlists |
| `budgets` | Spend caps in USD over a rolling window |
| `rate_limits` | Request and token rate limits |
| `customers` | Customer entities (attach budgets/rate limits) |
| `teams` | Team entities (attach to customers and rate limits; budgets attach to teams through `budgets[].team_id`) |
| `routing_rules` | CEL-based dynamic provider/model routing |
| `pricing_overrides` | Scoped per-model pricing overrides |
| `model_configs` | Per-model rate limit and budget configurations |
| `projects` | Projects: access gates and accounting scopes that requests opt into, with budgets divisible between members *(enterprise only)* |

Full documentation: [Governance](/deployment-guides/config-json/governance).

***

## `guardrails_config`

Enterprise-only. Two sub-keys: `guardrail_providers` (array) and `guardrail_rules` (array).

Full documentation: [Guardrails](/deployment-guides/config-json/guardrails).

***

## `alerting`

Enterprise-only. Supports `channels` (array), `rules` (array), `history_retention_days`, `evaluation_interval_seconds`, and `webhook_network`.

Full documentation: [Alerting](/deployment-guides/config-json/alerting).

***

## `access_profiles`

Enterprise-only. Defines access profile templates that can later be attached to roles/users.

```json theme={null}
{
  "access_profiles": [
    {
      "name": "platform-default",
      "description": "Default platform profile",
      "is_active": true,
      "tags": ["platform", "default"],
      "provider_configs": [
        {
          "provider_name": "openai",
          "all_models_allowed": false,
          "allowed_models": ["gpt-4o", "gpt-4o-mini"]
        }
      ],
      "virtual_mcps": [
        { "virtual_mcp_name": "Platform Tools" }
      ],
      "mcp_configs": [
        { "mcp_client_id": "github", "tools_to_execute": ["create_pull_request", "list_issues"] }
      ]
    }
  ]
}
```

A profile grants MCP access through two keys:

| Field | Type | Description |
| - | - | - |
| `virtual_mcps` | array | [Virtual MCPs](#mcp-virtual_mcps) the profile grants, as `{ "virtual_mcp_name": "<name>" }`. The name is resolved on startup and one that matches no Virtual MCP is refused. An explicit empty array detaches every Virtual MCP from the profile. |
| `mcp_configs` | array | Per-client tool allowlists, as `{ "mcp_client_id": <id or name>, "tools_to_execute": [...] }`. `["*"]` means all of that client's tools including ones added later, `[]` means none, and a named list means only those tools. Client names are resolved to client ids at startup. |

<Note>
  `virtual_mcp_id` is accepted as an alternative to `virtual_mcp_name` and wins when both are set.
  Ids are assigned by the database, so prefer the name in a file that has to be portable across
  environments.
</Note>

<Note>
  `mcp_tool_groups`, `mcp_servers`, and `mcp_tool_overrides` are the deprecated former spellings.
  They are still read and folded into `virtual_mcps` / `mcp_configs` at load time, with a warning in
  the startup logs. `mcp_tool_groups` is ignored when `virtual_mcps` is present; `mcp_servers`
  becomes a `["*"]` allowlist for each listed client, except where `mcp_configs` already names that
  client. The new model has no exclude concept, so an `mcp_tool_overrides` entry with
  `"action": "exclude"` only narrows the tools Bifrost can enumerate for that client.
</Note>

***

## `governance.roles`

Enterprise-only. Declares RBAC roles, the access profiles they grant, their data access scope, and their permissions.

```json theme={null}
{
  "governance": {
    "roles": [
      {
        "name": "engineer",
        "description": "Product engineers",
        "dac": "team-data",
        "entity_dac": { "PromptRepository": "all-data" },
        "access_profiles": ["Engineering Baseline", "Opus Pilot"],
        "permissions": [
          { "resource": "VirtualKeys", "operation": "View" },
          { "resource": "PromptRepository", "operation": "Create" }
        ]
      }
    ]
  }
}
```

| Field | Type | Description |
| - | - | - |
| `name` | string | Role name. Identity field — renaming declares a different role. |
| `description` | string | Optional free-text description. |
| `dac` | string | Role-level [data access scope](/enterprise/data-access-control): `own-data`, `team-data`, or `all-data`. |
| `entity_dac` | object | Per-resource scope overrides, keyed by resource name (same names as `permissions`, below). Resources not listed follow `dac`. Applied as a **full replace** — removing `entity_dac` clears every override on the next sync. |
| `access_profiles` | string\[] | Names of every [access profile](/enterprise/access-profiles) this role grants. A role grants **all** of them, and users with the role receive each one. |
| `access_profile` | string | **Deprecated** — single-profile form, kept for existing files. Ignored whenever `access_profiles` is set. |
| `permissions` | array | `{ resource, operation }` pairs granted to the role. Both values are case-sensitive — see below. |

Resource and operation names must match Bifrost's own spelling exactly. Resources are PascalCase - `VirtualKeys`, `PromptRepository`, `Teams`, `Customers`, `BusinessUnits`, `AccessProfiles`, `Users`, `RBAC`, `APIKeys`, `Logs`, `AuditLogs`, `MCPGateway`, `RoutingRules`, `GuardrailsConfig`, and so on. Operations are normally `Create`, `View`, `Update`, and `Delete`; a few resources add their own, such as `Reveal` on `Logs`, `Download` on `AuditLogs`, and `CreateStandalone` on `VirtualKeys`.

<Warning>
  A permission Bifrost does not recognise is skipped with a warning in the startup logs, not
  rejected. A misspelling like `virtual_keys` or `read` leaves the role without that permission
  instead of failing the sync, so check the logs after editing this section.
</Warning>

The `access_profiles` list is the full set of profiles the role grants. On each sync Bifrost attaches any that are missing and removes any that are no longer listed, so editing the list is how you change what a role grants. Note that an explicit empty list (`"access_profiles": []`) means "grant nothing" and removes every profile from the role — leaving the field out entirely is different, and falls back to the deprecated `access_profile`.

<Note>
  Switching a role from `access_profile: "X"` to `access_profiles: ["X"]` is a no-op — Bifrost treats
  the two as the same declaration, so nothing re-syncs on upgrade.
</Note>

***

## `governance.business_units`

Enterprise-only. Declares business unit **definitions** and their governance.

```json theme={null}
{
  "governance": {
    "business_units": [
      {
        "id": "bu-platform",
        "name": "Platform"
      }
    ]
  }
}
```

| Field | Type | Description |
| - | - | - |
| `id` | string | Stable identifier, referenced by other config rows. |
| `name` | string | Display name. |

This section defines business units, not who belongs to them. Members are users, and they are added by an admin or by your identity provider — not from `config.json`, which cannot reference users that do not exist yet at startup. See [User Provisioning](/enterprise/user-provisioning#business-unit-membership).

***

## `cluster_config`

Enterprise-only clustering settings for multi-node deployments.

| Sub-key | Description |
| - | - |
| `enabled` | Enables cluster mode |
| `region` | Region label used by enterprise clustering |
| `peers` | Static peer list (`host:port`) |
| `gossip` | Gossip/memberlist port + liveness thresholds |
| `discovery` | Auto-discovery configuration (`kubernetes`, `dns`, `udp`, `consul`, `etcd`, `mdns`) |

Full documentation: [Cluster](/deployment-guides/config-json/cluster).

***

## `config_store`, `logs_store`, `vector_store`

Storage backends. Each has `enabled` (boolean), `type` (string), and `config` (object).

| Store | Types |
| - | - |
| `config_store` | `"sqlite"`, `"postgres"` |
| `logs_store` | `"sqlite"`, `"postgres"`, `"clickhouse"` (+ optional `object_storage` for LLM and MCP logs) |
| `vector_store` | `"weaviate"`, `"redis"`, `"qdrant"`, `"pinecone"` (`"redis"` also covers Valkey-compatible endpoints) |

Full documentation: [Storage](/deployment-guides/config-json/storage).

***

## `framework`

Controls model pricing catalog sync and background model discovery:

```json theme={null}
{
  "framework": {
    "pricing": {
      "pricing_url": "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
      "pricing_sync_interval": 86400,
      "live_models_sync_interval": 3600
    }
  }
}
```

| Field | Default | Description |
| - | - | - |
| `pricing.pricing_url` | LiteLLM catalog | URL of a model pricing JSON file |
| `pricing.pricing_sync_interval` | `86400` | Sync interval in seconds (minimum: `3600`) |
| `pricing.live_models_sync_interval` | `3600` | How often each provider's model list is re-fetched, in seconds. `0` disables it (minimum when enabled: `60`) |

### Background model discovery

Each provider's model list is fetched at startup and whenever you add, edit, or
delete a key. `live_models_sync_interval` additionally re-fetches it on a timer,
so a model a provider starts serving after the gateway booted becomes routable
without a restart.

Every node runs its own refresh, because the model list is cached in process
memory rather than in the database. Each pass costs two `list models` calls per
enabled key, per provider, so raise the interval if a provider meters that
endpoint. The interval is jittered by ±10% to keep replicas that booted together
from calling every upstream at the same instant.

Set it to `0` to turn the timer off entirely. Model discovery then happens only
at startup and on key edits, and you can trigger it on demand from the
**Providers** page.

***

## `mcp`

Declares the catalog of MCP servers Bifrost connects to. Each entry in `client_configs` is one MCP server.

```json theme={null}
{
  "mcp": {
    "client_configs": [
      {
        "name": "weather",
        "connection_type": "http",
        "connection_string": "https://mcp.example.com/weather",
        "auth_type": "none",
        "tools_to_execute": ["*"]
      }
    ]
  }
}
```

Common fields on each `client_configs` entry:

| Field | Type | Description |
| - | - | - |
| `name` | string | Unique display name |
| `client_id` | string | Optional stable client identifier (defaults to a generated UUID) |
| `connection_type` | string | `"http"`, `"sse"`, or `"stdio"` |
| `connection_string` | object/string | HTTP/SSE URL or stdio command spec |
| `auth_type` | string | `"none"`, `"headers"`, `"oauth"`, `"per_user_oauth"`, `"per_user_headers"`, or `"token_exchange"` (enterprise only — see below) |
| `tools_to_execute` | array | Allow-list of tool names; `["*"]` for all |
| `tools_to_auto_execute` | array | Subset of `tools_to_execute` that runs without user confirmation |
| `headers` | object | Static admin headers (used by `headers` and as additions on `per_user_headers`) |
| `is_code_mode_client` | boolean | Wrap tools as Python code-mode helpers instead of raw tool calls |
| `needs_session_stickiness` | boolean | HTTP-only, and only meaningful for `oauth`/`headers`/`none` (per-user auth types are always per-call). `true` holds one persistent upstream connection reused for every tool call; `false`/omitted (default) dials fresh per tool call. Cannot be `false` for `connection_type` `"sse"`/`"stdio"` — both are always sticky. See [Session Stickiness](/mcp/connecting-to-servers#session-stickiness-http-only). |
| `is_ping_available` | boolean | Default `true`. Whether the MCP server supports a lightweight ping for health checks; `false` falls back to a full `listTools` call instead. |
| `require_public_target` | boolean | Server-managed, default `false`. Set to `true` for HTTP/SSE clients registered over the management API without an admin credential check; every network connection to the MCP server (initial, reconnect, per-call) must then resolve to a public address. It does not apply to stdio or in-process clients, which have no network target (a stdio client cannot be registered without an admin credential at all). Once stored as `true` it is never cleared by the API or by config.json reconciliation; declaring it `true` here is honored. |
| `tool_sync_interval` | string \| integer | Per-client tool-list sync interval as a Go duration string in whole seconds (for example `"5m"` or `"90s"`), or a legacy non-negative integer in nanoseconds that is a whole number of seconds. `"0s"`/omitted falls back to the global `mcp.tool_sync_interval`. |
| `tool_execution_timeout` | integer | Per-client tool execution timeout in seconds. `0`/omitted falls back to the global `client.mcp_tool_execution_timeout`. |
| `allow_by_default` | boolean | When `true`, any caller can use this client without an explicit assignment, with all tools allowed. An explicit assignment for a caller takes precedence for that caller, including an empty tool list. |
| `allow_on_all_virtual_keys` | boolean | Deprecated alias of `allow_by_default`, read only when `allow_by_default` is absent. |
| `tls_config` | object | `{ "insecure_skip_verify": bool, "ca_cert_pem": string }` — skip TLS verification (development only) and/or trust a custom CA certificate for this client's connection. `ca_cert_pem` supports `env.VAR_NAME`. |

Auth-type-specific fields:

| Field | Auth type | Description |
| - | - | - |
| `oauth_config` | `oauth`, `per_user_oauth` | Optional inline OAuth provider block. The whole block can be omitted, and any inner field (`client_id`, `client_secret`, `authorize_url`, `token_url`, `registration_url`, `scopes`) can be omitted individually — RFC 8414 metadata discovery + RFC 7591 dynamic client registration fill the gaps off `connection_string` at admin-click time. `client_id` / `client_secret` support `env.VAR_NAME` and `vault.path` references (resolved at runtime, reference stored); the other fields take literal values (encrypted at rest, redacted in API responses). |
| `per_user_header_keys` | `per_user_headers` | Required, non-empty. Array of header names each end-user must supply. |
| `token_exchange` | `token_exchange` | Required. `{ "audience": string, "use_idp_credentials": boolean (optional, default false), "client_id": SecretVar (required unless use_idp_credentials is true), "client_secret": SecretVar (optional, public clients; ignored when use_idp_credentials is true), "scopes": string[] (optional), "authorization_server_url": string (optional) }`. `client_id`/`client_secret` support `env.VAR_NAME`/`vault.path` references. `use_idp_credentials` performs the exchange as the SSO login application instead of a dedicated one — required for Microsoft Entra ID, see [Token Exchange auth](../../mcp/auth/token-exchange#prerequisites). Include `"offline_access"` in `scopes` where the identity provider supports it to keep the retained admin discovery credential self-renewing instead of expiring into `needs_reauth`. |

The schema enforces these pairings: `oauth_config` is rejected on non-OAuth auth types, `per_user_header_keys` is rejected on any auth type other than `per_user_headers`, and `token_exchange` is rejected on any auth type other than `token_exchange` — a misplaced block fails `$schema` validation instead of being silently ignored.

<Warning>
  **Enterprise only:** `auth_type: "token_exchange"` in `config.json` is rejected on OSS — the client is skipped entirely at boot with an error logged naming it. Declare `token_exchange` clients via the API/Web UI on an enterprise deployment instead if you need them in a non-enterprise `config.json` environment during a migration.
</Warning>

<Warning>
  **Migration note:** `oauth_config_id` is no longer a valid field on MCP client entries in `config.json`. Older guidance for shared OAuth suggested checking it in after completing an OAuth flow — remove it from existing config files (declare an `oauth_config` block instead, or leave the client to the dashboard). Bifrost now **ignores** the field if present (with a warning at boot): the OAuth link is managed server-side and survives restarts and config re-syncs on its own, and `$schema`-based editor/CI validation rejects the field.
</Warning>

Clients declared with `auth_type` in `{oauth, per_user_oauth, per_user_headers, token_exchange}` boot into a **`pending_verification`** state. The MCP Gateway UI surfaces an **Authorize** / **Verify** CTA on each pending row; one admin click runs the same verification flow the Web UI Create form uses, after which the client transitions to `healthy`. The same steps are scriptable via `POST /api/mcp/client/{id}/initiate-verification` (OAuth types), `POST /api/mcp/client/{id}/verify-headers` (per-user headers), or `POST /api/mcp/client/{id}/verify-exchange` (token exchange). Verified state is server-side and survives restarts and config re-syncs. Immutable fields (`auth_type`, `connection_type`, `connection_string`, `stdio_config`, `oauth_config`) cannot be changed after creation — file edits to them are ignored with a boot warning naming the fields, matching the update API; delete and re-declare the client to change them. See [MCP Auth](/mcp/auth/overview) and [Connections, States & Lifecycles](/mcp/connections).

### `mcp.virtual_mcps`

Virtual MCPs bundle tools from one or more `client_configs` into a single endpoint served at `/mcp/<endpoint_slug>` and attachable to virtual keys. Reconciled into the config store at load.

```json theme={null}
{
  "mcp": {
    "virtual_mcps": [
      {
        "name": "Support Tools",
        "endpoint_slug": "support-tools",
        "enabled": true,
        "tools": [
          { "mcp_client_name": "zendesk", "tool_names": ["*"] },
          { "mcp_client_name": "docs-search", "tool_names": ["query", "get_page"] }
        ],
        "virtual_key_ids": ["vk-support"]
      }
    ]
  }
}
```

| Field | Type | Description |
| - | - | - |
| `id` | integer | Positive integer (>= 1). When set, the reconciler matches by this ID first and falls back to name when no stored vMCP has that ID; a name match keeps its existing stored ID |
| `name` | string | Required. Display name (unique) |
| `endpoint_slug` | string | Lowercase URL-safe kebab-case (pattern `^[a-z0-9]+(-[a-z0-9]+)*$`) path served at `/mcp/<endpoint_slug>`. Derived from the name when omitted; immutable after creation; unique across vMCPs and direct MCP clients |
| `description` | string | Free text |
| `enabled` | boolean | Defaults to `true`. A disabled vMCP is not served |
| `tools` | array | Required, at least one entry. Each item needs `mcp_client_id` or `mcp_client_name`, plus `tool_names` (`["*"]` = all current and future tools, `[]` = none) |
| `virtual_key_ids` | string\[] | Virtual keys the vMCP is attached to |

When `source_of_truth` is `config.json` and `mcp.virtual_mcps` is present, it is authoritative for vMCPs: any stored vMCP absent from it is removed, and an explicit `"virtual_mcps": []` prunes them all. Omitting `mcp.virtual_mcps` leaves stored vMCPs unchanged. In `split` mode the file creates and updates vMCPs but never prunes runtime-managed ones. See [Source of Truth](/deployment-guides/config-json/source-of-truth).

<Note>
  `mcp.tool_groups` is the deprecated former name for this key, kept for backward compatibility. It carries legacy attachment arrays (`team_ids`, `customer_ids`, `user_ids`, `provider_names`, `api_key_ids`) and does not expose `endpoint_slug`. Prefer `mcp.virtual_mcps`; when both are present, `mcp.virtual_mcps` wins and `mcp.tool_groups` is ignored.
</Note>

***

## `websocket`

Optional tuning for the WebSocket gateway (Responses API WebSocket mode, Realtime API). WebSocket is always enabled.

```json theme={null}
{
  "websocket": {
    "max_connections_per_user": 100,
    "transcript_buffer_size": 100,
    "pool": {
      "max_idle_per_key": 50,
      "max_total_connections": 1000,
      "idle_timeout_seconds": 600,
      "max_connection_lifetime_seconds": 7200
    }
  }
}
```

| Field | Default | Description |
| - | - | - |
| `max_connections_per_user` | `100` | Max concurrent WebSocket connections per user |
| `transcript_buffer_size` | `100` | Transcript entries buffered for Realtime API mid-session fallback |
| `pool.max_idle_per_key` | `50` | Max idle upstream connections per provider/key |
| `pool.max_total_connections` | `1000` | Max total idle upstream connections |
| `pool.idle_timeout_seconds` | `600` | Evict idle connections after this many seconds |
| `pool.max_connection_lifetime_seconds` | `7200` | Max lifetime of any upstream connection |

***

## Minimal Valid Config

```json theme={null}
{
  "$schema": "https://www.getbifrost.ai/schema",
  "encryption_key": "env.BIFROST_ENCRYPTION_KEY",
  "providers": {
    "openai": {
      "keys": [
        { "name": "primary", "value": "env.OPENAI_API_KEY", "models": ["*"], "weight": 1.0 }
      ]
    }
  },
  "config_store": { "enabled": false }
}
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.