> ## Documentation Index
> Fetch the complete documentation index at: https://wundergraphinc-milinda-router-80-entity-caching-base-implem.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# OAuth Configuration Reference

> Complete reference for all MCP OAuth configuration options, JWKS settings, environment variables, error responses, and RFC 9728 metadata.

## Configuration Options

| Option                                       | Description                                                                                                                                                                                                                                                          | Default |
| -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| `oauth.enabled`                              | Enable OAuth 2.1 / JWKS-based authentication for the MCP server                                                                                                                                                                                                      | `false` |
| `oauth.authorization_server_urls`            | List of base URLs of OAuth 2.0 authorization servers. Advertised via the [RFC 9728 metadata endpoint](#rfc-9728-protected-resource-metadata) so clients can discover authorization endpoints. See [Multiple Authorization Servers](#multiple-authorization-servers). | `[]`    |
| `oauth.authorization_server_url`             | **Deprecated.** Use `authorization_server_urls` instead. Base URL of a single OAuth 2.0 authorization server. The router merges this value into `authorization_server_urls`.                                                                                         | -       |
| `oauth.scope_challenge_include_token_scopes` | When `true`, includes the token's existing scopes in the `scope` parameter of 403 responses. Works around MCP SDK scope accumulation bugs. See [Scope Challenge Behavior](/router/mcp/oauth/scopes#scope-challenge-behavior).                                        | `false` |
| `oauth.max_scope_combinations`               | Maximum scope combinations computed per operation. Raise for schemas with many overlapping `@requiresScopes`.                                                                                                                                                        | `2048`  |
| `oauth.scopes.initialize`                    | Scopes required for **all** HTTP requests (checked before JSON-RPC parsing). This is the baseline scope needed to establish an MCP connection.                                                                                                                       | `[]`    |
| `oauth.scopes.tools_list`                    | Scopes required for the `tools/list` MCP method.                                                                                                                                                                                                                     | `[]`    |
| `oauth.scopes.tools_call`                    | Scopes required for the `tools/call` MCP method (any tool invocation). Per-tool and built-in tool scopes are enforced additively.                                                                                                                                    | `[]`    |
| `oauth.scopes.execute_graphql`               | Scopes required to call the `execute_graphql` built-in tool. Additive to `tools_call`. Only relevant when `enable_arbitrary_operations` is `true`.                                                                                                                   | `[]`    |
| `oauth.scopes.get_operation_info`            | Scopes required to call the `get_operation_info` built-in tool. Additive to `tools_call`.                                                                                                                                                                            | `[]`    |
| `oauth.scopes.get_schema`                    | Scopes required to call the `get_schema` built-in tool. Additive to `tools_call`. Only relevant when `expose_schema` is `true`.                                                                                                                                      | `[]`    |
| `oauth.jwks`                                 | List of JWKS providers for JWT verification. Supports remote JWKS URLs or symmetric secrets.                                                                                                                                                                         | `[]`    |

## JWKS Configuration

The `oauth.jwks` array configures one or more JWKS providers for JWT verification.

### Remote JWKS URL

```yaml theme={null}
oauth:
  jwks:
    - url: 'https://auth.example.com/.well-known/jwks.json'
      audiences:
        - 'https://mcp.example.com'
      algorithms:
        - 'RS256'
        - 'ES256'
      refresh_interval: '1m' # How often to refresh the key set
```

| Field              | Description                                                   | Default    |
| ------------------ | ------------------------------------------------------------- | ---------- |
| `url`              | URL of the JWKS endpoint                                      | (required) |
| `audiences`        | Allowed JWT `aud` claim values                                | (any)      |
| `algorithms`       | Allowed signing algorithms (RS256, ES256, PS256, EdDSA, etc.) | (all)      |
| `refresh_interval` | How often to refresh the JWKS key set                         | `1m`       |

For the full JWKS configuration reference including all options (`refresh_unknown_kid`, `allowed_use`, etc.), see [Router Authentication](/router/authentication-and-authorization).

### Symmetric Secret

For development or testing, you can use a shared symmetric secret instead of a remote JWKS endpoint:

```yaml theme={null}
oauth:
  jwks:
    - secret: 'your-shared-secret'
      symmetric_algorithm: 'HS256' # HS256, HS384, or HS512. For other algorithms, use a remote JWKS URL.
      header_key_id: 'my-key-id'
```

## Multiple Authorization Servers

The router can trust more than one OAuth 2.0 authorization server. Use `authorization_server_urls` to list all trusted servers. Configure a JWKS entry for each issuer so the router can validate its tokens.

```yaml theme={null}
oauth:
  enabled: true
  authorization_server_urls:
    - 'https://auth-a.example.com'
    - 'https://auth-b.example.com'
  jwks:
    - url: 'https://auth-a.example.com/.well-known/jwks.json'
    - url: 'https://auth-b.example.com/.well-known/jwks.json'
```

The router validates an incoming token against every configured JWKS provider. A token is accepted when any provider validates it.

All configured servers are advertised in the `authorization_servers` field of the [RFC 9728 metadata endpoint](#rfc-9728-protected-resource-metadata). MCP clients pick one of the advertised servers for authorization.

The option `authorization_server_url` is deprecated. Use `authorization_server_urls` for new configurations. Existing configurations with `authorization_server_url` continue to work. When both options are set, the router merges them into one list, with the single URL first, and removes duplicates.

## Environment Variables

| Environment Variable                             | Configuration Path                                      |
| ------------------------------------------------ | ------------------------------------------------------- |
| `MCP_OAUTH_ENABLED`                              | `mcp.oauth.enabled`                                     |
| `MCP_OAUTH_AUTHORIZATION_SERVER_URLS`            | `mcp.oauth.authorization_server_urls` (comma-separated) |
| `MCP_OAUTH_AUTHORIZATION_SERVER_URL`             | `mcp.oauth.authorization_server_url` (deprecated)       |
| `MCP_OAUTH_SCOPE_CHALLENGE_INCLUDE_TOKEN_SCOPES` | `mcp.oauth.scope_challenge_include_token_scopes`        |
| `MCP_OAUTH_MAX_SCOPE_COMBINATIONS`               | `mcp.oauth.max_scope_combinations`                      |

## HTTP Error Responses

### 401 Unauthorized

Returned when the token is missing, invalid, expired, or signature verification fails.

```
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="mcp",
                  scope="mcp:connect",
                  resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource/mcp"
```

The `scope` parameter contains the `initialize` scopes (minimum scopes needed to connect). The `resource_metadata` URL points to the [RFC 9728 metadata endpoint](#rfc-9728-protected-resource-metadata) for OAuth discovery.

### 403 Forbidden

Returned when the token is valid but lacks required scopes. The exact `scope` parameter depends on which level of enforcement rejected the request:

**Method-level rejection** (e.g., missing `tools_call` scopes):

```
HTTP/1.1 403 Forbidden
WWW-Authenticate: Bearer error="insufficient_scope",
                  scope="mcp:tools:execute",
                  resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource/mcp",
                  error_description="missing required scopes: mcp:tools:execute"
```

**Per-tool rejection** (e.g., calling a tool that requires `read:fact`):

```
HTTP/1.1 403 Forbidden
WWW-Authenticate: Bearer error="insufficient_scope",
                  scope="read:fact",
                  resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource/mcp",
                  error_description="insufficient scopes for tool execute_operation_get_top_secret_facts"
```

The `scope` parameter always contains only the scopes needed for the specific operation that failed (unless `scope_challenge_include_token_scopes` is enabled).

<Info>
  Per the MCP specification, HTTP-level authentication failures return only HTTP status codes and headers - no JSON-RPC
  response body is included.
</Info>

## RFC 9728 Protected Resource Metadata

When OAuth is enabled and at least one authorization server is configured (`authorization_server_url` or `authorization_server_urls`), the MCP server exposes a public (unauthenticated) metadata endpoint at:

```
GET /.well-known/oauth-protected-resource/mcp
```

This follows the [RFC 9728](https://datatracker.ietf.org/doc/rfc9728/) path-aware format. MCP clients use this endpoint to automatically discover the authorization server and all supported scopes.

**Example response:**

```json theme={null}
{
  "resource": "https://mcp.example.com",
  "authorization_servers": ["https://auth.example.com"],
  "bearer_methods_supported": ["header"],
  "resource_documentation": "https://mcp.example.com/mcp",
  "scopes_supported": [
    "mcp:connect",
    "mcp:tools:execute",
    "mcp:tools:read",
    "read:all",
    "read:employee",
    "read:fact",
    "read:private"
  ]
}
```

The `scopes_supported` field is **automatically computed** as the union of:

* All configured static scopes (`initialize`, `tools_list`, `tools_call`)
* All scopes extracted from `@requiresScopes` directives on fields used by registered operations

## Startup Validation

The router performs startup validation when OAuth is enabled:

* If `oauth.jwks` is empty, the router exits with a fatal error to prevent starting an unprotected endpoint
* If `server.base_url` is empty, the router exits with a fatal error because it is required for [RFC 9728](https://datatracker.ietf.org/doc/rfc9728/) metadata discovery

## Full Configuration Example

```yaml theme={null}
mcp:
  enabled: true
  server:
    listen_addr: '0.0.0.0:5025'
    base_url: 'https://mcp.example.com'
  graph_name: 'my-graph'
  exclude_mutations: true
  enable_arbitrary_operations: true # Enables execute_graphql tool
  expose_schema: true # Enables get_schema tool
  oauth:
    enabled: true
    authorization_server_urls:
      - 'https://auth.example.com'
    scope_challenge_include_token_scopes: false # Set to true for MCP clients with scope accumulation bugs
    scopes:
      initialize:
        - 'mcp:connect'
      tools_list:
        - 'mcp:tools:read'
      tools_call:
        - 'mcp:tools:execute'
      # Built-in tool scopes (additive to tools_call)
      execute_graphql:
        - 'mcp:graphql:execute'
      get_schema:
        - 'mcp:schema:read'
      get_operation_info:
        - 'mcp:ops:read'
    jwks:
      - url: 'https://auth.example.com/.well-known/jwks.json'
        audiences:
          - 'https://mcp.example.com'
        algorithms:
          - 'RS256'
        refresh_interval: '1m'
  storage:
    provider_id: 'mcp'
  session:
    stateless: true

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