php-mcp / laravel
The official Laravel integration for the PHP MCP Server package.
Requires
- php: ^8.1
- laravel/framework: ^9.46 || ^10.34 || ^11.29 || ^12.0
- php-mcp/server: ^1.0
Requires (Dev)
- laravel/pint: ^1.13
- mockery/mockery: ^1.6
- orchestra/testbench: ^8.0 || ^9.0
- pestphp/pest: ^2.0
- pestphp/pest-plugin-laravel: ^2.0
- php-mcp/react-transport: ^0.1.0
- phpunit/phpunit: ^10.0 || ^11.0
- react/http: ^1.11
README
Integrates the core php-mcp/server
package seamlessly into your Laravel application, allowing you to expose parts of your application as Model Context Protocol (MCP) tools, resources, and prompts using simple PHP attributes.
This package handles:
- Automatically wiring Laravel's Cache, Logger, and Container for use by the MCP server.
- Providing configuration options via
config/mcp.php
. - Registering Artisan commands (
mcp:serve
,mcp:discover
,mcp:list
). - Setting up HTTP+SSE transport routes and controllers.
- Integrating with Laravel's event system for dynamic updates.
Requirements
- PHP >= 8.1
- Laravel >= 10.0 (May work with older versions, but tested with 10+)
php-mcp/server
(Installed as a dependency)
Installation
- Require the package via Composer:
composer require php-mcp/laravel
- The
LaravelMcpServiceProvider
will be automatically discovered and registered by Laravel. - Publish the configuration file:
php artisan vendor:publish --provider="PhpMcp\Laravel\Server\LaravelMcpServiceProvider" --tag="mcp-config"
This will create aconfig/mcp.php
file where you can customize the server's behavior.
Configuration
The primary way to configure the MCP server in Laravel is through the config/mcp.php
file.
server
: Basic server information (name, version).discovery
:base_path
: The root path for discovery (defaults tobase_path()
).directories
: An array of paths relative tobase_path
to scan for MCP attributes (defaults to['app/Mcp']
). Add the directories where you define your MCP element classes here.
cache
:store
: The Laravel cache store to use (e.g.,file
,redis
). Uses the default store ifnull
.prefix
: The cache prefix to use for caching internally.ttl
: Default cache TTL in seconds for discovered elements and transport state.
transports
:http
: Configures the built-in HTTP+SSE transport.enabled
: Set tofalse
to disable the HTTP routes.prefix
: URL prefix for the MCP routes (defaults tomcp
, resulting in/mcp
and/mcp/sse
).middleware
: Array of middleware groups to apply. Defaults to['web']
. Important: Theweb
middleware group (or another group that enables sessions) is generally required for the HTTP transport to correctly identify clients using session IDs.domain
: Optional route domain.
stdio
: Configures the stdio transport.enabled
: Set tofalse
to disable themcp:serve
command.
protocol_versions
: Array of supported MCP protocol versions (only'2024-11-05'
currently).pagination_limit
: Default number of items returned by list methods.capabilities
: Enable/disable specific MCP features (tools, resources, prompts, logging) and list change notifications.logging
:channel
: Specific Laravel log channel to use. Defaults to the application's default channel.level
: Default log level if not provided by the core server.
Usage
Defining MCP Elements
Define your MCP Tools, Resources, and Prompts as methods within PHP classes, decorated with attributes from the php-mcp/server
package (#[McpTool]
, #[McpResource]
, #[McpPrompt]
, etc.).
Place these classes in a directory included in the discovery.directories
config array (e.g., app/Mcp/MyTools.php
).
Example (app/Mcp/MyTools.php
):
<?php namespace App\Mcp; use Illuminate\Support\Facades\Config; use PhpMcp\Server\Attributes\McpResource; use PhpMcp\Server\Attributes\McpTool; use Psr\Log\LoggerInterface; class MyTools { public function __construct(private LoggerInterface $logger) {} #[McpResource(uri: 'laravel://config/app.name', mimeType: 'text/plain')] public function getAppName(): string { return Config::get('app.name', 'Laravel'); } #[McpTool] public function add(int $a, int $b): int { $this->logger->info('Adding numbers via MCP'); return $a + $b; } }
- Dependency Injection: Your classes' constructors will be resolved using Laravel's service container, so you can inject any application dependencies (like the
LoggerInterface
above). - Attribute Usage: Refer to the
php-mcp/server
README for detailed information on defining elements and formatting return values.
Automatic Discovery (Development) vs. Manual Discovery (Production)
The server needs to discover your annotated elements before clients can use them.
-
Development: In non-production environments (e.g.,
APP_ENV=local
), the server will automatically discover elements the first time the MCP server is needed (like on the first relevant HTTP request or Artisan command). You generally do not need to run the command manually during development after adding or changing elements. -
Production: For performance reasons, automatic discovery is disabled in production environments (
APP_ENV=production
). You must run the discovery command manually as part of your deployment process:php artisan mcp:discover
This command scans the configured directories and caches the found elements using the configured Laravel cache store. Running it during deployment ensures your production environment uses the pre-discovered, cached elements for optimal performance.
(You can still run
mcp:discover
manually in development if you wish, for example, to pre-populate the cache.)
Running the Server
You can expose your MCP server using either the stdio or HTTP+SSE transport.
Stdio Transport:
-
Configure your MCP client (Cursor, Claude Desktop) to connect via command. Important: Ensure you provide the full path to your project's
artisan
file.Example Client Config (e.g.,
.cursor/mcp.json
):{ "mcpServers": { "my-laravel-mcp": { "command": "php", "args": [ "/full/path/to/your/laravel/project/artisan", "mcp:serve" ], } } }
(Replace
/full/path/to/...
with the correct absolute paths)
HTTP+SSE Transport:
-
Enable: Ensure
transports.http.enabled
istrue
inconfig/mcp.php
. -
Routes: The package automatically registers two routes (by default
/mcp/sse
[GET] and/mcp/message
[POST]) using the configured prefix and middleware (web
by default). -
Web Server Environment (CRITICAL):
- The built-in
php artisan serve
development server cannot reliably handle the concurrent nature of SSE (long-running GET request) and subsequent POST requests from the MCP client. This is because it runs as a single PHP process. You will likely encounter hangs or requests not being processed. - For the HTTP+SSE transport to function correctly, you must run your Laravel application using a web server setup capable of handling concurrent requests properly:
- Nginx + PHP-FPM or Apache + PHP-FPM (Recommended for typical deployments): Ensure FPM is configured to handle multiple worker processes.
- Laravel Octane (with Swoole or RoadRunner): Optimized for high concurrency and suitable for this use case.
- Other async runtimes capable of handling concurrent I/O.
- You also need to ensure your web server (Nginx/Apache) and PHP-FPM configurations allow for long-running requests (
set_time_limit(0)
is handled by the controller, but server/FPM timeouts might interfere) and do not buffer thetext/event-stream
response (e.g.,X-Accel-Buffering: no
for Nginx).
- The built-in
-
Middleware: Make sure the middleware applied (usually
web
inconfig/mcp.php
) correctly handles sessions or provides another way to consistently identify the client across requests if you modify the defaultMcpController
behaviour. -
CSRF Protection Exclusion (Important!): The default
web
middleware group includes CSRF protection. Since MCP clients do not send CSRF tokens, you must exclude the MCP POST route from CSRF verification to prevent419
errors.- The specific URI to exclude depends on the
prefix
configured inconfig/mcp.php
. By default, the prefix ismcp
, so you should excludemcp
ormcp/*
. - Laravel 10 and below: Add the pattern to the
$except
array inapp/Http/Middleware/VerifyCsrfToken.php
:// app/Http/Middleware/VerifyCsrfToken.php protected $except = [ // ... other routes 'mcp', // Or config('mcp.transports.http.prefix', 'mcp').'/*' ];
- Laravel 11+: Add the pattern within the
bootstrap/app.php
file'swithMiddleware
call:// bootstrap/app.php ->withMiddleware(function (Middleware $middleware) { $mcpPrefix = config('mcp.transports.http.prefix', 'mcp'); $middleware->validateCsrfTokens(except: [ $mcpPrefix, // Or $mcpPrefix.'/*' // ... other routes ]); })
- The specific URI to exclude depends on the
-
Client Configuration: Configure your MCP client to connect via URL, using the SSE endpoint URL.
Example Client Config:
{ "mcpServers": { "my-laravel-mcp-http": { "url": "http://your-laravel-app.test/mcp/sse" // Adjust URL as needed } } }
The server will automatically inform the client about the correct POST endpoint URL (including a unique
?clientId=...
query parameter) via the initialendpoint
event sent over the SSE connection.
Other Commands
- List Elements: View the discovered MCP elements.
php artisan mcp:list # Or list specific types: php artisan mcp:list tools php artisan mcp:list resources php artisan mcp:list prompts php artisan mcp:list templates # Output as JSON: php artisan mcp:list --json
Dynamic Updates (Notifications)
If the list of available tools, resources, or prompts changes while the server is running, or if a specific resource's content is updated, you can notify connected clients (primarily useful for HTTP+SSE).
-
List Changes: Dispatch the corresponding event from anywhere in your Laravel application:
use PhpMcp\Laravel\Server\Events\ToolsListChanged; use PhpMcp\Laravel\Server\Events\ResourcesListChanged; use PhpMcp\Laravel\Server\Events\PromptsListChanged; // When tools have changed: ToolsListChanged::dispatch(); // When resources have changed: ResourcesListChanged::dispatch(); // When prompts have changed: PromptsListChanged::dispatch();
The service provider includes listeners that automatically send the appropriate
*ListChanged
notifications to clients. -
Specific Resource Content Change: Inject or resolve the
PhpMcp\Server\Registry
and callnotifyResourceChanged
:use PhpMcp\Server\Registry; public function updateMyResource(Registry $registry, string $resourceUri) { // ... update the resource data ... $registry->notifyResourceChanged($resourceUri); }
This will trigger a
resources/didChange
notification for clients subscribed to that specific URI.
Contributing
Please see CONTRIBUTING.md for details.
License
The MIT License (MIT). Please see License File for more information.
Support & Feedback
Please open an issue on the GitHub repository for bugs, questions, or feedback.