03-custom-skills/examples/module-mcp/SKILL.md
Add a Laravel MCP server to any project — full domain coverage with dual transport (HTTP + stdio)
npx skillsauth add escapeboy/ai-prompts module:mcpInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Implement a complete Model Context Protocol (MCP) server on any Laravel project. Gives LLMs and AI agents (Claude Code, Codex, Cursor, etc.) full programmatic access to the application's domain.
Reference implementation: Agent Fleet — 61 tools across 14 domains, dual transport, role-based auth.
/module:mcp [action]
/module:mcp # Full implementation (analyze → scaffold → implement)
/module:mcp analyze # Only analyze the project and produce a tool plan
/module:mcp add <domain> # Add MCP tools for a specific domain
/module:mcp sync # Sync tools with current domain — find missing coverage
Analyzes the project, scaffolds the MCP server, and implements all tools.
Process:
Read CLAUDE.md (or README.md) to understand the domain, models, and architecture.
Discover domain models — scan for Eloquent models, their relationships, and fillable fields:
# Find all models
find app -name "*.php" -path "*/Models/*" | head -50
# Or use Laravel Boost if available
php artisan model:show
# Find action classes
find app -name "*Action.php" | head -30
# Find controllers
find app -name "*Controller.php" -path "*/Controllers/*" | head -30
php artisan route:list --columns=method,uri,name,action --json
User domain: User, Role, Team
Product domain: Product, Category, Variant
Order domain: Order, OrderItem, Payment, Refund
Domain: Order (6 tools)
- order_list (read) — List orders with status/date filters
- order_get (read) — Get order details with items
- order_create (write) — Create a new order
- order_update (write) — Update order details
- order_cancel (destructive) — Cancel an order
- order_refund (write) — Initiate a refund
Tool naming convention: {domain}_{action} — lowercase, underscores.
Standard CRUD tools per domain:
{domain}_list — List with filters and pagination{domain}_get — Get single entity by ID with relations{domain}_create — Create new entity{domain}_update — Update existing entity{domain}_delete — Soft-delete or archiveLifecycle tools (for stateful entities):
{domain}_{transition} — e.g. order_cancel, experiment_pauseSystem tools (always include):
dashboard_kpis — Key metrics summarysystem_health — Database, cache, queue healthaudit_log — Recent activity log (if audit exists)laravel/mcp:composer require laravel/mcp
app/Mcp/Servers/{ProjectName}Server.php:<?php
namespace App\Mcp\Servers;
use App\Mcp\Concerns\BootstrapsMcpAuth;
use Laravel\Mcp\Server;
class ProjectNameServer extends Server
{
use BootstrapsMcpAuth;
protected string $name = 'Project Name';
protected string $version = '1.0.0';
protected string $instructions = 'Project Name MCP Server — describe what this project does.';
protected function boot(): void
{
$this->bootstrapMcpAuth();
}
protected array $tools = [
// Tools are registered here — one class per tool
];
}
app/Mcp/Concerns/BootstrapsMcpAuth.php:<?php
namespace App\Mcp\Concerns;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
trait BootstrapsMcpAuth
{
/**
* Bootstrap authentication for stdio MCP sessions.
* HTTP sessions use Sanctum middleware instead.
*/
protected function bootstrapMcpAuth(): void
{
if (auth()->check()) {
return;
}
// Resolve default user for stdio (local CLI) sessions.
// Adapt this to your auth model — find the first admin or owner.
$user = User::where('is_admin', true)->first()
?? User::first();
if (! $user) {
throw new \RuntimeException('No user found. Run your install/seed command first.');
}
Auth::login($user);
// If your app uses team/tenant scoping, set the team context here:
// app()->instance('mcp.active', true);
}
}
routes/ai.php:<?php
use App\Mcp\Servers\ProjectNameServer;
use Laravel\Mcp\Facades\Mcp;
// HTTP/SSE endpoint — for Cursor, remote MCP clients
Mcp::web('/mcp', ProjectNameServer::class)
->middleware(['auth:sanctum']);
// Local stdio server — for Claude Code, Codex
Mcp::local('project-name', ProjectNameServer::class);
bootstrap/app.php or RouteServiceProvider:// In bootstrap/app.php (Laravel 12+)
->withRouting(
// ... existing routes ...
then: function () {
require base_path('routes/ai.php');
},
)
// In your TeamScope (or similar global scope)
public function apply(Builder $builder, Model $model): void
{
// Allow MCP stdio sessions to use the scope
if (app()->runningInConsole() && ! app()->bound('mcp.active')) {
return; // Skip scope in regular console (migrations, seeders)
}
// Apply normal scope
$builder->where($model->getTable().'.team_id', auth()->user()?->current_team_id);
}
For each tool in the plan, create a class in app/Mcp/Tools/{Domain}/.
Read tool pattern (list):
<?php
namespace App\Mcp\Tools\Order;
use App\Models\Order;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Tool;
use Laravel\Mcp\Server\Tools\Annotations\IsIdempotent;
use Laravel\Mcp\Server\Tools\Annotations\IsReadOnly;
#[IsReadOnly]
#[IsIdempotent]
class OrderListTool extends Tool
{
protected string $name = 'order_list';
protected string $description = 'List orders with optional status and date filters. Returns id, number, status, total, created_at.';
public function schema(JsonSchema $schema): array
{
return [
'status' => $schema->string()
->description('Filter by status: pending, processing, completed, cancelled')
->enum(['pending', 'processing', 'completed', 'cancelled']),
'limit' => $schema->integer()
->description('Max results (default 10, max 100)')
->default(10),
];
}
public function handle(Request $request): Response
{
$query = Order::query()->orderByDesc('created_at');
if ($status = $request->get('status')) {
$query->where('status', $status);
}
$limit = min((int) ($request->get('limit', 10)), 100);
$items = $query->limit($limit)->get(['id', 'order_number', 'status', 'total', 'created_at']);
return Response::text(json_encode([
'count' => $items->count(),
'orders' => $items->toArray(),
]));
}
}
Read tool pattern (get single):
#[IsReadOnly]
#[IsIdempotent]
class OrderGetTool extends Tool
{
protected string $name = 'order_get';
protected string $description = 'Get order details by ID, including items and payment info.';
public function schema(JsonSchema $schema): array
{
return [
'id' => $schema->string()->description('Order ID (UUID)')->required(),
];
}
public function handle(Request $request): Response
{
$order = Order::with(['items', 'payment'])->find($request->get('id'));
if (! $order) {
return Response::error('Order not found');
}
return Response::text(json_encode($order->toArray()));
}
}
Write tool pattern:
class OrderCreateTool extends Tool
{
protected string $name = 'order_create';
protected string $description = 'Create a new order. Specify customer and items.';
public function schema(JsonSchema $schema): array
{
return [
'customer_id' => $schema->string()->description('Customer ID')->required(),
'items' => $schema->string()->description('JSON array of items: [{product_id, quantity}]')->required(),
'notes' => $schema->string()->description('Order notes'),
];
}
public function handle(Request $request): Response
{
$validated = $request->validate([
'customer_id' => 'required|exists:customers,id',
'items' => 'required|json',
'notes' => 'nullable|string',
]);
try {
// Prefer using existing action classes
$order = app(CreateOrderAction::class)->execute(
customerId: $validated['customer_id'],
items: json_decode($validated['items'], true),
notes: $validated['notes'] ?? null,
);
return Response::text(json_encode([
'success' => true,
'order_id' => $order->id,
'status' => $order->status,
]));
} catch (\Throwable $e) {
return Response::error($e->getMessage());
}
}
}
Destructive tool pattern:
use Laravel\Mcp\Server\Tools\Annotations\IsDestructive;
#[IsDestructive]
class OrderCancelTool extends Tool
{
protected string $name = 'order_cancel';
protected string $description = 'Cancel an order. Only pending/processing orders can be cancelled.';
public function schema(JsonSchema $schema): array
{
return [
'id' => $schema->string()->description('Order ID')->required(),
'reason' => $schema->string()->description('Cancellation reason')->required(),
];
}
public function handle(Request $request): Response
{
$order = Order::find($request->get('id'));
if (! $order) {
return Response::error('Order not found');
}
if (! in_array($order->status, ['pending', 'processing'])) {
return Response::error("Cannot cancel order in '{$order->status}' status");
}
try {
app(CancelOrderAction::class)->execute($order, $request->get('reason'));
return Response::text(json_encode([
'success' => true,
'order_id' => $order->id,
'status' => 'cancelled',
]));
} catch (\Throwable $e) {
return Response::error($e->getMessage());
}
}
}
Key rules for all tools:
handle)#[IsReadOnly] + #[IsIdempotent] for read tools#[IsDestructive] for delete/cancel/archive toolsResponse::text(json_encode(...)) for successResponse::error(...) for failures$request->validate() for write toolsRegister all tools in the Server class $tools array.
Test stdio:
php artisan mcp:start project-name
curl -X POST http://localhost/mcp \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json"
## MCP Server
The platform exposes a Model Context Protocol (MCP) server via `laravel/mcp`.
### Architecture
- **Server:** `app/Mcp/Servers/ProjectNameServer.php`
- **Auth (stdio):** `BootstrapsMcpAuth` trait auto-resolves default user
- **Auth (HTTP):** Sanctum bearer token via `auth:sanctum` middleware
- **Routes:** `routes/ai.php`
### Usage
\`\`\`bash
# Local stdio (for Codex, Claude Code)
php artisan mcp:start project-name
# HTTP/SSE (for Cursor, remote clients)
# POST /mcp with Sanctum bearer token
\`\`\`
### IMPORTANT: When adding/modifying features
When any domain functionality is added or changed, the corresponding MCP tool(s)
must also be created or updated. The MCP server must maintain 100% coverage
of platform capabilities.
analyze)Scans the project and produces a tool plan without implementing anything.
/module:mcp analyze
Output: A markdown table of proposed tools grouped by domain, with tool name, type (read/write/destructive), and description.
add <domain>)Adds MCP tools for a specific domain to an existing MCP server.
/module:mcp add orders
/module:mcp add users
Process:
$tools arraysync)Finds domain functionality that's not covered by MCP tools.
/module:mcp sync
Process:
| Annotation | When to Use | Example |
|------------|-------------|---------|
| #[IsReadOnly] | Tool only reads data | order_list, order_get |
| #[IsIdempotent] | Same input → same result | order_list, order_get |
| #[IsDestructive] | Irreversible action | order_cancel, user_delete |
| (none) | Write that's not destructive | order_create, order_update |
All tools return JSON via Response::text(json_encode(...)).
List tools:
{"count": 5, "orders": [{...}, {...}]}
Get tools:
{"id": "...", "name": "...", "status": "...", "relations": [...]}
Write tools (success):
{"success": true, "order_id": "...", "status": "created"}
Error:
Response::error("Human-readable error message")
After implementation, verify:
composer require laravel/mcp installedBootstrapsMcpAuth trait handles stdio authroutes/ai.php has both Mcp::web() and Mcp::local() routesbootstrap/app.phpmcp.active flag (if applicable)list + get tools{domain}_{action}php artisan mcp:start project-name workstools
Add an AI assistant chat panel to any Laravel project — with tool calling, streaming, and MCP support
development
Search the web for latest Claude API changes, compare findings to existing documentation, and apply targeted updates to keep prompts and configs current. Use when docs are outdated, after a Claude API update, to refresh system prompts, or to validate documentation accuracy.
development
Reduce token usage by selecting a planning strategy, loading Serena memories, enforcing symbol-first exploration, and reporting savings. Use when starting a non-trivial task, asking to reduce token costs, requesting efficient execution, or wanting optimized Claude Code workflows.
development
Initialize Claude Code optimization for a new project — detect stack, create memories, generate constitution, configure settings