php-mcp/laravel

The official Laravel integration for the PHP MCP Server package.

1.0.0 2025-04-28 15:41 UTC

This package is auto-updated.

Last update: 2025-04-28 15:47:51 UTC


README

Latest Version on Packagist Total Downloads License

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

  1. Require the package via Composer:
    composer require php-mcp/laravel
  2. The LaravelMcpServiceProvider will be automatically discovered and registered by Laravel.
  3. Publish the configuration file:
    php artisan vendor:publish --provider="PhpMcp\Laravel\Server\LaravelMcpServiceProvider" --tag="mcp-config"
    This will create a config/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 to base_path()).
    • directories: An array of paths relative to base_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 if null.
    • 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 to false to disable the HTTP routes.
      • prefix: URL prefix for the MCP routes (defaults to mcp, resulting in /mcp and /mcp/sse).
      • middleware: Array of middleware groups to apply. Defaults to ['web']. Important: The web 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 to false to disable the mcp: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 is true in config/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 the text/event-stream response (e.g., X-Accel-Buffering: no for Nginx).
  • Middleware: Make sure the middleware applied (usually web in config/mcp.php) correctly handles sessions or provides another way to consistently identify the client across requests if you modify the default McpController 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 prevent 419 errors.

    • The specific URI to exclude depends on the prefix configured in config/mcp.php. By default, the prefix is mcp, so you should exclude mcp or mcp/*.
    • Laravel 10 and below: Add the pattern to the $except array in app/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's withMiddleware call:
      // bootstrap/app.php
      ->withMiddleware(function (Middleware $middleware) {
          $mcpPrefix = config('mcp.transports.http.prefix', 'mcp');
          $middleware->validateCsrfTokens(except: [
              $mcpPrefix, // Or $mcpPrefix.'/*'
              // ... other routes
          ]);
      })
  • 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 initial endpoint 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 call notifyResourceChanged:

    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.