Skip to content

[Server] Expose OAuth protected resource metadata as a reusable request handler#391

Open
chr-hertel wants to merge 3 commits into
mainfrom
oauth-metadata-request-handler
Open

[Server] Expose OAuth protected resource metadata as a reusable request handler#391
chr-hertel wants to merge 3 commits into
mainfrom
oauth-metadata-request-handler

Conversation

@chr-hertel

@chr-hertel chr-hertel commented Jul 5, 2026

Copy link
Copy Markdown
Member

Why

The SDK's OAuth "middleware" like ProtectedResourceMetadataMiddleware isn't really cross-cutting middleware. It self-selects on GET /.well-known/oauth-protected-resource, short-circuits, and never participates in the JSON-RPC flow — it's an endpoint controller wearing middleware clothing. That makes it awkward to reuse in Symfony/Laravel, which express endpoints as callable controllers rather than PSR-15 middleware.

The bridge already sits in the dependency tree: PSR-15's RequestHandlerInterface::handle(ServerRequestInterface): ResponseInterface is structurally identical to a framework callable controller. The framework router decides when; the handler decides what.

What

Scoped to src/Server/Transport/Http only:

  • New ProtectedResourceMetadataHandler — the RFC 9728 endpoint as a plain, transport-neutral PSR-15 RequestHandlerInterface. Zero new dependencies.
  • ProtectedResourceMetadataMiddleware is now a thin path-guard adapter that delegates to it. Public constructor unchanged — fully backward compatible.
  • Tests — new ProtectedResourceMetadataHandlerTest; the existing middleware test still covers delegation + pass-through.

The same shape applies to ClientRegistrationMiddleware and OAuthProxyMiddleware as a follow-up.

Using it from a framework

Because the handler is just handle(ServerRequestInterface): ResponseInterface, you can mount it directly on a framework route instead of routing the well-known GET through the MCP transport.

Symfony (constructor-inject the handler; bridge with symfony/psr-http-message-bridge):

use Mcp\Server\Transport\Http\OAuth\ProtectedResourceMetadataHandler;
use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory;
use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory;
use Symfony\Component\HttpFoundation\{Request, Response};
use Symfony\Component\Routing\Attribute\Route;

final class MetadataController
{
    public function __construct(private readonly ProtectedResourceMetadataHandler $handler) {}

    #[Route('/.well-known/oauth-protected-resource', methods: ['GET'])]
    public function metadata(Request $request): Response
    {
        $psr = (new PsrHttpFactory())->createRequest($request);

        return (new HttpFoundationFactory())->createResponse($this->handler->handle($psr));
    }
}

Laravel (type-hint the PSR-7 request, return the PSR-7 response directly):

use Mcp\Server\Transport\Http\OAuth\ProtectedResourceMetadataHandler;
use Psr\Http\Message\{ResponseInterface, ServerRequestInterface};

// Route::get('/.well-known/oauth-protected-resource', [MetadataController::class, 'metadata']);
final class MetadataController
{
    public function __construct(private readonly ProtectedResourceMetadataHandler $handler) {}

    public function metadata(ServerRequestInterface $request): ResponseInterface
    {
        return $this->handler->handle($request);
    }
}

Verification

  • phpunit --testsuite unit → green; phpstan clean; php-cs-fixer --dry-run clean.

@chr-hertel chr-hertel added this to the 0.7.0 milestone Jul 5, 2026
@chr-hertel chr-hertel marked this pull request as ready for review July 5, 2026 23:23
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.

1 participant