Skip to content

feat: add LenientOidcDiscoveryMetadataPolicy and Dynamic Client Registration middleware (RFC 7591)#269

Open
simonchrz wants to merge 2 commits intomodelcontextprotocol:mainfrom
simonchrz:main
Open

feat: add LenientOidcDiscoveryMetadataPolicy and Dynamic Client Registration middleware (RFC 7591)#269
simonchrz wants to merge 2 commits intomodelcontextprotocol:mainfrom
simonchrz:main

Conversation

@simonchrz
Copy link
Contributor

Add two features to the HTTP transport layer:

  1. LenientOidcDiscoveryMetadataPolicy — an alternative OIDC discovery metadata validation policy that does not require
    code_challenge_methods_supported
  2. ClientRegistrationMiddleware — PSR-15 middleware implementing OAuth 2.0 Dynamic Client Registration (RFC 7591)

Motivation and Context

LenientOidcDiscoveryMetadataPolicy: Several identity providers (FusionAuth, Microsoft Entra ID) omit code_challenge_methods_supported from
their OIDC discovery response despite fully supporting PKCE with S256. The existing OidcDiscoveryMetadataPolicy rejects these responses,
making it impossible to use the SDK's OAuth transport with those providers without a custom policy. This provides a ready-made workaround.

ClientRegistrationMiddleware: The MCP specification requires servers to support OAuth 2.0 Dynamic Client Registration (RFC 7591) so that MCP
clients can register themselves automatically. This middleware handles POST /register by delegating to a ClientRegistrarInterface
implementation and enriches /.well-known/oauth-authorization-server responses with the registration_endpoint. The ClientRegistrarInterface
keeps the actual registration logic (e.g. calling FusionAuth, storing in a database) pluggable.

How Has This Been Tested?

  • Unit tests: 16 tests covering both features
    • LenientOidcDiscoveryMetadataPolicyTest (7 cases via data providers): valid metadata with/without code_challenge_methods_supported, missing
      required fields, empty strings, non-array input
    • ClientRegistrationMiddlewareTest (9 cases): successful registration (201), invalid JSON (400), registrar exception (400), metadata
      enrichment with registration_endpoint, Cache-Control preservation, non-200 passthrough, non-matching route passthrough, empty localBaseUrl
      rejection, trailing slash normalization
  • Tested in a real Symfony application against FusionAuth as the identity provider (the motivation for these changes)

Breaking Changes

None. Both features are purely additive — new classes and interfaces only.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the https://modelcontextprotocol.io
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

  • ClientRegistrarInterface::register() accepts and returns array<string, mixed> to stay flexible with RFC 7591's extensible metadata fields
  • ClientRegistrationMiddleware follows the same patterns as OAuthProxyMiddleware (PSR-17 factory discovery, optional DI overrides, anonymous
    handler classes in tests)
  • The registration path (/register) is extracted to a class constant for consistency
  • The middleware rewinds the response stream before reading in enrichAuthServerMetadata to handle cases where the stream cursor was already
    advanced

…allenge_methods_supported

Some identity providers (e.g. FusionAuth, Microsoft Entra ID) omit
code_challenge_methods_supported from their OIDC discovery response
despite supporting PKCE with S256. This policy relaxes the validation
to only require authorization_endpoint, token_endpoint, and jwks_uri.
PSR-15 middleware that handles POST /register by delegating to a
ClientRegistrarInterface and enriches /.well-known/oauth-authorization-server
responses with the registration_endpoint.
@sveneld
Copy link
Contributor

sveneld commented Mar 17, 2026

There is an examples for microsoft connection. There is a trouble not only with OIDC discovery.

https://github.com/modelcontextprotocol/php-sdk/blob/main/examples/server/oauth-microsoft/MicrosoftOidcMetadataPolicy.php
https://github.com/modelcontextprotocol/php-sdk/blob/main/examples/server/oauth-microsoft/MicrosoftJwtTokenValidator.php

Examples should be also updated if alternative OIDC discovery is accepted.

There is also alternative way - in case when code_challenge_methods_supported is missed we can set it's value to default S256

?StreamFactoryInterface $streamFactory = null,
) {
if ('' === trim($localBaseUrl)) {
throw new \InvalidArgumentException('The $localBaseUrl must not be empty.');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use a lib specific exception here instead of a global one: Mcp\Exception\InvalidArgumentException

Suggested change
throw new \InvalidArgumentException('The $localBaseUrl must not be empty.');
throw new InvalidArgumentException('The $localBaseUrl must not be empty.');

/**
* Interface for OAuth 2.0 Dynamic Client Registration (RFC 7591).
*/
interface ClientRegistrarInterface
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you provide a description here, as documentation or reference what is expected here from an interface implementation?

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.

3 participants