omouren/mcp-oauth-bundle
A PHP bundle that adds OAuth support to Model Context Protocol (MCP) servers, providing configurable authorization endpoints and token handling to help secure MCP clients without building OAuth plumbing from scratch.
Read the repo before the pitch. The description claims "Laravel-native configuration and middleware" and "Artisan commands" — the repository itself is a Symfony bundle (omouren/mcp-oauth-bundle) built on top of mcp-bundle and league/oauth2-server-bundle. There is no vendor:publish, no service provider, no Artisan command. If you are a Laravel developer, the honest framing is: this package is a reference implementation of the MCP OAuth specs that you will either (a) run as a Symfony service next to your Laravel app, or (b) read as a conformance checklist and reimplement in Laravel on Passport.
Where to look first, in order:
README.md — the only prose that exists; expect it to be short given 2 stars.DependencyInjection/Configuration.php — the Symfony config tree is the real documentation. Every knob the bundle accepts is declared here, and the 1.2 release ("add login redirect query parameter in config") confirms that config changes are the primary API surface.Resources/config/routing*.yaml (or route attributes) — this tells you which endpoints the bundle actually adds: the /.well-known/oauth-authorization-server and /.well-known/oauth-protected-resource discovery documents, and the authorize/token/registration routes.Minimal setup (Symfony side):
composer require omouren/mcp-oauth-bundle
// config/bundles.php
return [
// ...
Omouren\McpOAuthBundle\McpOAuthBundle::class => ['all' => true],
];
# config/packages/mcp_oauth.yaml (keys: confirm against Configuration.php)
mcp_oauth:
issuer: '%env(MCP_ISSUER)%'
login_redirect: { enabled: true, query_parameter: 'redirect' }
Then point an MCP client (MCP Inspector is the fastest loop) at your MCP endpoint and watch it walk discovery → authorize → token → authenticated call.
First use case for a Laravel developer: rather than adopting the bundle, serve the discovery document from your Laravel app and use the bundle's output as the shape you must match.
Route::get('/.well-known/oauth-protected-resource', fn () => [
'resource' => config('app.url').'/mcp',
'authorization_servers' => [config('app.url')],
'bearer_methods_supported' => ['header'],
]);
Discovery-as-contract. Treat the /.well-known JSON as an interface you can snapshot in tests. Http::get('/.well-known/oauth-protected-resource')->assertJsonPath('resource', ...) catches accidental issuer/host drift, which is the most common breakage.
Two topologies, pick deliberately.
league/oauth2-server under the hood — same engine, different glue). Tokens are validated on the Symfony side by introspection or JWKS.Bearer middleware on MCP routes is where day-to-day work actually lives:
Route::middleware(['auth.bearer:mcp.read'])->post('/mcp', McpController::class);
The middleware must do four things, in order: resolve the token, check exp, check that the token's audience/resource claim exactly equals the MCP server URL, then check scopes. Returning 401 with WWW-Authenticate: Bearer resource_metadata="https://…/.well-known/oauth-protected-resource" is what makes clients auto-discover instead of dead-ending.
Resource-bound tokens (RFC 8707). One token per MCP server. If you front two MCP servers, issue two tokens — never let a token minted for server A pass validation at server B. Enforce it on the authorization request (resource param) and on validation; the bundle's enforcement is only useful if you don't hand out a broad token elsewhere.
CIMD (Client ID Metadata Document). Clients identify themselves with an HTTPS URL to a JSON metadata document rather than a pre-registered client_id. For a Laravel AS this means: fetch the URL with a short timeout, cache it, allowlist hosts, and reject redirects to private address space (classic SSRF). This is the piece that needs the most of your security review time.
Stage the rollout so failures are isolatable: discovery only → discovery + validation → enable CIMD → enforce resource binding. Enabling all four at once means every 401 is a guessing game.
php artisan vendor:publish for this package is hallucinated (including the login-redirect assessment in circulation). Verify against composer.json and the config tree.redirect value as hostile input: validate host/scheme, reject protocol-relative //evil.example, prefer relative paths. Never redirect()->to($request->query('redirect')) raw.http://internal-host/..., the client rejects it, and nothing appears in your app log. Check X-Forwarded-* handling before you debug anything else.resource the client requested and the URL it's calling must match character-for-character — trailing slash, port, and scheme included.league/oauth2-server encryption keys must survive deploys or every refresh token dies at once. If your Laravel app rotates them per container, pin them via env/secret.2026-09-02 last release — the API is not stabilized. composer require omouren/mcp-oauth-bundle:1.2.*.services.yaml (resource validator, client repository). Check the bundle's service definitions for the tag/alias before you copy code.WWW-Authenticate header. Never log tokens or full authorize URLs with codes.How can I help you explore Laravel packages today?