Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 20 additions & 8 deletions src/Capability/Registry.php
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public function registerTool(Tool $tool, callable|array|string $handler): ToolRe
$reference = new ToolReference($tool, $handler);
$this->tools[$tool->name] = $reference;

$this->eventDispatcher?->dispatch(new ToolListChangedEvent());
$this->dispatch(new ToolListChangedEvent());

return $reference;
}
Expand All @@ -118,7 +118,7 @@ public function registerResource(ResourceDefinition $resource, callable|array|st
$reference = new ResourceReference($resource, $handler);
$this->resources[$resource->uri] = $reference;

$this->eventDispatcher?->dispatch(new ResourceListChangedEvent());
$this->dispatch(new ResourceListChangedEvent());

return $reference;
}
Expand All @@ -131,7 +131,7 @@ public function registerResourceTemplate(
$reference = new ResourceTemplateReference($template, $handler, $completionProviders);
$this->resourceTemplates[$template->uriTemplate] = $reference;

$this->eventDispatcher?->dispatch(new ResourceTemplateListChangedEvent());
$this->dispatch(new ResourceTemplateListChangedEvent());

return $reference;
}
Expand All @@ -144,7 +144,7 @@ public function registerPrompt(
$reference = new PromptReference($prompt, $handler, $completionProviders);
$this->prompts[$prompt->name] = $reference;

$this->eventDispatcher?->dispatch(new PromptListChangedEvent());
$this->dispatch(new PromptListChangedEvent());

return $reference;
}
Expand All @@ -157,7 +157,7 @@ public function unregisterTool(string $name): void

unset($this->tools[$name]);

$this->eventDispatcher?->dispatch(new ToolListChangedEvent());
$this->dispatch(new ToolListChangedEvent());
}

public function unregisterResource(string $uri): void
Expand All @@ -168,7 +168,7 @@ public function unregisterResource(string $uri): void

unset($this->resources[$uri]);

$this->eventDispatcher?->dispatch(new ResourceListChangedEvent());
$this->dispatch(new ResourceListChangedEvent());
}

public function unregisterResourceTemplate(string $uriTemplate): void
Expand All @@ -179,7 +179,7 @@ public function unregisterResourceTemplate(string $uriTemplate): void

unset($this->resourceTemplates[$uriTemplate]);

$this->eventDispatcher?->dispatch(new ResourceTemplateListChangedEvent());
$this->dispatch(new ResourceTemplateListChangedEvent());
}

public function unregisterPrompt(string $name): void
Expand All @@ -190,7 +190,7 @@ public function unregisterPrompt(string $name): void

unset($this->prompts[$name]);

$this->eventDispatcher?->dispatch(new PromptListChangedEvent());
$this->dispatch(new PromptListChangedEvent());
}

public function hasTool(string $name): bool
Expand Down Expand Up @@ -390,6 +390,18 @@ public function getPrompt(string $name): PromptReference
return $this->prompts[$name] ?? throw new PromptNotFoundException($name);
}

/**
* Suppressed while the deferred loader runs, since it only sets up initial state.
*/
private function dispatch(object $event): void
{
if ($this->loading) {
return;
}

$this->eventDispatcher?->dispatch($event);
}

/**
* Calculate next cursor for pagination.
*
Expand Down
46 changes: 46 additions & 0 deletions tests/Unit/Capability/RegistryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use Mcp\Capability\Registry\ResourceTemplateReference;
use Mcp\Capability\Registry\ToolReference;
use Mcp\Capability\RegistryInterface;
use Mcp\Event\ToolListChangedEvent;
use Mcp\Exception\PromptNotFoundException;
use Mcp\Exception\ResourceNotFoundException;
use Mcp\Exception\ToolNotFoundException;
Expand All @@ -31,6 +32,7 @@
use Mcp\Schema\Tool;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\Log\LoggerInterface;

class RegistryTest extends TestCase
Expand Down Expand Up @@ -722,6 +724,50 @@ public function load(RegistryInterface $registry): void
$this->assertArrayHasKey('loaded', $registry->getTools()->references);
}

public function testListChangedEventsAreSuppressedDuringTheDeferredLoad(): void
{
$eventDispatcher = $this->createMock(EventDispatcherInterface::class);
$eventDispatcher->expects($this->never())->method('dispatch');

$loader = new class($this->createValidTool('loaded'), $this->createValidResource('file:///loaded'), $this->createValidResourceTemplate('file:///loaded/{id}'), $this->createValidPrompt('loaded_prompt')) implements LoaderInterface {
public function __construct(
private readonly Tool $tool,
private readonly ResourceDefinition $resource,
private readonly ResourceTemplate $template,
private readonly Prompt $prompt,
) {
}

public function load(RegistryInterface $registry): void
{
$registry->registerTool($this->tool, 'handler');
$registry->registerResource($this->resource, 'handler');
$registry->registerResourceTemplate($this->template, 'handler');
$registry->registerPrompt($this->prompt, 'handler');
}
};

$registry = new Registry($eventDispatcher, $this->logger, loader: $loader);

$this->assertTrue($registry->hasTools());
$this->assertTrue($registry->hasPrompts());
}

public function testListChangedEventsAreStillDispatchedForRuntimeRegistrations(): void
{
$eventDispatcher = $this->createMock(EventDispatcherInterface::class);
$eventDispatcher->expects($this->once())
->method('dispatch')
->with($this->isInstanceOf(ToolListChangedEvent::class))
->willReturnArgument(0);

$loader = $this->toolLoader($this->createValidTool('loaded'));
$registry = new Registry($eventDispatcher, $this->logger, loader: $loader);
$registry->load();

$registry->registerTool($this->createValidTool('runtime'), 'handler');
}

public function testLoadRunsTheConfiguredLoaderEagerly(): void
{
$loader = $this->createMock(LoaderInterface::class);
Expand Down