> ## Documentation Index
> Fetch the complete documentation index at: https://wundergraphinc-ahmet-router-624-multi-collection-mcp.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Configuration

> Complete reference for all MCP Gateway configuration options, including the mcp.servers map, session handling, storage providers, and environment variables.

<Info>
  This page covers two configuration forms. [Multiple MCP Servers](#running-multiple-mcp-servers) documents
  `mcp.servers`, the current way to configure MCP. The rest of this section documents the single-server, top-level
  options. These options are deprecated. See [Migrating from the Deprecated Top-Level Options](#migrating-from-the-deprecated-top-level-options).
</Info>

## Basic Configuration

To enable MCP in your Cosmo Router, add the following to your `config.yaml`:

```yaml theme={null}
mcp:
  enabled: true
  server:
    listen_addr: 'localhost:5025'
  graph_name: 'my-graph'
  exclude_mutations: true
  storage:
    provider_id: 'mcp'

storage_providers:
  file_system:
    - id: 'mcp'
      path: 'operations'
```

## Configuration Options

| Option                         | Description                                                                                                                                                                                                                                                                  | Default          |
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- |
| `enabled`                      | Enable or disable the MCP server                                                                                                                                                                                                                                             | `false`          |
| `server.listen_addr`           | The address and port where the MCP server will listen for requests                                                                                                                                                                                                           | `localhost:5025` |
| `server.base_url`              | The public base URL of the MCP server. **Required when OAuth is enabled.** Used for the RFC 9728 metadata endpoint and `resource_metadata` in `WWW-Authenticate` headers. Set this to your externally-reachable URL when behind a reverse proxy or load balancer.            | -                |
| `server.version`               | The version reported to MCP clients as the server version in `serverInfo`, alongside the server name derived from `graph_name`. Use this to expose your own API version to AI agents.                                                                                        | Router version   |
| `server.title`                 | A human-readable display name for this MCP server, reported in `serverInfo`. MCP clients show it in UIs, falling back to the machine name derived from `graph_name` when unset.                                                                                              | -                |
| `server.description`           | A human-readable description of this MCP server, reported in `serverInfo`.                                                                                                                                                                                                   | -                |
| `server.discover.instructions` | Natural-language guidance for MCP clients (AI agents) on how to use this server effectively. Served in the `server/discover` response. See [Server Discovery](#server-discovery).                                                                                            | -                |
| `router_url`                   | Custom URL to use for the router GraphQL endpoint in MCP responses. Use this when your router is behind a proxy.                                                                                                                                                             | -                |
| `storage.provider_id`          | The ID of a storage provider to use for loading GraphQL operations. Only `file_system` providers are supported.                                                                                                                                                              | -                |
| `session.stateless`            | Whether the MCP server should operate in stateless mode. When `true`, no server-side session state is maintained between requests.                                                                                                                                           | `true`           |
| `graph_name`                   | The name of the graph this router exposes via MCP. Converted to kebab-case and used to build the MCP server name (`wundergraph-cosmo-<kebab-case-name>`) and for logging; it does not select a different graph. For example, `MyGraph` becomes `wundergraph-cosmo-my-graph`. | `mygraph`        |
| `exclude_mutations`            | Whether to exclude mutation operations from being exposed                                                                                                                                                                                                                    | `false`          |
| `enable_arbitrary_operations`  | Enables the `execute_graphql` built-in tool, allowing clients to run arbitrary GraphQL operations beyond the pre-defined operation set.                                                                                                                                      | `false`          |
| `expose_schema`                | Enables the `get_schema` built-in tool, exposing the full GraphQL schema to MCP clients.                                                                                                                                                                                     | `false`          |
| `omit_tool_name_prefix`        | When enabled, MCP tool names omit the `execute_operation_` prefix. For example, `GetUser` becomes `get_user` instead of `execute_operation_get_user`. See [Tools - Omitting the Tool Name Prefix](/router/mcp/tools#omitting-the-tool-name-prefix).                          | `false`          |

For OAuth-specific configuration, see [OAuth 2.1 Authorization](/router/mcp/oauth/overview).

## Environment Variables

All MCP options can also be set via environment variables:

| Environment Variable               | Configuration Path                 |
| ---------------------------------- | ---------------------------------- |
| `MCP_ENABLED`                      | `mcp.enabled`                      |
| `MCP_SERVER_LISTEN_ADDR`           | `mcp.server.listen_addr`           |
| `MCP_SERVER_BASE_URL`              | `mcp.server.base_url`              |
| `MCP_SERVER_VERSION`               | `mcp.server.version`               |
| `MCP_SERVER_TITLE`                 | `mcp.server.title`                 |
| `MCP_SERVER_DESCRIPTION`           | `mcp.server.description`           |
| `MCP_SERVER_DISCOVER_INSTRUCTIONS` | `mcp.server.discover.instructions` |
| `MCP_ROUTER_URL`                   | `mcp.router_url`                   |
| `MCP_STORAGE_PROVIDER_ID`          | `mcp.storage.provider_id`          |
| `MCP_SESSION_STATELESS`            | `mcp.session.stateless`            |
| `MCP_GRAPH_NAME`                   | `mcp.graph_name`                   |
| `MCP_EXCLUDE_MUTATIONS`            | `mcp.exclude_mutations`            |
| `MCP_ENABLE_ARBITRARY_OPERATIONS`  | `mcp.enable_arbitrary_operations`  |
| `MCP_EXPOSE_SCHEMA`                | `mcp.expose_schema`                |
| `MCP_OMIT_TOOL_NAME_PREFIX`        | `mcp.omit_tool_name_prefix`        |

For OAuth-related environment variables, see [OAuth Configuration Reference](/router/mcp/oauth/configuration#environment-variables).

<Info>
  These environment variables set the deprecated top-level options only. The `mcp.servers` map is YAML-only.
  Environment variables cannot address a map entry. Set every field of a server entry directly in `config.yaml`.
</Info>

## Running Multiple MCP Servers

Use `mcp.servers` to run one or more MCP servers from a single router instance. Each server gets its own:

* Path
* Operations directory
* OAuth configuration (optional)
* Base URL (optional)

`mcp.servers` is a map. Each key names one server. Each value configures that server.

```yaml theme={null}
mcp:
  enabled: true
  server:
    listen_addr: 'localhost:5025'
  servers:
    support:
      enabled: true
      path: '/mcp/support'
      storage:
        provider_id: 'support-ops'
    billing:
      enabled: true
      path: '/billing/mcp'
      base_url: 'https://billing.example.com'
      storage:
        provider_id: 'billing-ops'

storage_providers:
  file_system:
    - id: 'support-ops'
      path: 'operations/support'
    - id: 'billing-ops'
      path: 'operations/billing'
```

`mcp.enabled` must be `true` for any server in `mcp.servers` to start. Set it once, at the top level. When
`mcp.enabled` is `false`, no server starts, whatever the entries say.

Every server in `mcp.servers` shares:

* One listener: `mcp.server.listen_addr`.
* The router's global CORS configuration. See [CORS](#cors).

### Server Entry Fields

| Field                         | Description                                                                                                                                                                                                                             | Default               |
| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- |
| `enabled`                     | Starts this server. A server with no `enabled: true` never starts, even when `mcp.enabled` is `true`.                                                                                                                                   | `false`               |
| `path`                        | The mount path for this server on the shared listener. Required on every entry, enabled or not. See [Path Rules](#path-rules).                                                                                                          | (required)            |
| `base_url`                    | The public base URL for this server. Overrides `mcp.server.base_url`. When this server's `oauth.enabled` is `true`, one of `base_url` or `mcp.server.base_url` must be set. Used for RFC 9728 metadata and `resource_metadata` headers. | `mcp.server.base_url` |
| `graph_name`                  | The name of the graph this server exposes. Feeds the `Name` field of `serverInfo` as `wundergraph-cosmo-<kebab-case graph_name>`.                                                                                                       | the map key           |
| `storage.provider_id`         | The ID of a `file_system` storage provider that holds this server's GraphQL operations.                                                                                                                                                 | -                     |
| `exclude_mutations`           | Excludes mutation operations from this server's tools.                                                                                                                                                                                  | `false`               |
| `enable_arbitrary_operations` | Enables the `execute_graphql` built-in tool for this server.                                                                                                                                                                            | `false`               |
| `expose_schema`               | Enables the `get_schema` built-in tool for this server.                                                                                                                                                                                 | `false`               |
| `omit_tool_name_prefix`       | Removes the `execute_operation_` prefix from this server's tool names.                                                                                                                                                                  | `false`               |
| `session.stateless`           | Runs this server without server-side session state. See the note below.                                                                                                                                                                 | `true`                |
| `resource_documentation`      | A URL to a human-readable page describing this server, its access policies, and how to get started. Included in RFC 9728 metadata if set.                                                                                               | -                     |
| `title`                       | A human-readable display name for this server, reported in `serverInfo`.                                                                                                                                                                | -                     |
| `description`                 | A human-readable description of this server, reported in `serverInfo`.                                                                                                                                                                  | -                     |
| `version`                     | The version this server reports to MCP clients in `serverInfo`.                                                                                                                                                                         | router version        |
| `discover.instructions`       | Natural-language guidance for MCP clients on how to use this server. See [Server Discovery](#server-discovery).                                                                                                                         | -                     |
| `oauth`                       | This server's OAuth configuration. Same fields as the top-level `oauth` block. See [OAuth Configuration Reference](/router/mcp/oauth/configuration).                                                                                    | disabled              |

<Note>
  A field inside an `mcp.servers` entry takes only the value you give it in YAML. It does not read the `MCP_*`
  environment variables documented for the matching top-level option elsewhere on this page. An unset field
  normally takes its Go zero value:

  * `false` for a boolean
  * An empty string
  * `0` for a number
  * `0s` for a duration

  `session.stateless` and `oauth.max_scope_combinations` are exceptions. The router applies their documented
  default when you leave them unset in an `mcp.servers` entry, so they match the deprecated top-level default:

  * `session.stateless` defaults to `true` inside `mcp.servers`, the same as the deprecated top-level
    `mcp.session.stateless`.
  * `oauth.max_scope_combinations` defaults to `2048` inside `mcp.servers`, the same as the top-level default.
</Note>

### Path Rules

Each server's `path` must follow these rules:

* It must start with `/`.
* It must not start with `//`.
* It must not be `/` alone. Every `mcp.servers` entry shares one listener. Go's router treats `/` as a catch-all
  that matches every request, including requests meant for another server. A future release that gives each server
  its own listener can lift this restriction.
* It must not end with `/`.
* It must not contain a wildcard character (`{`, `}`, or `*`).
* It must not start with `/.well-known/oauth-protected-resource`. The router reserves this prefix for OAuth
  metadata.
* It must be unique among **enabled** servers. Two servers can use the same path when at least one of them is
  disabled.

### Failure Isolation

<Warning>
  An unknown `storage.provider_id`, a duplicate path, or any other config error stops the router from starting. Fix
  the reported server before you restart.
</Warning>

An operations directory that the router cannot read affects only its own server. That server starts, serves the
built-in tools, and serves no operation tools. The router logs an error naming the server. Every other server in
`mcp.servers` keeps running.

<Warning>
  A per-server reload failure never fails the router or the config reload. This applies to every reload error,
  including an unreadable operations directory, a scope computation failure, and a tool registration failure. Alert
  on the error log for the affected server. Do not rely on router or config reload failure to detect an MCP
  problem.
</Warning>

When the router reloads its configuration, it also reloads every server in `mcp.servers`. A server whose scope
computation fails during reload keeps its previous tools. The router removes a server's old tools before it
registers the new ones. A tool registration failure during reload can therefore leave that server with fewer tools
than before. Both failures log an error naming the server. The router reload always succeeds, even when one MCP
server fails to reload.

### Multiple Servers with OAuth Behind a Load Balancer

<Warning>
  Without a matching audience, the router accepts a token minted for one server on every other server behind the
  same load balancer. Set `oauth.jwks[].audiences` on every server that enables OAuth to prevent this.
</Warning>

Each server's `base_url` and `path` together determine the resource identifier it publishes and the URL where its
RFC 9728 metadata lives. Take this server:

```yaml theme={null}
mcp:
  servers:
    billing:
      enabled: true
      path: '/billing/mcp'
      base_url: 'https://billing.example.com'
      oauth:
        enabled: true
        authorization_server_url: 'https://auth.example.com'
        jwks:
          - url: 'https://auth.example.com/.well-known/jwks.json'
            audiences:
              - 'https://billing.example.com/billing/mcp'
```

This server publishes:

* Resource identifier: `https://billing.example.com/billing/mcp`
* RFC 9728 metadata: `https://billing.example.com/.well-known/oauth-protected-resource/billing/mcp`

Set `oauth.jwks[].audiences` to the resource identifier, `https://billing.example.com/billing/mcp` in this example.
The router rejects a token whose `aud` claim does not match, even when the token is valid for another server on
the same router.

A load balancer or reverse proxy in front of the router can change the host in these URLs. It must not change the
path. The router derives both URLs from `base_url` and `path`. It does not know the load balancer's own hostname.

## Migrating from the Deprecated Top-Level Options

The top-level `mcp` options documented in [Basic Configuration](#basic-configuration) and
[Configuration Options](#configuration-options) are deprecated. `mcp.server.listen_addr` and `mcp.server.base_url`
are not deprecated. They configure the shared listener and its default base URL for every server.

When `mcp.servers` has one or more entries, the router ignores every deprecated top-level option and logs a warning
naming each one you set:

```
Ignoring deprecated top-level mcp options because mcp.servers is set  ignored_options=["mcp.graph_name", ...]
```

When `mcp.servers` has no entries, the deprecated options build one server on `/mcp`, so an existing config keeps
working unchanged.

The deprecated options are:

* `mcp.graph_name`
* `mcp.storage`
* `mcp.exclude_mutations`
* `mcp.enable_arbitrary_operations`
* `mcp.expose_schema`
* `mcp.omit_tool_name_prefix`
* `mcp.oauth`
* `mcp.router_url`
* `mcp.resource_documentation`
* `mcp.server.title`
* `mcp.server.description`
* `mcp.server.version`
* `mcp.server.discover.instructions`
* `mcp.session`

The router's warning does not name `mcp.session`. `mcp.session.stateless` carries a default of `true`, so the
router cannot tell a value you set from the default. The router still ignores it. `mcp.session.stateless: false`
at the top level has no effect once `mcp.servers` has entries, and the router does not warn you. Set
`session.stateless` on each entry that needs it.

`mcp.router_url` only applies to the deprecated single-server form. It has no effect once `mcp.servers` has
entries. The router uses its own GraphQL endpoint for every server in the map.

<Warning>
  When you do not set `graph_name` explicitly, moving to `mcp.servers` changes the identity your MCP server
  advertises. `graph_name` feeds the `Name` field of
  MCP `serverInfo` as `wundergraph-cosmo-<kebab-case graph_name>`. In the map form, `graph_name` defaults to the map
  key, not to the old top-level `graph_name` value. Some MCP clients store trust or configuration against this
  name. Set `graph_name` explicitly on the entry to keep the name your clients already trust.
</Warning>

**Before**, using the deprecated top-level options:

```yaml theme={null}
mcp:
  enabled: true
  server:
    listen_addr: 'localhost:5025'
  graph_name: 'mygraph'
  exclude_mutations: true
  storage:
    provider_id: 'mcp'

storage_providers:
  file_system:
    - id: 'mcp'
      path: 'operations'
```

**After**, using `mcp.servers`. The entry sets `graph_name: 'mygraph'` explicitly, so `serverInfo` still reports
`wundergraph-cosmo-mygraph`:

```yaml theme={null}
mcp:
  enabled: true
  server:
    listen_addr: 'localhost:5025'
  servers:
    support:
      enabled: true
      path: '/mcp'
      graph_name: 'mygraph'
      exclude_mutations: true
      storage:
        provider_id: 'mcp'

storage_providers:
  file_system:
    - id: 'mcp'
      path: 'operations'
```

## Storage Providers

MCP loads operations from a configured storage provider. Currently, only the `file_system` provider is supported:

```yaml theme={null}
storage_providers:
  file_system:
    - id: 'mcp'
      path: 'operations' # Relative to the router binary
```

Then reference this storage provider in your MCP configuration:

```yaml theme={null}
mcp:
  storage:
    provider_id: 'mcp'
```

A storage provider **must** be specified to load GraphQL operations. See [Storage Providers](/router/storage-providers) for more details on configuring storage providers.

## Server Discovery

The MCP server implements the `server/discover` method. This method arrived in MCP protocol version `2026-07-28` as the successor to the `initialize` handshake. Clients call it to read the supported protocol versions, capabilities, and identity in one stateless request.

The `discover.instructions` option sends natural-language guidance to MCP clients. Use it to tell AI agents how to work with your graph:

```yaml theme={null}
mcp:
  enabled: true
  server:
    discover:
      instructions: |
        Prefer the pre-defined operation tools over execute_graphql.
        Employee data is refreshed nightly; do not treat it as real-time.
```

In `mcp.servers`, set `discover.instructions` on each entry. Each server sends its own instructions and does not
inherit them from another server or from the deprecated `mcp.server.discover.instructions`.

The server sends the instructions to every MCP client. This covers clients that connect with `server/discover` and clients that still use the legacy `initialize` handshake.

The router advertises protocol version `2026-07-28` by default. In session-based mode (`session.stateless: false`), clients negotiate `2025-11-25` or older.

## Session Handling

The MCP server uses the Streamable HTTP transport and maintains per-session state via the `Mcp-Session-Id` header. When deploying multiple Router instances, you need **sticky sessions** to ensure all requests for a session reach the same instance.

To configure sticky sessions:

1. The Router returns a unique `Mcp-Session-Id` response header when a session is established
2. Clients must include that value in subsequent requests as the `Mcp-Session-Id` request header
3. Your load balancer or reverse proxy must route requests with the same `Mcp-Session-Id` to the same instance

For details, see your load balancer or reverse proxy documentation (e.g., [F5 NGINX Plus - MCP Session Affinity](https://community.f5.com/kb/technicalarticles/mcp-session-affinity-with-f5-nginx-plus/341961)).

In `mcp.servers`, sticky sessions apply per server. Each server keeps its own `Mcp-Session-Id` values. Route on
the `Mcp-Session-Id` header together with the request path. This keeps a session for `/mcp/support` from ever
reaching the `/billing/mcp` server.

## CORS

The MCP server automatically configures CORS to allow cross-origin requests from MCP clients. It sets `Access-Control-Allow-Origin: *` and allows the required MCP headers (`Mcp-Protocol-Version`, `Mcp-Session-Id`, `Authorization`, `Last-Event-ID`). The `Mcp-Session-Id` and `WWW-Authenticate` headers are exposed in responses. If you have additional CORS headers configured on the router, they are merged with the MCP-specific headers.

<Info>
  CORS is a property of the shared listener, `mcp.server.listen_addr`. Every server in `mcp.servers` uses the same
  CORS configuration. There is no per-server CORS setting.
</Info>

## Full Configuration Example

This example uses the deprecated top-level options, in the single-server form. For an example with
`mcp.servers`, see [Running Multiple MCP Servers](#running-multiple-mcp-servers).

```yaml theme={null}
mcp:
  enabled: true
  server:
    listen_addr: 'localhost:5025'
  router_url: 'https://your-public-router-url.example.com/graphql'
  graph_name: 'my-graph'
  exclude_mutations: true
  enable_arbitrary_operations: false
  expose_schema: false
  omit_tool_name_prefix: false
  storage:
    provider_id: 'mcp'

storage_providers:
  file_system:
    - id: 'mcp'
      path: 'operations'
```
