Skip to main content

Configuration Options

OptionDescriptionDefault
oauth.enabledEnable OAuth 2.1 / JWKS-based authentication for the MCP serverfalse
oauth.authorization_server_urlBase URL of the OAuth 2.0 authorization server. Advertised via the RFC 9728 metadata endpoint so clients can discover authorization endpoints.-
oauth.scope_challenge_include_token_scopesWhen 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.false
oauth.max_scope_combinationsMaximum scope combinations computed per operation. Raise for schemas with many overlapping @requiresScopes.2048
oauth.scopes.initializeScopes required for all HTTP requests (checked before JSON-RPC parsing). This is the baseline scope needed to establish an MCP connection.[]
oauth.scopes.tools_listScopes required for the tools/list MCP method.[]
oauth.scopes.tools_callScopes required for the tools/call MCP method (any tool invocation). Per-tool and built-in tool scopes are enforced additively.[]
oauth.scopes.execute_graphqlScopes 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_infoScopes required to call the get_operation_info built-in tool. Additive to tools_call.[]
oauth.scopes.get_schemaScopes required to call the get_schema built-in tool. Additive to tools_call. Only relevant when expose_schema is true.[]
oauth.jwksList 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

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
FieldDescriptionDefault
urlURL of the JWKS endpoint(required)
audiencesAllowed JWT aud claim values(any)
algorithmsAllowed signing algorithms (RS256, ES256, PS256, EdDSA, etc.)(all)
refresh_intervalHow often to refresh the JWKS key set1m
For the full JWKS configuration reference including all options (refresh_unknown_kid, allowed_use, etc.), see Router Authentication.

Symmetric Secret

For development or testing, you can use a shared symmetric secret instead of a remote JWKS endpoint:
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'

Environment Variables

Environment VariableConfiguration Path
MCP_OAUTH_ENABLEDmcp.oauth.enabled
MCP_OAUTH_AUTHORIZATION_SERVER_URLmcp.oauth.authorization_server_url
MCP_OAUTH_SCOPE_CHALLENGE_INCLUDE_TOKEN_SCOPESmcp.oauth.scope_challenge_include_token_scopes
MCP_OAUTH_MAX_SCOPE_COMBINATIONSmcp.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 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).
Per the MCP specification, HTTP-level authentication failures return only HTTP status codes and headers - no JSON-RPC response body is included.

RFC 9728 Protected Resource Metadata

When OAuth is enabled and authorization_server_url is configured, the MCP server exposes a public (unauthenticated) metadata endpoint at:
GET /.well-known/oauth-protected-resource/mcp
This follows the RFC 9728 path-aware format. MCP clients use this endpoint to automatically discover the authorization server and all supported scopes. Example response:
{
  "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 metadata discovery

Full Configuration Example

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_url: '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'