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 82e1cbe..ec29cc5 100644 --- a/src/McpServiceProvider.php +++ b/src/McpServiceProvider.php @@ -54,7 +54,6 @@ public function boot(): void $this->bootRoutes(); $this->bootEvents(); $this->bootCommands(); - $this->bootEventListeners(); } protected function loadMcpDefinitions(): void @@ -95,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; @@ -162,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/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'));