diff --git a/CHANGELOG.md b/CHANGELOG.md index ec7d523..dc66dc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,86 @@ All notable changes to `php-mcp/laravel` will be documented in this file. +## v2.0.0 - 2025-06-04 + +This release marks a **major overhaul**, bringing it into full alignment with `php-mcp/server` v2.1.0+ and introducing a significantly improved, more "Laravely" developer experience. + +### Added + +* **Fluent Manual Registration API:** + * Introduced the `Mcp` Facade (`PhpMcp\Laravel\Facades\Mcp`). + * Define Tools, Resources, Prompts, and Resource Templates fluently (e.g., `Mcp::tool(...)->description(...)`). + * Definitions are typically placed in `routes/mcp.php` (configurable). + * Handlers are resolved via Laravel's service container, allowing dependency injection. + +* **Dedicated HTTP Server Transport via `mcp:serve`:** + * The `php artisan mcp:serve --transport=http` command now launches a standalone, high-performance ReactPHP-based HTTP server using `\PhpMcp\Server\Transports\HttpServerTransport`. + * Configuration for this dedicated server is in `config/mcp.php` under `transports.http_dedicated`. + * CLI options (`--host`, `--port`, `--path-prefix`) can override config defaults. + +* **`LaravelHttpTransport` for Integrated HTTP:** + * New `PhpMcp\Laravel\Transports\LaravelHttpTransport` class implements `ServerTransportInterface` to bridge Laravel's HTTP request lifecycle with the core MCP `Protocol` handler. + +* **Configurable Auto-Discovery:** + * `config('mcp.discovery.auto_discover')` (default: `true`) now controls whether discovery runs automatically or not. You can set it to false in production.. + +* **Interactive Prompt for `mcp:serve`:** If `--transport` is not specified, the command now interactively prompts the user to choose between `stdio` and `http`. + +### Changed + +* **Core Server Integration:** Now uses `\PhpMcp\Server\Server::make()` (ServerBuilder) for all server instantiation, fully leveraging `php-mcp/server` v2.x architecture. +* **Namespace:** Base package namespace changed from `PhpMcp\Laravel\Server` to **`PhpMcp\Laravel`**. +* **Configuration (`config/mcp.php`):** + * Significantly restructured and updated to align with `ServerBuilder` options. + * Clearer separation of settings for `http_dedicated` vs. `http_integrated` transports. + * Simplified cache TTL (`cache.ttl`) and discovery (`discovery.save_to_cache_on_discover`) keys. + * Added `server.instructions` for the `initialize` MCP response. + * Added `discovery.exclude_dirs` and `discovery.definitions_file`. + +* **`McpServiceProvider`:** + * Completely rewritten to correctly build and configure the `\PhpMcp\Server\Server` instance using Laravel's services for logging, caching (with fallback to core `FileCache`), container, and event loop. + * Loads manual definitions from the configured `definitions_file` via `McpRegistrar`. + * Sets up core `Registry` notifiers to dispatch Laravel events for list changes. + +* **`McpController` (Integrated HTTP):** More robustly handles the integrated server behavior, working with a custom `LaravelHttpTransport`. +* **Artisan Commands:** + * `mcp:discover`: Now directly calls `Server::discover()` with configured/CLI parameters. `force` option behavior clarified. + * `mcp:list`: Fetches elements from the live, fully configured `Registry` from the resolved `Server` instance. + * `mcp:serve`: Refactored to use core `StdioServerTransport` or `HttpServerTransport` directly. + +* **Dependency:** Updated `php-mcp/server` to `^2.2.0` + +### Fixed + +* More robust error handling and logging in Artisan commands and `McpController`. +* Improved clarity and consistency in how core server components are resolved and used within the Laravel context. + +### Removed + +* `PhpMcp\Laravel\Server\Adapters\ConfigAdapter`: No longer needed due to changes in `php-mcp/server` v2.x. + +### BREAKING CHANGES + +* **Namespace Change:** The primary package namespace has changed from `PhpMcp\Laravel\Server` to `PhpMcp\Laravel`. Update all `use` statements and FQCN references in your application. You may have to uninstall and reinstall the package to avoid conflicts. +* **Configuration File:** The `config/mcp.php` file has been significantly restructured. You **must** republish and merge your customizations: + ```bash + php artisan vendor:publish --provider="PhpMcp\Laravel\McpServiceProvider" --tag="mcp-config" --force + + ``` +* **`mcp:serve` for HTTP:** The `--transport=http` option for `mcp:serve` now launches a *dedicated* ReactPHP-based server process. For serving MCP via your main Laravel application routes, ensure the `http_integrated` transport is enabled in `config/mcp.php` and your web server is configured appropriately. +* **Event Handling:** If you were directly listening to internal events from the previous version, these may have changed. Rely on the documented Laravel events (`ToolsListChanged`, etc.). +* **Removed Classes:** `PhpMcp\Laravel\Server\Adapters\ConfigAdapter` is removed. + +**Full Changelog**: https://github.com/php-mcp/laravel/compare/1.1.1...2.0.0 + ## v1.1.1 - 2025-05-12 ### What's Changed + * McpServiceProvider File And loadElements function are not found by @tsztodd in https://github.com/php-mcp/laravel/pull/2 ### New Contributors + * @tsztodd made their first contribution in https://github.com/php-mcp/laravel/pull/2 **Full Changelog**: https://github.com/php-mcp/laravel/compare/1.1.0...1.1.1 @@ -18,13 +92,13 @@ This release updates the package for compatibility with `php-mcp/server` v1.1.0. ### What Changed -* Updated dependency requirement to `php-mcp/server: ^1.1.0`. -* Modified `McpServiceProvider` to correctly provide `ConfigurationRepositoryInterface`, `LoggerInterface`, and `CacheInterface` bindings to the underlying `Server` instance when resolved from the Laravel container. -* Updated `ServeCommand` and `McpController` to inject the `Server` instance and instantiate `TransportHandler` classes according to `php-mcp/server` v1.1.0 constructor changes. +* Updated dependency requirement to `php-mcp/server: ^1.1.0`. +* Modified `McpServiceProvider` to correctly provide `ConfigurationRepositoryInterface`, `LoggerInterface`, and `CacheInterface` bindings to the underlying `Server` instance when resolved from the Laravel container. +* Updated `ServeCommand` and `McpController` to inject the `Server` instance and instantiate `TransportHandler` classes according to `php-mcp/server` v1.1.0 constructor changes. ### Fixed -* Ensures compatibility with the refactored dependency injection and transport handler instantiation logic in `php-mcp/server` v1.1.0. +* Ensures compatibility with the refactored dependency injection and transport handler instantiation logic in `php-mcp/server` v1.1.0. **Full Changelog**: https://github.com/php-mcp/laravel/compare/1.0.0...1.1.0 @@ -36,14 +110,14 @@ Welcome to the first release of `php-mcp/laravel`! This package provides seamles ## Key Features -* **Effortless Integration:** Automatically wires up Laravel's Cache, Logger, and Service Container for use by the MCP server. -* **Attribute-Based Definition:** Define MCP tools, resources, and prompts using PHP attributes (`#[McpTool]`, `#[McpResource]`, etc.) within your Laravel application structure. Leverage Laravel's Dependency Injection within your MCP element classes. -* **Configuration:** Provides a publishable configuration file (`config/mcp.php`) for fine-grained control over discovery, transports, caching, and capabilities. -* **Artisan Commands:** Includes commands for element discovery (`mcp:discover`), listing discovered elements (`mcp:list`), and running the server via stdio (`mcp:serve`). -* **HTTP+SSE Transport:** Sets up routes (`/mcp/message`, `/mcp/sse` by default) and a controller for handling MCP communication over HTTP, enabling browser-based clients and other HTTP consumers. -* **Automatic Discovery (Dev):** Automatically discovers MCP elements in development environments on first use, improving developer experience (no need to manually run `mcp:discover` after changes). -* **Manual Discovery (Prod):** Requires manual discovery (`mcp:discover`) for production environments, ensuring optimal performance via caching (integrates well with deployment workflows). -* **Event Integration:** Dispatches Laravel events (`ToolsListChanged`, `ResourcesListChanged`, `PromptsListChanged`) when element lists change, allowing for custom integrations or notifications. +* **Effortless Integration:** Automatically wires up Laravel's Cache, Logger, and Service Container for use by the MCP server. +* **Attribute-Based Definition:** Define MCP tools, resources, and prompts using PHP attributes (`#[McpTool]`, `#[McpResource]`, etc.) within your Laravel application structure. Leverage Laravel's Dependency Injection within your MCP element classes. +* **Configuration:** Provides a publishable configuration file (`config/mcp.php`) for fine-grained control over discovery, transports, caching, and capabilities. +* **Artisan Commands:** Includes commands for element discovery (`mcp:discover`), listing discovered elements (`mcp:list`), and running the server via stdio (`mcp:serve`). +* **HTTP+SSE Transport:** Sets up routes (`/mcp/message`, `/mcp/sse` by default) and a controller for handling MCP communication over HTTP, enabling browser-based clients and other HTTP consumers. +* **Automatic Discovery (Dev):** Automatically discovers MCP elements in development environments on first use, improving developer experience (no need to manually run `mcp:discover` after changes). +* **Manual Discovery (Prod):** Requires manual discovery (`mcp:discover`) for production environments, ensuring optimal performance via caching (integrates well with deployment workflows). +* **Event Integration:** Dispatches Laravel events (`ToolsListChanged`, `ResourcesListChanged`, `PromptsListChanged`) when element lists change, allowing for custom integrations or notifications. ## Installation @@ -55,27 +129,29 @@ composer require php-mcp/laravel # 2. Publish the configuration file (optional but recommended) php artisan vendor:publish --provider="PhpMcp\Laravel\Server\McpServiceProvider" --tag="mcp-config" -``` +``` ## Getting Started -1. **Define Elements:** Create PHP classes with methods annotated with `#[McpTool]`, `#[McpResource]`, etc., within directories specified in `config/mcp.php` (e.g., `app/Mcp`). Inject dependencies as needed. See [Defining MCP Elements](https://github.com/php-mcp/laravel/blob/main/README.md#defining-mcp-elements). -2. **Discovery:** - * In development, discovery runs automatically when needed. - * In production, run `php artisan mcp:discover` during your deployment process. See [Automatic Discovery vs. Manual Discovery](https://github.com/php-mcp/laravel/blob/main/README.md#automatic-discovery-development-vs-manual-discovery-production). -3. **Run the Server:** - * For **Stdio Transport:** Use `php artisan mcp:serve` and configure your client to execute this command (using the full path to `artisan`). - * For **HTTP+SSE Transport:** Ensure `transports.http.enabled` is true, run your Laravel app on a suitable web server (Nginx+FPM, Octane, etc. - **not** `php artisan serve`), exclude the MCP route from CSRF protection, and configure your client with the SSE URL (e.g., `http://your-app.test/mcp/sse`). See [Running the Server](https://github.com/php-mcp/laravel/blob/main/README.md#running-the-server) for critical details. +1. **Define Elements:** Create PHP classes with methods annotated with `#[McpTool]`, `#[McpResource]`, etc., within directories specified in `config/mcp.php` (e.g., `app/Mcp`). Inject dependencies as needed. See [Defining MCP Elements](https://github.com/php-mcp/laravel/blob/main/README.md#defining-mcp-elements). +2. **Discovery:** + * In development, discovery runs automatically when needed. + * In production, run `php artisan mcp:discover` during your deployment process. See [Automatic Discovery vs. Manual Discovery](https://github.com/php-mcp/laravel/blob/main/README.md#automatic-discovery-development-vs-manual-discovery-production). + +3. **Run the Server:** + * For **Stdio Transport:** Use `php artisan mcp:serve` and configure your client to execute this command (using the full path to `artisan`). + * For **HTTP+SSE Transport:** Ensure `transports.http.enabled` is true, run your Laravel app on a suitable web server (Nginx+FPM, Octane, etc. - **not** `php artisan serve`), exclude the MCP route from CSRF protection, and configure your client with the SSE URL (e.g., `http://your-app.test/mcp/sse`). See [Running the Server](https://github.com/php-mcp/laravel/blob/main/README.md#running-the-server) for critical details. + ## Important Notes -* **HTTP Transport Server Requirement:** The standard `php artisan serve` development server is **not suitable** for the HTTP+SSE transport due to its single-process nature. Use a proper web server setup like Nginx/Apache + PHP-FPM or Laravel Octane. -* **CSRF Exclusion:** If using the default `web` middleware group for the HTTP transport, you **must** exclude the MCP message route (default: `mcp` or `mcp/*`) from CSRF protection in your application to avoid `419` errors. -* **Dependencies:** Requires PHP >= 8.1 and Laravel >= 10.0. +* **HTTP Transport Server Requirement:** The standard `php artisan serve` development server is **not suitable** for the HTTP+SSE transport due to its single-process nature. Use a proper web server setup like Nginx/Apache + PHP-FPM or Laravel Octane. +* **CSRF Exclusion:** If using the default `web` middleware group for the HTTP transport, you **must** exclude the MCP message route (default: `mcp` or `mcp/*`) from CSRF protection in your application to avoid `419` errors. +* **Dependencies:** Requires PHP >= 8.1 and Laravel >= 10.0. ## Links -* **GitHub Repository:** https://github.com/php-mcp/laravel -* **Packagist:** https://packagist.org/packages/php-mcp/laravel +* **GitHub Repository:** https://github.com/php-mcp/laravel +* **Packagist:** https://packagist.org/packages/php-mcp/laravel -Please report any issues or provide feedback on the GitHub repository. \ No newline at end of file +Please report any issues or provide feedback on the GitHub repository. diff --git a/README.md b/README.md index a9ec83d..90fc00a 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ **Seamlessly integrate the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) into your Laravel applications.** -This package is the official Laravel wrapper for the powerful [`php-mcp/server`](https://github.com/php-mcp/server) library. It allows you to effortlessly expose parts of your Laravel application as MCP **Tools**, **Resources**, and **Prompts**, enabling standardized communication with AI assistants like Anthropic's Claude, Cursor IDE, and others. +This package is a Laravel compatible wrapper for the powerful [`php-mcp/server`](https://github.com/php-mcp/server) library. It allows you to effortlessly expose parts of your Laravel application as MCP **Tools**, **Resources**, and **Prompts**, enabling standardized communication with AI assistants like Anthropic's Claude, Cursor IDE, and others. **Key Features:** @@ -38,7 +38,7 @@ This package utilizes `php-mcp/server` v2.1.0+ which supports the `2024-11-05` v 2. **Publish Configuration:** ```bash - php artisan vendor:publish --provider="PhpMcp\Laravel\Server\McpServiceProvider" --tag="mcp-config" + php artisan vendor:publish --provider="PhpMcp\Laravel\McpServiceProvider" --tag="mcp-config" ``` ## Configuration @@ -217,7 +217,7 @@ For Laravel 11+: // bootstrap/app.php ->withMiddleware(function (Middleware $middleware) { $middleware->validateCsrfTokens(except: [ - config('mcp.transports.http_integrated.route_prefix') . '/message', + 'mcp/message', // Adjust if you changed the route prefix ]); }) ``` @@ -274,7 +274,8 @@ Configure your MCP client to execute this command directly. For example, in Curs "command": "php", "args": [ "/full/path/to/your/laravel/project/artisan", - "mcp:serve" + "mcp:serve", + "--transport=stdio" ] } } @@ -303,9 +304,9 @@ If your available MCP elements or resource content change while the server is ru * **List Changes (Tools, Resources, Prompts):** Dispatch the corresponding Laravel event. The package includes listeners to send the appropriate MCP notification. ```php - use PhpMcp\Laravel\Server\Events\ToolsListChanged; - use PhpMcp\Laravel\Server\Events\ResourcesListChanged; - use PhpMcp\Laravel\Server\Events\PromptsListChanged; + use PhpMcp\Laravel\Events\ToolsListChanged; + use PhpMcp\Laravel\Events\ResourcesListChanged; + use PhpMcp\Laravel\Events\PromptsListChanged; ToolsListChanged::dispatch(); // ResourcesListChanged::dispatch(); @@ -313,9 +314,9 @@ If your available MCP elements or resource content change while the server is ru ``` * **Specific Resource Content Update:** - Dispatch the `PhpMcp\Laravel\Server\Events\ResourceUpdated` event with the URI of the changed resource. + Dispatch the `PhpMcp\Laravel\Events\ResourceUpdated` event with the URI of the changed resource. ```php - use PhpMcp\Laravel\Server\Events\ResourceUpdated; + use PhpMcp\Laravel\Events\ResourceUpdated; $resourceUri = 'file:///path/to/updated_file.txt'; // ... your logic that updates the resource ... @@ -333,4 +334,4 @@ Please see [CONTRIBUTING.md](CONTRIBUTING.md) in the main [`php-mcp/server`](htt ## License -The MIT License (MIT). See [LICENSE](LICENSE). \ No newline at end of file +The MIT License (MIT). See [LICENSE](LICENSE). diff --git a/composer.json b/composer.json index 5d56a64..12851e3 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ "require": { "php": "^8.1", "laravel/framework": "^9.46 || ^10.34 || ^11.29 || ^12.0", - "php-mcp/server": "^2.2" + "php-mcp/server": "^2.3" }, "require-dev": { "laravel/pint": "^1.13", @@ -30,8 +30,7 @@ "orchestra/testbench": "^8.0 || ^9.0", "pestphp/pest": "^2.0", "pestphp/pest-plugin-laravel": "^2.0", - "phpunit/phpunit": "^10.0 || ^11.0", - "react/http": "^1.11" + "phpunit/phpunit": "^10.0 || ^11.0" }, "autoload": { "psr-4": { diff --git a/samples/basic/composer.json b/samples/basic/composer.json index 8b9368d..23dae59 100644 --- a/samples/basic/composer.json +++ b/samples/basic/composer.json @@ -12,7 +12,7 @@ "php": "^8.2", "laravel/framework": "^12.0", "laravel/tinker": "^2.10.1", - "php-mcp/laravel": "*" + "php-mcp/laravel": "dev-main" }, "require-dev": { "fakerphp/faker": "^1.23", diff --git a/samples/basic/composer.lock b/samples/basic/composer.lock index 87bfb12..2e51e65 100644 --- a/samples/basic/composer.lock +++ b/samples/basic/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "0657b476192ef74ed23544ff24c8dac5", + "content-hash": "26187052f3195fd4e3e90385de4d0446", "packages": [ { "name": "brick/math", @@ -1207,16 +1207,16 @@ }, { "name": "laravel/framework", - "version": "v12.17.0", + "version": "v12.18.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "8729d084510480fdeec9b6ad198180147d4a7f06" + "reference": "7d264a0dad2bfc5c154240b38e8ce9b2c4cdd14d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/8729d084510480fdeec9b6ad198180147d4a7f06", - "reference": "8729d084510480fdeec9b6ad198180147d4a7f06", + "url": "https://api.github.com/repos/laravel/framework/zipball/7d264a0dad2bfc5c154240b38e8ce9b2c4cdd14d", + "reference": "7d264a0dad2bfc5c154240b38e8ce9b2c4cdd14d", "shasum": "" }, "require": { @@ -1418,7 +1418,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-06-03T14:04:18+00:00" + "time": "2025-06-10T14:48:34+00:00" }, { "name": "laravel/prompts", @@ -2262,16 +2262,16 @@ }, { "name": "nesbot/carbon", - "version": "3.9.1", + "version": "3.10.0", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon.git", - "reference": "ced71f79398ece168e24f7f7710462f462310d4d" + "reference": "c1397390dd0a7e0f11660f0ae20f753d88c1f3d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/ced71f79398ece168e24f7f7710462f462310d4d", - "reference": "ced71f79398ece168e24f7f7710462f462310d4d", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/c1397390dd0a7e0f11660f0ae20f753d88c1f3d9", + "reference": "c1397390dd0a7e0f11660f0ae20f753d88c1f3d9", "shasum": "" }, "require": { @@ -2279,9 +2279,9 @@ "ext-json": "*", "php": "^8.1", "psr/clock": "^1.0", - "symfony/clock": "^6.3 || ^7.0", + "symfony/clock": "^6.3.12 || ^7.0", "symfony/polyfill-mbstring": "^1.0", - "symfony/translation": "^4.4.18 || ^5.2.1|| ^6.0 || ^7.0" + "symfony/translation": "^4.4.18 || ^5.2.1 || ^6.0 || ^7.0" }, "provide": { "psr/clock-implementation": "1.0" @@ -2289,14 +2289,13 @@ "require-dev": { "doctrine/dbal": "^3.6.3 || ^4.0", "doctrine/orm": "^2.15.2 || ^3.0", - "friendsofphp/php-cs-fixer": "^3.57.2", + "friendsofphp/php-cs-fixer": "^3.75.0", "kylekatarnls/multi-tester": "^2.5.3", - "ondrejmirtes/better-reflection": "^6.25.0.4", "phpmd/phpmd": "^2.15.0", - "phpstan/extension-installer": "^1.3.1", - "phpstan/phpstan": "^1.11.2", - "phpunit/phpunit": "^10.5.20", - "squizlabs/php_codesniffer": "^3.9.0" + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.1.17", + "phpunit/phpunit": "^10.5.46", + "squizlabs/php_codesniffer": "^3.13.0" }, "bin": [ "bin/carbon" @@ -2364,7 +2363,7 @@ "type": "tidelift" } ], - "time": "2025-05-01T19:51:51+00:00" + "time": "2025-06-12T10:24:28+00:00" }, { "name": "nette/schema", @@ -2855,12 +2854,12 @@ "dist": { "type": "path", "url": "../..", - "reference": "44b8217a32cb8031bf2ed8c9000e007a24c33cba" + "reference": "cdf7fbadc4830e0f01ffef363473e98f1ec3e8f4" }, "require": { "laravel/framework": "^9.46 || ^10.34 || ^11.29 || ^12.0", "php": "^8.1", - "php-mcp/server": "^2.2" + "php-mcp/server": "^2.3" }, "require-dev": { "laravel/pint": "^1.13", @@ -2868,10 +2867,8 @@ "orchestra/pest-plugin-testbench": "^2.1", "orchestra/testbench": "^8.0 || ^9.0", "pestphp/pest": "^2.0", - "pestphp/pest-plugin-drift": "^2.6", "pestphp/pest-plugin-laravel": "^2.0", - "phpunit/phpunit": "^10.0 || ^11.0", - "react/http": "^1.11" + "phpunit/phpunit": "^10.0 || ^11.0" }, "type": "library", "extra": { @@ -2928,16 +2925,16 @@ }, { "name": "php-mcp/server", - "version": "2.2.0", + "version": "2.3.1", "source": { "type": "git", "url": "https://github.com/php-mcp/server.git", - "reference": "9892dd32793a6dff324c5024d812645d10cdf786" + "reference": "686cac47af096907179ebf9ab38c9d5a75c5aa6f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-mcp/server/zipball/9892dd32793a6dff324c5024d812645d10cdf786", - "reference": "9892dd32793a6dff324c5024d812645d10cdf786", + "url": "https://api.github.com/repos/php-mcp/server/zipball/686cac47af096907179ebf9ab38c9d5a75c5aa6f", + "reference": "686cac47af096907179ebf9ab38c9d5a75c5aa6f", "shasum": "" }, "require": { @@ -2950,6 +2947,7 @@ "psr/simple-cache": "^1.0 || ^2.0 || ^3.0", "react/event-loop": "^1.5", "react/http": "^1.11", + "react/promise": "^3.0", "react/stream": "^1.4", "symfony/finder": "^6.4 || ^7.2" }, @@ -2990,9 +2988,9 @@ ], "support": { "issues": "https://github.com/php-mcp/server/issues", - "source": "https://github.com/php-mcp/server/tree/2.2.0" + "source": "https://github.com/php-mcp/server/tree/2.3.1" }, - "time": "2025-06-03T23:05:08+00:00" + "time": "2025-06-13T11:04:31+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -7250,16 +7248,16 @@ }, { "name": "filp/whoops", - "version": "2.18.1", + "version": "2.18.2", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "8fcc6a862f2e7b94eb4221fd0819ddba3d30ab26" + "reference": "89dabca1490bc77dbcab41c2b20968c7e44bf7c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/8fcc6a862f2e7b94eb4221fd0819ddba3d30ab26", - "reference": "8fcc6a862f2e7b94eb4221fd0819ddba3d30ab26", + "url": "https://api.github.com/repos/filp/whoops/zipball/89dabca1490bc77dbcab41c2b20968c7e44bf7c3", + "reference": "89dabca1490bc77dbcab41c2b20968c7e44bf7c3", "shasum": "" }, "require": { @@ -7309,7 +7307,7 @@ ], "support": { "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.18.1" + "source": "https://github.com/filp/whoops/tree/2.18.2" }, "funding": [ { @@ -7317,7 +7315,7 @@ "type": "github" } ], - "time": "2025-06-03T18:56:14+00:00" + "time": "2025-06-11T20:42:19+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -7432,16 +7430,16 @@ }, { "name": "laravel/pail", - "version": "v1.2.2", + "version": "v1.2.3", "source": { "type": "git", "url": "https://github.com/laravel/pail.git", - "reference": "f31f4980f52be17c4667f3eafe034e6826787db2" + "reference": "8cc3d575c1f0e57eeb923f366a37528c50d2385a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pail/zipball/f31f4980f52be17c4667f3eafe034e6826787db2", - "reference": "f31f4980f52be17c4667f3eafe034e6826787db2", + "url": "https://api.github.com/repos/laravel/pail/zipball/8cc3d575c1f0e57eeb923f366a37528c50d2385a", + "reference": "8cc3d575c1f0e57eeb923f366a37528c50d2385a", "shasum": "" }, "require": { @@ -7461,7 +7459,7 @@ "orchestra/testbench-core": "^8.13|^9.0|^10.0", "pestphp/pest": "^2.20|^3.0", "pestphp/pest-plugin-type-coverage": "^2.3|^3.0", - "phpstan/phpstan": "^1.10", + "phpstan/phpstan": "^1.12.27", "symfony/var-dumper": "^6.3|^7.0" }, "type": "library", @@ -7497,6 +7495,7 @@ "description": "Easily delve into your Laravel application's log files directly from the command line.", "homepage": "https://github.com/laravel/pail", "keywords": [ + "dev", "laravel", "logs", "php", @@ -7506,7 +7505,7 @@ "issues": "https://github.com/laravel/pail/issues", "source": "https://github.com/laravel/pail" }, - "time": "2025-01-28T15:15:15+00:00" + "time": "2025-06-05T13:55:57+00:00" }, { "name": "laravel/pint", @@ -7782,23 +7781,23 @@ }, { "name": "nunomaduro/collision", - "version": "v8.8.0", + "version": "v8.8.1", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "4cf9f3b47afff38b139fb79ce54fc71799022ce8" + "reference": "44ccb82e3e21efb5446748d2a3c81a030ac22bd5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/4cf9f3b47afff38b139fb79ce54fc71799022ce8", - "reference": "4cf9f3b47afff38b139fb79ce54fc71799022ce8", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/44ccb82e3e21efb5446748d2a3c81a030ac22bd5", + "reference": "44ccb82e3e21efb5446748d2a3c81a030ac22bd5", "shasum": "" }, "require": { - "filp/whoops": "^2.18.0", - "nunomaduro/termwind": "^2.3.0", + "filp/whoops": "^2.18.1", + "nunomaduro/termwind": "^2.3.1", "php": "^8.2.0", - "symfony/console": "^7.2.5" + "symfony/console": "^7.3.0" }, "conflict": { "laravel/framework": "<11.44.2 || >=13.0.0", @@ -7806,15 +7805,15 @@ }, "require-dev": { "brianium/paratest": "^7.8.3", - "larastan/larastan": "^3.2", - "laravel/framework": "^11.44.2 || ^12.6", - "laravel/pint": "^1.21.2", - "laravel/sail": "^1.41.0", - "laravel/sanctum": "^4.0.8", + "larastan/larastan": "^3.4.2", + "laravel/framework": "^11.44.2 || ^12.18", + "laravel/pint": "^1.22.1", + "laravel/sail": "^1.43.1", + "laravel/sanctum": "^4.1.1", "laravel/tinker": "^2.10.1", - "orchestra/testbench-core": "^9.12.0 || ^10.1", - "pestphp/pest": "^3.8.0", - "sebastian/environment": "^7.2.0 || ^8.0" + "orchestra/testbench-core": "^9.12.0 || ^10.4", + "pestphp/pest": "^3.8.2", + "sebastian/environment": "^7.2.1 || ^8.0" }, "type": "library", "extra": { @@ -7877,7 +7876,7 @@ "type": "patreon" } ], - "time": "2025-04-03T14:33:09+00:00" + "time": "2025-06-11T01:04:21+00:00" }, { "name": "pestphp/pest", @@ -9993,7 +9992,9 @@ ], "aliases": [], "minimum-stability": "dev", - "stability-flags": {}, + "stability-flags": { + "php-mcp/laravel": 20 + }, "prefer-stable": true, "prefer-lowest": false, "platform": { diff --git a/src/Listeners/McpNotificationListener.php b/src/Listeners/McpNotificationListener.php index d3a9630..32e564a 100644 --- a/src/Listeners/McpNotificationListener.php +++ b/src/Listeners/McpNotificationListener.php @@ -3,62 +3,32 @@ namespace PhpMcp\Laravel\Listeners; use PhpMcp\Laravel\Events\McpNotificationEvent; +use PhpMcp\Laravel\Events\PromptsListChanged; use PhpMcp\Laravel\Events\ResourceUpdated; -use PhpMcp\Server\Server; -use PhpMcp\Server\State\ClientStateManager; +use PhpMcp\Laravel\Events\ResourcesListChanged; +use PhpMcp\Laravel\Events\ToolsListChanged; +use PhpMcp\Server\Registry; /** * Handles routing MCP notifications to the appropriate transport. */ class McpNotificationListener { - private ClientStateManager $clientStateManager; /** * Create a new event handler instance. */ - public function __construct( - private Server $server - ) { - $this->clientStateManager = $server->getClientStateManager(); - } + public function __construct(private Registry $registry) {} /** * Handle the event. */ public function handle(McpNotificationEvent $event): void { - if ($event instanceof ResourceUpdated) { - $this->handleResourceUpdated($event); - - return; - } - - $this->handleListChanged($event); - } - - /** - * Handle resource updated notifications. - */ - private function handleResourceUpdated(ResourceUpdated $event): void - { - $subscribers = $this->clientStateManager->getResourceSubscribers($event->uri); - - $message = json_encode($event->toNotification()->toArray()); - foreach ($subscribers as $clientId) { - $this->clientStateManager->queueMessage($clientId, $message); - } - } - - /** - * Handle list changed notifications (tools, prompts and resources) - */ - private function handleListChanged(McpNotificationEvent $event): void - { - $activeClients = $this->clientStateManager->getActiveClients(); - - $message = json_encode($event->toNotification()->toArray()); - foreach ($activeClients as $clientId) { - $this->clientStateManager->queueMessage($clientId, $message); - } + match (true) { + $event instanceof ResourceUpdated => $this->registry->notifyResourceUpdated($event->uri), + $event instanceof ToolsListChanged => $this->registry->notifyToolsListChanged(), + $event instanceof ResourcesListChanged => $this->registry->notifyResourcesListChanged(), + $event instanceof PromptsListChanged => $this->registry->notifyPromptsListChanged(), + }; } } diff --git a/src/McpServiceProvider.php b/src/McpServiceProvider.php index ef3f2bd..ec29cc5 100644 --- a/src/McpServiceProvider.php +++ b/src/McpServiceProvider.php @@ -5,7 +5,6 @@ namespace PhpMcp\Laravel; use Illuminate\Contracts\Foundation\Application; -use Illuminate\Contracts\Support\DeferrableProvider; use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Route; use Illuminate\Support\ServiceProvider; @@ -21,7 +20,7 @@ use PhpMcp\Server\Registry; use PhpMcp\Server\Server; -class McpServiceProvider extends ServiceProvider implements DeferrableProvider +class McpServiceProvider extends ServiceProvider { public function register(): void { @@ -55,7 +54,6 @@ public function boot(): void $this->bootRoutes(); $this->bootEvents(); $this->bootCommands(); - $this->bootEventListeners(); } protected function loadMcpDefinitions(): void @@ -96,14 +94,19 @@ protected function buildServer(): void $registrar->applyBlueprints($builder); $server = $builder->build(); + $registry = $server->getRegistry(); if (config('mcp.discovery.auto_discover', true)) { + $registry->disableNotifications(); + $server->discover( basePath: config('mcp.discovery.base_path', base_path()), scanDirs: config('mcp.discovery.directories', ['app/Mcp']), excludeDirs: config('mcp.discovery.exclude_dirs', []), saveToCache: config('mcp.discovery.save_to_cache', true) ); + + $registry->enableNotifications(); } return $server; @@ -163,14 +166,4 @@ protected function bootEvents(): void McpNotificationListener::class, ); } - - protected function bootEventListeners(): void - { - $server = $this->app->make(Server::class); - $registry = $server->getRegistry(); - - $registry->setToolsChangedNotifier(ToolsListChanged::dispatch(...)); - $registry->setResourcesChangedNotifier(ResourcesListChanged::dispatch(...)); - $registry->setPromptsChangedNotifier(PromptsListChanged::dispatch(...)); - } } diff --git a/src/Transports/LaravelHttpTransport.php b/src/Transports/LaravelHttpTransport.php index 0fe7043..29ccf3f 100644 --- a/src/Transports/LaravelHttpTransport.php +++ b/src/Transports/LaravelHttpTransport.php @@ -7,13 +7,11 @@ use Evenement\EventEmitterTrait; use PhpMcp\Server\Contracts\LoggerAwareInterface; use PhpMcp\Server\Contracts\ServerTransportInterface; -use PhpMcp\Server\Exception\TransportException; use PhpMcp\Server\State\ClientStateManager; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; use React\Promise\PromiseInterface; -use function React\Promise\reject; use function React\Promise\resolve; class LaravelHttpTransport implements ServerTransportInterface, LoggerAwareInterface @@ -24,23 +22,11 @@ class LaravelHttpTransport implements ServerTransportInterface, LoggerAwareInter protected ClientStateManager $clientStateManager; - /** @var array Tracks active client IDs managed by this transport */ - private array $activeClients = []; - public function __construct(ClientStateManager $clientStateManager) { $this->clientStateManager = $clientStateManager; $this->logger = new NullLogger; - $this->on('client_connected', function (string $clientId) { - $this->activeClients[$clientId] = true; - $this->clientStateManager->updateClientActivity($clientId); - }); - - $this->on('client_disconnected', function (string $clientId, string $reason) { - unset($this->activeClients[$clientId]); - }); - $this->on('message', function (string $message, string $clientId) { $this->clientStateManager->updateClientActivity($clientId); }); @@ -68,12 +54,6 @@ public function listen(): void */ public function sendToClientAsync(string $clientId, string $rawFramedMessage): PromiseInterface { - if (! isset($this->activeClients[$clientId])) { - $this->logger->warning('Attempted to send message to inactive or unknown client.', ['clientId' => $clientId]); - - return reject(new TransportException("Client '{$clientId}' is not actively managed by this transport.")); - } - $messagePayload = rtrim($rawFramedMessage, "\n"); if (empty($messagePayload)) { @@ -90,13 +70,7 @@ public function sendToClientAsync(string $clientId, string $rawFramedMessage): P */ public function close(): void { - $activeClientIds = array_keys($this->activeClients); - - foreach ($activeClientIds as $clientId) { - $this->emit('client_disconnected', [$clientId, 'Transport globally closed']); - $this->emit('close', ['Transport closed.']); - } - + $this->emit('close', ['Transport closed.']); $this->removeAllListeners(); } } diff --git a/tests/Feature/McpServiceProviderTest.php b/tests/Feature/McpServiceProviderTest.php index 2ef8d77..0da15ab 100644 --- a/tests/Feature/McpServiceProviderTest.php +++ b/tests/Feature/McpServiceProviderTest.php @@ -95,23 +95,6 @@ public function test_auto_discovery_is_skipped_if_disabled() $this->assertNull($registry->findTool('stub_tool_one'), "Tool 'stub_tool_one' should not be found if auto-discovery is off."); } - public function test_event_notifiers_are_set_on_core_registry_and_dispatch_laravel_events() - { - Event::fake(); - - $server = $this->app->make('mcp.server'); - $registry = $server->getRegistry(); - - $newToolName = 'dynamic_tool_for_event_test'; - $this->assertNull($registry->findTool($newToolName)); - - $registry->registerTool( - new ToolDefinition(ManualTestHandler::class, 'handleTool', $newToolName, 'd', []) - ); - - Event::assertDispatched(ToolsListChanged::class); - } - public function test_http_integrated_routes_are_registered_if_enabled() { $this->assertTrue(Route::has('mcp.sse'));