Skip to content

Commit 49318b9

Browse files
fix(registry): Prevent excessive list_changed notifications
1 parent 5a22112 commit 49318b9

File tree

1 file changed

+82
-50
lines changed

1 file changed

+82
-50
lines changed

src/Registry.php

Lines changed: 82 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,8 @@ class Registry
5454

5555
private bool $discoveredElementsLoaded = false;
5656

57-
/** @var callable|null */
58-
private $notifyToolsChanged = null;
57+
private bool $notificationsEnabled = true;
5958

60-
/** @var callable|null */
61-
private $notifyResourcesChanged = null;
62-
63-
/** @var callable|null */
64-
private $notifyPromptsChanged = null;
6559

6660
public function __construct(
6761
LoggerInterface $logger,
@@ -73,7 +67,6 @@ public function __construct(
7367
$this->clientStateManager = $clientStateManager;
7468

7569
$this->initializeCollections();
76-
$this->initializeDefaultNotifiers();
7770

7871
if ($this->cache) {
7972
$this->loadDiscoveredElementsFromCache();
@@ -115,54 +108,93 @@ private function initializeCollections(): void
115108
$this->manualTemplateUris = [];
116109
}
117110

118-
private function initializeDefaultNotifiers(): void
111+
public function enableNotifications(): void
119112
{
120-
$this->notifyToolsChanged = function () {
121-
if ($this->clientStateManager) {
122-
$notification = Notification::make('notifications/tools/list_changed');
123-
$framedMessage = json_encode($notification->toArray(), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . "\n";
124-
if ($framedMessage !== false) {
125-
$this->clientStateManager->queueMessageForAll($framedMessage);
126-
}
127-
}
128-
};
129-
130-
$this->notifyResourcesChanged = function () {
131-
if ($this->clientStateManager) {
132-
$notification = Notification::make('notifications/resources/list_changed');
133-
$framedMessage = json_encode($notification->toArray(), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . "\n";
134-
if ($framedMessage !== false) {
135-
$this->clientStateManager->queueMessageForAll($framedMessage);
136-
}
137-
}
138-
};
139-
140-
$this->notifyPromptsChanged = function () {
141-
if ($this->clientStateManager) {
142-
$notification = Notification::make('notifications/prompts/list_changed');
143-
$framedMessage = json_encode($notification->toArray(), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . "\n";
144-
if ($framedMessage !== false) {
145-
$this->clientStateManager->queueMessageForAll($framedMessage);
146-
}
147-
}
148-
};
113+
$this->notificationsEnabled = true;
149114
}
150115

151-
public function setToolsChangedNotifier(?callable $notifier): void
116+
public function disableNotifications(): void
152117
{
153-
$this->notifyToolsChanged = $notifier;
118+
$this->notificationsEnabled = false;
154119
}
155120

156-
public function setResourcesChangedNotifier(?callable $notifier): void
121+
public function notifyToolsChanged(): void
157122
{
158-
$this->notifyResourcesChanged = $notifier;
123+
if (!$this->notificationsEnabled || !$this->clientStateManager) {
124+
return;
125+
}
126+
$notification = Notification::make('notifications/tools/list_changed');
127+
128+
$framedMessage = json_encode($notification, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . "\n";
129+
if ($framedMessage === false || $framedMessage === "\n") {
130+
$this->logger->error('Failed to encode notification for queuing.', ['method' => $notification->method]);
131+
return;
132+
}
133+
$this->clientStateManager->queueMessageForAll($framedMessage);
159134
}
160135

161-
public function setPromptsChangedNotifier(?callable $notifier): void
136+
public function notifyResourcesChanged(): void
162137
{
163-
$this->notifyPromptsChanged = $notifier;
138+
if (!$this->notificationsEnabled || !$this->clientStateManager) {
139+
return;
140+
}
141+
$notification = Notification::make('notifications/resources/list_changed');
142+
143+
$framedMessage = json_encode($notification, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . "\n";
144+
if ($framedMessage === false || $framedMessage === "\n") {
145+
$this->logger->error('Failed to encode notification for queuing.', ['method' => $notification->method]);
146+
return;
147+
}
148+
$this->clientStateManager->queueMessageForAll($framedMessage);
164149
}
165150

151+
public function notifyPromptsChanged(): void
152+
{
153+
if (!$this->notificationsEnabled || !$this->clientStateManager) {
154+
return;
155+
}
156+
$notification = Notification::make('notifications/prompts/list_changed');
157+
158+
$framedMessage = json_encode($notification, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . "\n";
159+
if ($framedMessage === false || $framedMessage === "\n") {
160+
$this->logger->error('Failed to encode notification for queuing.', ['method' => $notification->method]);
161+
return;
162+
}
163+
$this->clientStateManager->queueMessageForAll($framedMessage);
164+
}
165+
166+
public function notifyResourceUpdated(string $uri): void
167+
{
168+
if (!$this->notificationsEnabled || !$this->clientStateManager) {
169+
return;
170+
}
171+
172+
$subscribers = $this->clientStateManager->getResourceSubscribers($uri);
173+
if (empty($subscribers)) {
174+
return;
175+
}
176+
$notification = Notification::make('notifications/resources/updated', ['uri' => $uri]);
177+
178+
$framedMessage = json_encode($notification, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . "\n";
179+
if ($framedMessage === false || $framedMessage === "\n") {
180+
$this->logger->error('Failed to encode resource/updated notification.', ['uri' => $uri]);
181+
return;
182+
}
183+
184+
foreach ($subscribers as $clientId) {
185+
$this->clientStateManager->queueMessage($clientId, $framedMessage);
186+
}
187+
}
188+
189+
/** @deprecated */
190+
public function setToolsChangedNotifier(?callable $notifier): void {}
191+
192+
/** @deprecated */
193+
public function setResourcesChangedNotifier(?callable $notifier): void {}
194+
195+
/** @deprecated */
196+
public function setPromptsChangedNotifier(?callable $notifier): void {}
197+
166198
public function registerTool(ToolDefinition $tool, bool $isManual = false): void
167199
{
168200
$toolName = $tool->getName();
@@ -187,8 +219,8 @@ public function registerTool(ToolDefinition $tool, bool $isManual = false): void
187219
unset($this->manualToolNames[$toolName]);
188220
}
189221

190-
if (! $exists && $this->notifyToolsChanged) {
191-
($this->notifyToolsChanged)($tool);
222+
if (! $exists) {
223+
$this->notifyToolsChanged();
192224
}
193225
}
194226

@@ -214,8 +246,8 @@ public function registerResource(ResourceDefinition $resource, bool $isManual =
214246
unset($this->manualResourceUris[$uri]);
215247
}
216248

217-
if (! $exists && $this->notifyResourcesChanged) {
218-
($this->notifyResourcesChanged)();
249+
if (! $exists) {
250+
$this->notifyResourcesChanged();
219251
}
220252
}
221253

@@ -265,8 +297,8 @@ public function registerPrompt(PromptDefinition $prompt, bool $isManual = false)
265297
unset($this->manualPromptNames[$promptName]);
266298
}
267299

268-
if (! $exists && $this->notifyPromptsChanged) {
269-
($this->notifyPromptsChanged)();
300+
if (! $exists) {
301+
$this->notifyPromptsChanged();
270302
}
271303
}
272304

0 commit comments

Comments
 (0)