Connecting Claude Desktop to Your Laravel App via MCP
Set up Laravel's official MCP package with Claude Desktop. All the gotchas around Sanctum tokens, mcp-remote config, and OAuth vs bearer auth.
Connecting Claude Desktop to Your Laravel App via MCP
Laravel has an official MCP package now. You can expose tools from your Laravel app directly to Claude - query your database, trigger actions, whatever you want. And it works with Claude Desktop.
Here's how I set it up for my Progress app, including all the gotchas I hit along the way.
The Laravel Side
Install the package:
composer require laravel/mcp
php artisan vendor:publish --tag=ai-routes
Create a server class:
php artisan make:mcp-server ProgressServer
This lives in app/Mcp/Servers/. Define your tools, resources, and prompts:
class ProgressServer extends Server
{
protected string $name = 'Progress';
protected string $version = '1.0.0';
protected string $instructions = <<<'MARKDOWN'
Use these tools to query activities and manage articles.
MARKDOWN;
protected array $tools = [
ListActivitiesTool::class,
CreateArticleTool::class,
// ... more tools
];
}
Register it in routes/ai.php:
use Laravel\Mcp\Facades\Mcp;
Mcp::web('/mcp/progress', ProgressServer::class)
->middleware('auth:sanctum');
That's it for Laravel. Your MCP server is now at /mcp/progress with Sanctum auth.
Getting a Sanctum Token
You need a Sanctum personal access token. The token includes a numeric prefix and pipe character like 3|abc123....
Local development - use Tinker:
$user = User::find(1);
$token = $user->createToken('claude-desktop')->plainTextToken;
// Returns: 3|4fMml9yCz5JvUEiGUnrIG0h1RTaQOUEWTe36O2Vi...
Production - SSH into your server and run Tinker there:
# If using Docker/Coolify:
docker exec YOUR_CONTAINER php artisan tinker --execute="\$user = App\Models\User::find(1); echo \$user->createToken('claude-desktop')->plainTextToken;"
Keep this token safe - it's a bearer token with full access as that user.
Test With Curl First
Before touching Claude Desktop, verify your endpoint works:
curl -s -X POST "https://yourapp.com/mcp/progress" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "Authorization: Bearer YOUR_TOKEN_HERE" \
-d '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"test","version":"0.1.0"}},"id":1}'
You should get a JSON response with serverInfo and capabilities. If you get {"message":"Unauthenticated."}, your token is wrong or expired.
The Claude Desktop Config
Claude Desktop doesn't natively speak HTTP to MCP servers - it expects stdio (local processes). For web-based MCP servers, you need mcp-remote as a bridge.
Edit ~/Library/Application Support/Claude/claude_desktop_config.json on Mac:
{
"mcpServers": {
"my-laravel-app": {
"command": "npx",
"args": [
"mcp-remote@latest",
"https://yourapp.com/mcp/progress",
"--transport",
"http-only",
"--header",
"Authorization:${AUTH_HEADER}"
],
"env": {
"AUTH_HEADER": "Bearer 3|your_actual_token_here"
}
}
}
}
Why This Specific Format?
URL first: mcp-remote expects the URL as the first argument, before any flags.
--transport http-only: Laravel MCP uses streamable HTTP transport. This flag tells mcp-remote to use HTTP directly instead of trying SSE (Server-Sent Events) first.
Token in env var: Sanctum tokens contain a | character which causes shell escaping issues. Putting the full bearer token in an env var and using ${AUTH_HEADER} in the args avoids this problem.
No space after colon: Use Authorization:${AUTH_HEADER} not Authorization: ${AUTH_HEADER}. The space can cause issues with how mcp-remote parses headers.
OAuth vs Sanctum
The MCP package supports both authentication methods:
Passport (OAuth 2.1): Use Mcp::oauthRoutes() to register OAuth discovery endpoints. Use auth:api middleware. MCP clients can use standard OAuth flows. Required if your MCP client only supports OAuth.
Sanctum (Bearer tokens): Just use auth:sanctum middleware. Clients pass the token via Authorization: Bearer header. Use --transport http-only to skip OAuth discovery.
For apps already using Sanctum, stick with it. Adding Passport just for MCP is overkill.
Troubleshooting
Check the logs:
tail -f ~/Library/Logs/Claude/mcp*.log
"Invalid URL" error: The URL must come before flags in the args array.
"OAuth discovery" errors or 404s: Add --transport http-only to skip OAuth. This is required for Sanctum setups without Passport.
401 Unauthorized: Token is wrong, expired, or for the wrong environment. Generate a fresh token on the correct environment (local vs production).
Connection closed immediately: Server not accessible - check HTTPS, firewalls, or try hitting the endpoint with curl first.
Server starts then errors: Check if the token works with curl. If curl works but Claude doesn't, the issue is likely in the mcp-remote config format.
The Tools
Each tool is a class in app/Mcp/Tools/:
class ListActivitiesTool extends Tool
{
protected string $name = 'list-activities';
protected string $description = 'List recent GitHub activities';
public function handle(Request $request): Response
{
$activities = FeedItem::query()
->where('user_id', $request->user()->id)
->latest()
->limit(20)
->get();
return Response::text($activities->toJson());
}
}
You get the authenticated user via $request->user() - Sanctum handles that automatically.
What I'm Using It For
My Progress app tracks GitHub activity. The MCP server lets Claude:
- List my recent activities (PRs, commits, releases)
- Create draft articles from activity summaries
- Publish articles when they're ready
- Link activities to articles
- Get suggested activities for a date range
Instead of opening the app, I can just ask Claude "create a draft article about this week's PRs" and it does it.
Why This Matters
This is your Laravel app as a first-class Claude tool. Not just asking Claude about code - Claude can actually interact with your running application.
Query production data. Trigger jobs. Generate reports. Whatever your app does, Claude can now do it too (with proper auth, obviously).
The official Laravel MCP package makes this surprisingly clean to set up. The main gotcha is the mcp-remote config format - hopefully this saves you the hour I spent debugging token escaping issues.
Get the Friday email
What I shipped this week, what I learned, one useful thing.
No spam. Unsubscribe anytime. Privacy policy.