Skip to content

[McpBundle] Add OAuth 2.1 authorization server support#2135

Draft
wachterjohannes wants to merge 1 commit into
symfony:mainfrom
wachterjohannes:feature/mcp-oauth-authorization-server
Draft

[McpBundle] Add OAuth 2.1 authorization server support#2135
wachterjohannes wants to merge 1 commit into
symfony:mainfrom
wachterjohannes:feature/mcp-oauth-authorization-server

Conversation

@wachterjohannes
Copy link
Copy Markdown
Contributor

Refs #2134
Depends on modelcontextprotocol/php-sdk#373 (mcp/sdk ^0.6)

Summary

Lets a Symfony MCP server become its own OAuth 2.1 authorization server via config, using the authorization-server primitives from mcp/sdk. It issues and validates its own RS256 JWT access tokens, registers clients (RFC 7591), and serves the discovery documents MCP clients need (RFC 8414/9728 + JWKS) — no external IdP, no third-party OAuth server.

Usage

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']
# security.yaml — protect the MCP endpoint
security:
    firewalls:
        mcp:
            pattern: ^/_mcp
            stateless: true
            custom_authenticators:
                - Symfony\AI\McpBundle\Security\AccessTokenAuthenticator

Registers /.well-known/oauth-authorization-server, /.well-known/oauth-protected-resource, /.well-known/jwks.json, /oauth/authorize, /oauth/token, /oauth/register.

What's in it

  • oauth config section (canBeEnabled) + McpBundle::configureOAuth() wiring the SDK engine, Cache-based storage (Psr16Cache over a cache pool — reusing the session-store pattern), token issuer/validator, DCR registrar, and discovery metadata.
  • Batteries-included defaults, all overridable: SecurityResourceOwnerResolver (the authenticated firewall user becomes the OAuth subject), AutoApproveConsent, and AccessTokenAuthenticator.
  • OAuthController (authorize/token/register/well-known/jwks) bridged Symfony ↔ PSR-7; routes emitted by RouteLoader.

Notes

  • Bumps mcp/sdk to ^0.6 (the version with the AS primitives); this also updates Profiler\TraceableRegistry for the revised RegistryInterface. CI will fail until mcp/sdk 0.6 is released — hence draft.
  • firebase/php-jwt, symfony/cache, symfony/security-bundle are only needed when OAuth is enabled (declared via suggest).
  • DI tests + docs included. Validated end-to-end against a real Sulu MCP server that drops league/oauth2-server in favour of this (container compiles, all OAuth routes resolve, full DCR→authorize→token→bearer flow).

Lets a Symfony MCP server become its own OAuth 2.1 authorization server via
config, using the authorization-server primitives from mcp/sdk. Symmetric with
how the bundle already wraps the transport: it issues and validates its own
RS256 JWT access tokens, registers clients (RFC 7591), and serves the discovery
documents MCP clients need (RFC 8414/9728 + JWKS) — no external IdP.

- New `oauth` config section (canBeEnabled) and `McpBundle::configureOAuth()`
  wiring the SDK engine, Cache-based storage (Psr16Cache over a cache pool,
  reusing the session-store pattern), token issuer/validator, DCR registrar,
  and discovery metadata.
- Batteries-included defaults: SecurityResourceOwnerResolver (firewall user
  becomes the OAuth subject), AutoApproveConsent, and an AccessTokenAuthenticator
  for the firewall — all overridable via config.
- Consolidated OAuthController (authorize/token/register/well-known/jwks) bridged
  Symfony <-> PSR-7; routes emitted by RouteLoader.

Requires mcp/sdk ^0.6, which also updates Profiler\TraceableRegistry for the
revised RegistryInterface. firebase/php-jwt, symfony/cache and
symfony/security-bundle are needed only when OAuth is enabled (suggest).

Docs and DI tests included.
@valtzu
Copy link
Copy Markdown
Contributor

valtzu commented Jun 3, 2026

Very nice!

Would it be reasonable to use the same jwt library Symfony Security is already using? https://github.com/symfony/symfony/blob/8.2/src/Symfony/Component/Security/Http/composer.json#L39

@wachterjohannes
Copy link
Copy Markdown
Contributor Author

@valtzu would make sense! i will evaluate that afer my vacation 🌴

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants