LABSCO SUMMARY
The server is your app, so your app's rules are the rules.
Tools here are PHP running inside the Laravel application itself, not a separate process calling it over an API — a custom tool is a closure with typed parameters, and the model toolkit reads through your app rather than around it. That is the whole appeal, and it is also where the thinking goes: whatever you register is what the client can reach, and on /mcp the middleware in config/loop.php is what stands in front of it. Start on STDIO with --user-id so the session is scoped to a real user, and turn the HTTP route on once you have decided who is allowed to call it. Two rough edges worth knowing before you pick a transport: Streamable HTTP is new enough that not every client speaks it, while SSE — which most clients do speak — is deprecated; and if you serve your local site over https, self-signed certificates will make most clients fail, so use http locally or stay on STDIO.
What it is
A Composer package — composer require kirschbaum-development/laravel-loop — that turns an existing Laravel application into an MCP server. Tools are PHP defined inside that application and registered on the Loop facade from a service provider such as app/Providers/AppServiceProvider; Prism builds them underneath. It speaks STDIO through php artisan loop:mcp:start, and it can serve MCP straight off your own app on two routes: /mcp for Streamable HTTP and /mcp/sse for the deprecated HTTP+SSE transport. It is a beta — the package and its pre-built tools are marked as still in development.
What you get
- Your models' data, read-only for now —
Kirschbaum\Loop\Toolkits\LaravelModelToolkit, with write operations listed as still to come - Test data made from the client —
Kirschbaum\Loop\Toolkits\LaravelFactoriesToolkitbuilds records through your factories - Stripe —
Kirschbaum\Loop\Tools\StripeToolcalls the Stripe API - Filament — a separate package,
kirschbaum-development/laravel-loop-filament, registered asLoop::toolkit(Kirschbaum\Loop\Filament\FilamentToolkit::make()) - Your own tool without writing a class —
CustomTool::make(name:, description:)chained withwithStringParameter/withNumberParameterand ausing()closure; the parameter types come from Prism - Your own tool as a class — implement the
Toolcontract, return aPrism\Prism\Toolfrombuild(); group several by implementingToolkitand returning aToolCollection - Client wiring generated for you —
php artisan loop:mcp:generate-configwalks through the config for Claude Code, Cursor and other clients that take a JSON file - A way to call a tool without a client —
php artisan loop:mcp:callruns it from the terminal so you can read the raw output
Requirements
A Laravel application you can deploy into — supported versions are Laravel 10.x/11.x/12.x — plus Composer, PHP, and php artisan vendor:publish --tag="loop-config" to get config/loop.php. On STDIO the session runs as one of your own users: pass --user-id, and --user-model / --auth-guard when the default guard or model is not what you want; --debug turns on verbose output. The HTTP routes are off until you set LOOP_STREAMABLE_HTTP_ENABLED=true or LOOP_SSE_ENABLED=true (paths move with LOOP_STREAMABLE_HTTP_PATH and LOOP_SSE_PATH), and each one is fronted by the middleware in config/loop.php — auth:sanctum is the shipped default, and clients then send an Authorization: Bearer header. SSE keeps session state through LOOP_SSE_DRIVER, file by default with session_ttl at 86400 seconds; redis is the recommended driver in production, to avoid file locking. Node.js (> 20) comes in only when your client cannot open an SSE connection itself and you bridge through the mcp-remote package.
Setup effort
One command — composer require kirschbaum-development/laravel-loop
USE CASES
01
Asking questions about the data already in your app
Register the model toolkit, start the server as a specific user, and the client reads your models' data through your application. Writes are not there yet.
02
Setting up a scenario before you debug it
Have the client build the records through your existing factories instead of hand-writing a seeder for a one-off reproduction.
03
Exposing one piece of business logic without writing a class
Name it, describe it, declare its parameters and hand it a closure — registered in a service provider in a few lines.
04
Letting an assistant work through your Filament panel
Install the companion package and register its toolkit; the client then works against your Filament resources rather than raw tables.
05
Connecting a hosted app instead of a local PHP process
Enable the HTTP route, put your own middleware in front of it, and point the client at the URL with a bearer token — no PHP or Node running on the client's machine.
06
Working out why a tool you wrote returns the wrong thing
Call it straight from the terminal to see the real output, and read your application log — a closed connection usually means an error inside the app.
INSTALLATION
Connect your client
Server setup
TERMINAL
composer require kirschbaum-development/laravel-loopMCP Clients
Claude Code — stdio
CLAUDE CODE — STDIO · TERMINAL
claude mcp add laravel-loop-mcp php <YOUR_FULL_PATH_TO>/laravel/artisan loop:mcp:startClaude Code — SSE
CLAUDE CODE — SSE · TERMINAL
claude mcp add laravel-loop-mcp <YOUR_URL>/mcp/sse -t sse --header "Authorization: Bearer ${AUTH_TOKEN}"Cursor — stdio, Any MCP client — stdio
JSON
{
"mcpServers": {
"laravel-loop-mcp": {
"command": "php",
"args": [
"<YOUR_FULL_PATH_TO>/laravel/artisan",
"loop:mcp:start",
"--user-id=1"
]
}
}
}Any MCP client — SSE
ANY MCP CLIENT — SSE · JSON
{
"mcpServers": {
"laravel-loop-mcp": {
"url": "<YOUR_URL>/mcp/sse",
"headers": {
"Authorization": "Bearer ${AUTH_TOKEN}"
}
}
}
}Enable the route with LOOP_SSE_ENABLED=true; its default auth is auth:sanctum and the bearer token is one your own Laravel app issues.
Values written as ${…} are filled in only by clients that expand variables.
Any MCP client — mcp-remote
ANY MCP CLIENT — MCP-REMOTE · JSON
{
"mcpServers": {
"laravel-loop-mcp": {
"command": "npx",
"args": [
"mcp-remote",
"<YOUR_REMOTE_URL>/mcp",
"--header",
"Authorization: Bearer ${AUTH_TOKEN}"
]
}
}
}Values written as ${…} are filled in only by clients that expand variables.
Any MCP client — generated config
ANY MCP CLIENT — GENERATED CONFIG · TERMINAL
php artisan loop:mcp:generate-config