trends.stumason.dev
TypeScript
Pull Request Opened
PR #165 opened: MCP auth: OAuth 2.1 via Passport (replaces static token)
Why
The static bearer token was a dead end — claude.ai/Cursor/VS Code remote connectors authenticate via OAuth (discovery + dynamic client registration), not pasted secrets. This wires real OAuth 2.1 on the existing [email protected] account.
How it fits together
- Passport = the OAuth2 authorization server (
oauth/authorize,oauth/token— auto-registered). - laravel/mcp = the MCP-side glue:
Mcp::oauthRoutes()publishes.well-known/oauth-*discovery +oauth/register(dynamic client registration); its built-inAddWwwAuthenticateHeaderturns the guard's 401 into an OAuth discovery challenge so clients start the flow automatically. /mcp/devtrendsis now guarded byauth:api(Passport). StaticMcpAuthenticate+devtrends.mcp_tokendeleted.
Changes
composer require laravel/passport(13.7) + published oauth_* migrations.UserimplementsOAuthenticatable+HasApiTokens; newapipassport guard.routes/mcp.phprewired;config/mcp.phpallowsclaude/cursor/vscodeschemes.- Fortify public registration disabled (single private account).
- CI gets a
passport:keysstep.
⚠️ Deploy runbook (required — OAuth won't work without this)
- Generate keys once and set them as Coolify env vars (they must persist across container rebuilds — Passport reads them from env):
Then in Coolify, set:php artisan passport:keys # generates storage/oauth-private.key + oauth-public.keyPASSPORT_PRIVATE_KEY= full contents ofoauth-private.key(multiline, incl. BEGIN/END lines)PASSPORT_PUBLIC_KEY= full contents ofoauth-public.key
- Migrations run automatically on deploy (
AUTO_MIGRATE=true) — creates theoauth_*tables. - Ensure
APP_URL=https://trends.stumason.dev(the OAuth issuer/discovery URLs derive from it). - Reconnect the client to
https://trends.stumason.dev/mcp/devtrends— it'll discover OAuth, send you to log in (your account), you consent, done. No pasted secrets.
Verification
- 142 tests pass on Postgres: unauthed MCP → 401 +
WWW-Authenticatediscovery;.well-knownmetadata points at Passport; authed user passes the guard. route:listshows the full chain:.well-known/oauth-*→oauth/register→oauth/authorize+oauth/token→ guardedmcp/devtrends.
Note
Keys are gitignored (/storage/*.key) — never committed. Generate prod keys fresh; don't reuse dev keys.
+1356
additions
-235
deletions
22
files changed