Skip to content

[McpBundle] Support acting as an OAuth 2.1 authorization server #2134

@wachterjohannes

Description

@wachterjohannes

Description

MCP's authorization spec lets an MCP server be protected by OAuth 2.1, with the server itself acting as the authorization server (issuing its own tokens) — the flow Claude.ai and similar clients use: dynamic client registration → authorize (PKCE) → token → bearer-protected MCP calls.

Today, making a Symfony MCP server do this requires bolting on a third-party OAuth server (e.g. league/oauth2-server-bundle) alongside mcp/sdk. Since mcp/sdk is gaining the authorization-server primitives, MCP Bundle should expose them via config so any Symfony MCP server can become its own OAuth 2.1 AS without an external IdP — symmetric with how the bundle already wraps the transport, sessions, and PSR bridge.

Proposed approach

A new mcp.oauth config section (opt-in) that wires the SDK engine and exposes the endpoints:

mcp:
    client_transports: { http: true }
    oauth:
        enabled: true
        issuer: '%env(MCP_BASE_URL)%'
        signing_key:
            private_key: '%kernel.project_dir%/config/jwt/private.pem'
        scopes: ['mcp:tools', 'mcp:resources']

Registering: /.well-known/oauth-authorization-server (RFC 8414), /.well-known/oauth-protected-resource (RFC 9728), /.well-known/jwks.json, /oauth/authorize (PKCE), /oauth/token, /oauth/register (RFC 7591).

Batteries-included, all overridable:

  • default resource-owner resolver = the authenticated firewall user becomes the OAuth subject,
  • auto-approve consent,
  • a firewall AccessTokenAuthenticator that validates the bearer JWT and loads the user from sub,
  • Cache-based storage (PSR-16 over a cache pool), reusing the bundle's existing session-store pattern.

This requires mcp/sdk ^0.6 (the version introducing the AS primitives), which also entails a small Profiler\TraceableRegistry update for the revised RegistryInterface.

Related

Opening as a discussion/tracking issue for the bundle change; the draft PR demonstrates the full wiring.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions