feat(mcp): expose an OAuth-authenticated /mcp/oauth endpoint

Register Mcp::oauthRoutes() (RFC 8414/9728 discovery, RFC 7591 DCR, the
mcp:use Passport scope) and a second streamable-HTTP MCP endpoint at
/mcp/oauth guarded by auth:api, so OAuth clients (Claude Desktop/web,
ChatGPT) can connect without a static token. The existing Sanctum /mcp
endpoint is left untouched.

Tighten config/mcp.php redirect_domains to the Claude and ChatGPT hosted
callbacks only (no wildcard).
This commit is contained in:
Víctor Falcón 2026-07-17 17:46:40 +02:00
parent b0e90dc271
commit 2627ec26fa
2 changed files with 76 additions and 4 deletions

55
config/mcp.php Normal file
View File

@ -0,0 +1,55 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Redirect Domains
|--------------------------------------------------------------------------
|
| These domains are the domains that OAuth clients are permitted to use
| for redirect URIs. Each domain should be specified with its scheme
| and host. Domains not in this list will raise validation errors.
|
| An "*" may be used to allow all domains.
|
*/
'redirect_domains' => [
// Anthropic's hosted callback for Claude Desktop / Claude web connectors.
'https://claude.ai',
// OpenAI's hosted callback for ChatGPT connectors.
'https://chatgpt.com',
],
/*
|--------------------------------------------------------------------------
| Allowed Custom Schemes
|--------------------------------------------------------------------------
|
| Native desktop OAuth clients like Cursor and VS Code use private-use URI
| schemes (RFC 8252) for redirect callbacks instead of standard schemes
| like HTTPS. Here, you may list which custom schemes you will allow.
|
*/
'custom_schemes' => [
// 'claude',
// 'cursor',
// 'vscode',
],
/*
|--------------------------------------------------------------------------
| Authorization Server
|--------------------------------------------------------------------------
|
| Here you may configure the OAuth authorization server issuer identifier
| per RFC 8414. This value appears in your protected resource and auth
| server metadata endpoints. When null, this defaults to `url('/')`.
|
*/
'authorization_server' => null,
];

View File

@ -8,12 +8,29 @@ use Laravel\Mcp\Facades\Mcp;
| MCP Routes
|--------------------------------------------------------------------------
|
| Remote (streamable HTTP) MCP server. Authenticated with Sanctum personal
| access tokens carrying an `mcp:read` ability, throttled per user. The
| Pro-plan gate is enforced inside each tool so a lapsed subscription stops
| working without the user having to revoke their token.
| Remote (streamable HTTP) MCP server, exposed on two endpoints:
|
| - `/mcp` is authenticated with Sanctum personal access tokens carrying an
| `mcp:read` ability (Claude Code, static bearer token, read/read_write via
| abilities). Left completely unchanged.
| - `/mcp/oauth` is authenticated with OAuth 2.1 (Authorization Code + PKCE)
| for clients that sign in rather than paste a token Claude Desktop / web
| and ChatGPT connectors. `Mcp::oauthRoutes()` registers the RFC 8414 / 9728
| discovery endpoints, the RFC 7591 DCR endpoint and the `mcp:use` Passport
| scope; the package's AddWwwAuthenticateHeader middleware then turns an
| unauthenticated request into the mandatory 401 bootstrap challenge.
|
| OAuth connections carry only the single `mcp:use` scope, so `tokenCan`
| reports no `mcp:write` ability and the write tools stay read-only for them
| (see WriteTool). The Pro-plan gate is enforced inside each tool under either
| guard, so a lapsed subscription stops working without revoking access.
|
*/
Mcp::oauthRoutes();
Mcp::web('/mcp', WhisperMoneyServer::class)
->middleware(['auth:sanctum', 'abilities:mcp:read', 'throttle:60,1']);
Mcp::web('/mcp/oauth', WhisperMoneyServer::class)
->middleware(['auth:api', 'throttle:60,1']);