From 2627ec26faf78bc15d69fae5e960c5f6d465c6e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vi=CC=81ctor=20Falco=CC=81n?= Date: Fri, 17 Jul 2026 17:46:40 +0200 Subject: [PATCH] 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). --- config/mcp.php | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++ routes/ai.php | 25 +++++++++++++++++++---- 2 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 config/mcp.php diff --git a/config/mcp.php b/config/mcp.php new file mode 100644 index 00000000..ca0052a0 --- /dev/null +++ b/config/mcp.php @@ -0,0 +1,55 @@ + [ + // 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, + +]; diff --git a/routes/ai.php b/routes/ai.php index a86b053a..64055af6 100644 --- a/routes/ai.php +++ b/routes/ai.php @@ -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']);