diff --git a/src/Registry.php b/src/Registry.php index 08b2bf9..281bf00 100644 --- a/src/Registry.php +++ b/src/Registry.php @@ -54,14 +54,8 @@ class Registry private bool $discoveredElementsLoaded = false; - /** @var callable|null */ - private $notifyToolsChanged = null; + private bool $notificationsEnabled = true; - /** @var callable|null */ - private $notifyResourcesChanged = null; - - /** @var callable|null */ - private $notifyPromptsChanged = null; public function __construct( LoggerInterface $logger, @@ -73,7 +67,6 @@ public function __construct( $this->clientStateManager = $clientStateManager; $this->initializeCollections(); - $this->initializeDefaultNotifiers(); if ($this->cache) { $this->loadDiscoveredElementsFromCache(); @@ -115,54 +108,93 @@ private function initializeCollections(): void $this->manualTemplateUris = []; } - private function initializeDefaultNotifiers(): void + public function enableNotifications(): void { - $this->notifyToolsChanged = function () { - if ($this->clientStateManager) { - $notification = Notification::make('notifications/tools/list_changed'); - $framedMessage = json_encode($notification->toArray(), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . "\n"; - if ($framedMessage !== false) { - $this->clientStateManager->queueMessageForAll($framedMessage); - } - } - }; - - $this->notifyResourcesChanged = function () { - if ($this->clientStateManager) { - $notification = Notification::make('notifications/resources/list_changed'); - $framedMessage = json_encode($notification->toArray(), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . "\n"; - if ($framedMessage !== false) { - $this->clientStateManager->queueMessageForAll($framedMessage); - } - } - }; - - $this->notifyPromptsChanged = function () { - if ($this->clientStateManager) { - $notification = Notification::make('notifications/prompts/list_changed'); - $framedMessage = json_encode($notification->toArray(), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . "\n"; - if ($framedMessage !== false) { - $this->clientStateManager->queueMessageForAll($framedMessage); - } - } - }; + $this->notificationsEnabled = true; } - public function setToolsChangedNotifier(?callable $notifier): void + public function disableNotifications(): void { - $this->notifyToolsChanged = $notifier; + $this->notificationsEnabled = false; } - public function setResourcesChangedNotifier(?callable $notifier): void + public function notifyToolsListChanged(): void { - $this->notifyResourcesChanged = $notifier; + if (!$this->notificationsEnabled || !$this->clientStateManager) { + return; + } + $notification = Notification::make('notifications/tools/list_changed'); + + $framedMessage = json_encode($notification, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . "\n"; + if ($framedMessage === false || $framedMessage === "\n") { + $this->logger->error('Failed to encode notification for queuing.', ['method' => $notification->method]); + return; + } + $this->clientStateManager->queueMessageForAll($framedMessage); } - public function setPromptsChangedNotifier(?callable $notifier): void + public function notifyResourcesListChanged(): void { - $this->notifyPromptsChanged = $notifier; + if (!$this->notificationsEnabled || !$this->clientStateManager) { + return; + } + $notification = Notification::make('notifications/resources/list_changed'); + + $framedMessage = json_encode($notification, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . "\n"; + if ($framedMessage === false || $framedMessage === "\n") { + $this->logger->error('Failed to encode notification for queuing.', ['method' => $notification->method]); + return; + } + $this->clientStateManager->queueMessageForAll($framedMessage); } + public function notifyPromptsListChanged(): void + { + if (!$this->notificationsEnabled || !$this->clientStateManager) { + return; + } + $notification = Notification::make('notifications/prompts/list_changed'); + + $framedMessage = json_encode($notification, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . "\n"; + if ($framedMessage === false || $framedMessage === "\n") { + $this->logger->error('Failed to encode notification for queuing.', ['method' => $notification->method]); + return; + } + $this->clientStateManager->queueMessageForAll($framedMessage); + } + + public function notifyResourceUpdated(string $uri): void + { + if (!$this->notificationsEnabled || !$this->clientStateManager) { + return; + } + + $subscribers = $this->clientStateManager->getResourceSubscribers($uri); + if (empty($subscribers)) { + return; + } + $notification = Notification::make('notifications/resources/updated', ['uri' => $uri]); + + $framedMessage = json_encode($notification, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . "\n"; + if ($framedMessage === false || $framedMessage === "\n") { + $this->logger->error('Failed to encode resource/updated notification.', ['uri' => $uri]); + return; + } + + foreach ($subscribers as $clientId) { + $this->clientStateManager->queueMessage($clientId, $framedMessage); + } + } + + /** @deprecated */ + public function setToolsChangedNotifier(?callable $notifier): void {} + + /** @deprecated */ + public function setResourcesChangedNotifier(?callable $notifier): void {} + + /** @deprecated */ + public function setPromptsChangedNotifier(?callable $notifier): void {} + public function registerTool(ToolDefinition $tool, bool $isManual = false): void { $toolName = $tool->getName(); @@ -187,8 +219,8 @@ public function registerTool(ToolDefinition $tool, bool $isManual = false): void unset($this->manualToolNames[$toolName]); } - if (! $exists && $this->notifyToolsChanged) { - ($this->notifyToolsChanged)($tool); + if (! $exists) { + $this->notifyToolsListChanged(); } } @@ -214,8 +246,8 @@ public function registerResource(ResourceDefinition $resource, bool $isManual = unset($this->manualResourceUris[$uri]); } - if (! $exists && $this->notifyResourcesChanged) { - ($this->notifyResourcesChanged)(); + if (! $exists) { + $this->notifyResourcesListChanged(); } } @@ -265,8 +297,8 @@ public function registerPrompt(PromptDefinition $prompt, bool $isManual = false) unset($this->manualPromptNames[$promptName]); } - if (! $exists && $this->notifyPromptsChanged) { - ($this->notifyPromptsChanged)(); + if (! $exists) { + $this->notifyPromptsListChanged(); } } diff --git a/tests/Unit/RegistryTest.php b/tests/Unit/RegistryTest.php index 842c3b6..9e1b6eb 100644 --- a/tests/Unit/RegistryTest.php +++ b/tests/Unit/RegistryTest.php @@ -378,7 +378,7 @@ function getRegistryProperty(Registry $reg, string $propName) // --- Notifier Tests --- -it('default notifiers send messages via ClientStateManager', function () { +it('sends notifications when tools, resources, and prompts are registered', function () { // Arrange $tool = createTestTool('notify-tool'); $resource = createTestResource('notify://res'); @@ -392,18 +392,12 @@ function getRegistryProperty(Registry $reg, string $propName) $this->registry->registerPrompt($prompt); }); -it('custom notifiers can be set and are called', function () { +it('does not send notifications when notifications are disabled', function () { // Arrange - $toolNotifierCalled = false; - $this->registry->setToolsChangedNotifier(function () use (&$toolNotifierCalled) { - $toolNotifierCalled = true; - }); + $this->registry->disableNotifications(); $this->clientStateManager->shouldNotReceive('queueMessageForAll'); // Act - $this->registry->registerTool(createTestTool('custom-notify')); - - // Assert - expect($toolNotifierCalled)->toBeTrue(); + $this->registry->registerTool(createTestTool('notify-tool')); });