# Laravel MCP Laravel MCP is a first-party Laravel package that enables rapid development of MCP (Model Context Protocol) servers for Laravel applications. MCP servers allow AI clients like Claude, Cursor, and other AI-powered tools to interact with your Laravel application through the standardized Model Context Protocol, exposing tools, resources, and prompts that AI agents can discover and use. The package provides a fluent, Laravel-style API for defining MCP primitives (Tools, Resources, and Prompts), handling requests with validation and authentication, returning various response types (text, JSON, images, audio), and testing your MCP servers. It supports both HTTP and stdio transports, OAuth 2.0 authentication via Laravel Passport, streaming responses, structured content with JSON schemas, and autocompletion for prompt arguments. ## Creating an MCP Server Create an MCP server class that registers your tools, resources, and prompts. Servers define the capabilities exposed to AI clients. ```php validate([ 'name' => 'required|string|max:255', 'email' => 'required|email|unique:users,email', 'role' => 'sometimes|string|in:admin,user,editor', ]); $user = \App\Models\User::create([ 'name' => $validated['name'], 'email' => $validated['email'], 'role' => $validated['role'] ?? 'user', 'password' => bcrypt(str()->random(16)), ]); return Response::text("User created successfully with ID: {$user->id}"); } public function schema(JsonSchema $schema): array { return [ 'name' => $schema->string() ->description('The full name of the user') ->required(), 'email' => $schema->string() ->description('The email address for the user account') ->required(), 'role' => $schema->string() ->description('The role to assign: admin, user, or editor') ->enum(['admin', 'user', 'editor']), ]; } } ``` ## Creating a Tool with Output Schema Define an output schema to describe the structured data your tool returns. Use `Response::structured()` for typed responses. ```php get('city'); // Simulate fetching weather data $weatherData = [ 'city' => $city, 'temperature' => 22.5, 'conditions' => 'Partly cloudy', 'humidity' => 65, 'wind_speed' => 12.3, ]; return Response::structured($weatherData); } public function schema(JsonSchema $schema): array { return [ 'city' => $schema->string() ->description('The city name to get weather for') ->required(), ]; } public function outputSchema(JsonSchema $schema): array { return [ 'city' => $schema->string()->description('City name')->required(), 'temperature' => $schema->number()->description('Temperature in Celsius')->required(), 'conditions' => $schema->string()->description('Weather conditions')->required(), 'humidity' => $schema->integer()->description('Humidity percentage'), 'wind_speed' => $schema->number()->description('Wind speed in km/h'), ]; } } ``` ## Creating a Streaming Tool Return a Generator to stream multiple responses with progress notifications. ```php get('items', []); $total = count($items); foreach ($items as $index => $item) { // Process item... sleep(1); // Simulate work $progress = (($index + 1) / $total) * 100; yield Response::notification('stream/progress', [ 'progress' => $progress, 'message' => "Processing item " . ($index + 1) . " of {$total}: {$item}", ]); } yield Response::text("Successfully processed {$total} items."); } public function schema(JsonSchema $schema): array { return [ 'items' => $schema->array() ->items($schema->string()) ->description('List of items to process') ->required(), ]; } } ``` ## Creating a Resource Resources expose data to AI clients. Define a URI and return content when the resource is read. ```php config('app.name'), 'environment' => config('app.env'), 'debug_mode' => config('app.debug'), 'php_version' => PHP_VERSION, 'laravel_version' => app()->version(), 'database_connection' => config('database.default'), 'cache_driver' => config('cache.default'), 'queue_connection' => config('queue.default'), ]; return Response::json($config); } } ``` ## Creating a Resource Template Resource templates use URI patterns with placeholders to expose dynamic content. ```php get('userId'); $user = \App\Models\User::findOrFail($userId); return Response::json([ 'id' => $user->id, 'name' => $user->name, 'email' => $user->email, 'created_at' => $user->created_at->toISOString(), 'orders_count' => $user->orders()->count(), ]); } } ``` ## Creating a Prompt Prompts provide reusable instructions or templates for AI agents. Define arguments that can be passed when the prompt is invoked. ```php get('language'); $focusAreas = $request->get('focus_areas', 'general best practices'); $instructions = << $this->completeProject($value), 'period' => CompletionResponse::match([ 'last-week', 'last-month', 'last-quarter', 'year-to-date', 'all-time', ]), default => CompletionResponse::empty(), }; } protected function completeProject(string $value): CompletionResponse { $projects = \App\Models\Project::pluck('slug')->toArray(); $filtered = CompletionHelper::filterByPrefix($projects, $value); return CompletionResponse::match($filtered); } public function handle(Request $request): Response { $project = $request->get('project'); $period = $request->get('period'); return Response::text("Generate a comprehensive report for project '{$project}' covering the '{$period}' period."); } } ``` ## Response Types The Response class provides multiple factory methods for different content types. ```php 'success', 'count' => 42]); // Structured response with schema validation $structured = Response::structured([ 'user_id' => 123, 'name' => 'John Doe', 'active' => true, ]); // Binary blob response $blob = Response::blob(file_get_contents('/path/to/file.bin')); // Image response (base64 encoded) $image = Response::image( data: file_get_contents('/path/to/image.png'), mimeType: 'image/png' ); // Audio response (base64 encoded) $audio = Response::audio( data: file_get_contents('/path/to/audio.wav'), mimeType: 'audio/wav' ); // Load from Laravel storage $fromStorage = Response::fromStorage('images/photo.jpg', 's3'); // Multiple responses combined $multiple = Response::make([ Response::text('First part of the response.'), Response::text('Second part of the response.'), ]); // Response with metadata $withMeta = Response::text('Result data') ->withMeta('processingTime', '150ms') ->withMeta(['cached' => true, 'source' => 'database']); ``` ## Request Handling The Request class provides methods to access and validate input arguments. ```php get('name', 'Guest'); // Get typed values $count = $request->integer('count', 10); $enabled = $request->boolean('enabled', false); $date = $request->date('created_at'); // Get all arguments $all = $request->all(); // Get specific keys only $subset = $request->all(['name', 'email']); // Validate with Laravel validation rules $validated = $request->validate([ 'name' => 'required|string|max:100', 'email' => 'required|email', 'age' => 'nullable|integer|min:0|max:150', ]); // Access authenticated user (requires OAuth setup) $user = $request->user(); // Access session ID $sessionId = $request->sessionId(); // Access request metadata $meta = $request->meta(); // For resources with URI templates $uri = $request->uri(); return Response::text("Processed request for: {$name}"); } } ``` ## Conditional Tool Registration Use the `shouldRegister` method to conditionally register tools based on runtime conditions. ```php user(); return $user && $user->hasRole('admin'); } public function handle(Request $request): Response { $setting = $request->get('setting'); $value = $request->get('value'); config([$setting => $value]); return Response::text("Setting '{$setting}' updated to '{$value}'."); } } ``` ## Testing MCP Servers Laravel MCP provides a fluent testing API for verifying tools, resources, and prompts. ```php 'John Doe', 'email' => 'john@example.com', 'role' => 'editor', ]) ->assertOk() ->assertSee('User created successfully') ->assertDontSee('error'); }); // Test validation errors it('validates required fields', function () { AppServer::tool(CreateUserTool::class, [ 'email' => 'invalid-email', ]) ->assertHasErrors(['name', 'email']); }); // Test structured content it('returns weather data with correct structure', function () { AppServer::tool(GetWeatherTool::class, ['city' => 'London']) ->assertOk() ->assertStructuredContent([ 'city' => 'London', 'temperature' => 22.5, 'conditions' => 'Partly cloudy', ]); }); // Test with authenticated user it('allows admin access', function () { $admin = User::factory()->create(['role' => 'admin']); AppServer::actingAs($admin) ->tool(AdminSettingsTool::class, ['setting' => 'app.name', 'value' => 'New Name']) ->assertOk() ->assertAuthenticated() ->assertAuthenticatedAs($admin); }); // Test a prompt it('generates code review instructions', function () { AppServer::prompt(CodeReviewPrompt::class, [ 'language' => 'PHP', 'focus_areas' => 'security', ]) ->assertOk() ->assertSee('PHP') ->assertSee('security') ->assertName('code-review-prompt') ->assertDescription('Instructions for performing a thorough code review.'); }); // Test a resource it('returns user profile data', function () { $user = User::factory()->create(); AppServer::resource(UserProfileResource::class, ['userId' => $user->id]) ->assertOk() ->assertSee($user->name) ->assertSee($user->email); }); // Test completions it('provides project completions', function () { AppServer::completion(ProjectReportPrompt::class, 'project', 'web') ->assertHasCompletions(['web-app', 'web-api']) ->assertCompletionCount(2); }); // Test notifications in streaming responses it('sends progress notifications', function () { AppServer::tool(BatchProcessTool::class, ['items' => ['a', 'b', 'c']]) ->assertOk() ->assertNotificationCount(3) ->assertSentNotification('stream/progress', ['progress' => 100]); }); ``` ## Artisan Commands Generate MCP components using Artisan commands. ```bash # Create a new MCP server php artisan make:mcp-server AppServer # Create a new tool php artisan make:mcp-tool CreateUserTool # Create a new resource php artisan make:mcp-resource UserProfileResource # Create a new prompt php artisan make:mcp-prompt CodeReviewPrompt # Start a local stdio server php artisan mcp:start app-server # Launch MCP Inspector for debugging php artisan mcp:inspector php artisan mcp:inspector --host=127.0.0.1 --port=8080 ``` ## Configuration Publish and customize the MCP configuration file. ```php [ 'https://your-app.com', 'http://localhost', // '*', // Allow all (development only) ], ]; ``` ## Summary Laravel MCP enables seamless integration between AI agents and Laravel applications through the Model Context Protocol. The primary use cases include exposing application functionality as callable tools (database operations, API integrations, business logic), providing contextual resources (configuration, documentation, user data), and defining reusable prompts with argument completion for AI interactions. The package follows Laravel conventions with attribute-based configuration, dependency injection, and comprehensive testing utilities. Integration patterns typically involve creating a central MCP server class that registers domain-specific tools, resources, and prompts, then exposing it via HTTP endpoints or stdio for different AI clients. For production deployments, OAuth authentication via Laravel Passport secures access, while the testing API ensures reliability. The streaming response support enables long-running operations with progress feedback, and structured content with output schemas provides type-safe data exchange between AI agents and your application.