diff --git a/src/Capability/Registry.php b/src/Capability/Registry.php index 2a327ae4..0e579536 100644 --- a/src/Capability/Registry.php +++ b/src/Capability/Registry.php @@ -161,36 +161,10 @@ public function registerPrompt( public function clear(): void { - $clearCount = 0; - - foreach ($this->tools as $name => $tool) { - if (!$tool->isManual) { - unset($this->tools[$name]); - ++$clearCount; - } - } - foreach ($this->resources as $uri => $resource) { - if (!$resource->isManual) { - unset($this->resources[$uri]); - ++$clearCount; - } - } - foreach ($this->prompts as $name => $prompt) { - if (!$prompt->isManual) { - unset($this->prompts[$name]); - ++$clearCount; - } - } - foreach ($this->resourceTemplates as $uriTemplate => $template) { - if (!$template->isManual) { - unset($this->resourceTemplates[$uriTemplate]); - ++$clearCount; - } - } - - if ($clearCount > 0) { - $this->logger->debug(\sprintf('Removed %d discovered elements from internal registry.', $clearCount)); - } + $this->tools = array_filter($this->tools, static fn ($t) => !$t->isDiscovered); + $this->resources = array_filter($this->resources, static fn ($r) => !$r->isDiscovered); + $this->prompts = array_filter($this->prompts, static fn ($p) => !$p->isDiscovered); + $this->resourceTemplates = array_filter($this->resourceTemplates, static fn ($t) => !$t->isDiscovered); } public function hasTools(): bool @@ -344,10 +318,10 @@ public function getPrompt(string $name): PromptReference public function getDiscoveryState(): DiscoveryState { return new DiscoveryState( - tools: array_filter($this->tools, static fn ($tool) => !$tool->isManual), - resources: array_filter($this->resources, static fn ($resource) => !$resource->isManual), - prompts: array_filter($this->prompts, static fn ($prompt) => !$prompt->isManual), - resourceTemplates: array_filter($this->resourceTemplates, static fn ($template) => !$template->isManual), + tools: array_filter($this->tools, static fn ($t) => $t->isDiscovered), + resources: array_filter($this->resources, static fn ($r) => $r->isDiscovered), + prompts: array_filter($this->prompts, static fn ($p) => $p->isDiscovered), + resourceTemplates: array_filter($this->resourceTemplates, static fn ($t) => $t->isDiscovered), ); } @@ -360,21 +334,29 @@ public function setDiscoveryState(DiscoveryState $state): void // Clear existing discovered elements $this->clear(); - // Import new discovered elements + // Import new discovered elements — skip any that conflict with manual or dynamic registrations foreach ($state->getTools() as $name => $tool) { - $this->tools[$name] = $tool; + if (!isset($this->tools[$name])) { + $this->tools[$name] = new ToolReference($tool->tool, $tool->handler, isDiscovered: true); + } } foreach ($state->getResources() as $uri => $resource) { - $this->resources[$uri] = $resource; + if (!isset($this->resources[$uri])) { + $this->resources[$uri] = new ResourceReference($resource->resource, $resource->handler, isDiscovered: true); + } } foreach ($state->getPrompts() as $name => $prompt) { - $this->prompts[$name] = $prompt; + if (!isset($this->prompts[$name])) { + $this->prompts[$name] = new PromptReference($prompt->prompt, $prompt->handler, completionProviders: $prompt->completionProviders, isDiscovered: true); + } } foreach ($state->getResourceTemplates() as $uriTemplate => $template) { - $this->resourceTemplates[$uriTemplate] = $template; + if (!isset($this->resourceTemplates[$uriTemplate])) { + $this->resourceTemplates[$uriTemplate] = new ResourceTemplateReference($template->resourceTemplate, $template->handler, completionProviders: $template->completionProviders, isDiscovered: true); + } } // Dispatch events for the imported elements diff --git a/src/Capability/Registry/ElementReference.php b/src/Capability/Registry/ElementReference.php index 6425ba13..9fd25764 100644 --- a/src/Capability/Registry/ElementReference.php +++ b/src/Capability/Registry/ElementReference.php @@ -24,6 +24,7 @@ class ElementReference public function __construct( public readonly \Closure|array|string $handler, public readonly bool $isManual = false, + public readonly bool $isDiscovered = false, ) { } } diff --git a/src/Capability/Registry/PromptReference.php b/src/Capability/Registry/PromptReference.php index 5de3a195..345ec03d 100644 --- a/src/Capability/Registry/PromptReference.php +++ b/src/Capability/Registry/PromptReference.php @@ -31,8 +31,9 @@ public function __construct( \Closure|array|string $handler, bool $isManual = false, public readonly array $completionProviders = [], + bool $isDiscovered = false, ) { - parent::__construct($handler, $isManual); + parent::__construct($handler, $isManual, $isDiscovered); } /** diff --git a/src/Capability/Registry/ResourceReference.php b/src/Capability/Registry/ResourceReference.php index d65f461e..3f717e81 100644 --- a/src/Capability/Registry/ResourceReference.php +++ b/src/Capability/Registry/ResourceReference.php @@ -29,8 +29,9 @@ public function __construct( public readonly Resource $resource, callable|array|string $handler, bool $isManual = false, + bool $isDiscovered = false, ) { - parent::__construct($handler, $isManual); + parent::__construct($handler, $isManual, $isDiscovered); } /** diff --git a/src/Capability/Registry/ResourceTemplateReference.php b/src/Capability/Registry/ResourceTemplateReference.php index ef2d915a..9483fb23 100644 --- a/src/Capability/Registry/ResourceTemplateReference.php +++ b/src/Capability/Registry/ResourceTemplateReference.php @@ -38,8 +38,9 @@ public function __construct( callable|array|string $handler, bool $isManual = false, public readonly array $completionProviders = [], + bool $isDiscovered = false, ) { - parent::__construct($handler, $isManual); + parent::__construct($handler, $isManual, $isDiscovered); $this->compileTemplate(); } diff --git a/src/Capability/Registry/ToolReference.php b/src/Capability/Registry/ToolReference.php index 9aa5a3c9..ba9af0bc 100644 --- a/src/Capability/Registry/ToolReference.php +++ b/src/Capability/Registry/ToolReference.php @@ -29,8 +29,9 @@ public function __construct( public readonly Tool $tool, callable|array|string $handler, bool $isManual = false, + bool $isDiscovered = false, ) { - parent::__construct($handler, $isManual); + parent::__construct($handler, $isManual, $isDiscovered); } /** diff --git a/tests/Inspector/Http/snapshots/HttpCombinedRegistrationTest-resources_list.json b/tests/Inspector/Http/snapshots/HttpCombinedRegistrationTest-resources_list.json index 2c7912e4..f3646e57 100644 --- a/tests/Inspector/Http/snapshots/HttpCombinedRegistrationTest-resources_list.json +++ b/tests/Inspector/Http/snapshots/HttpCombinedRegistrationTest-resources_list.json @@ -1,9 +1,9 @@ { "resources": [ { - "name": "priority_config_discovered", + "name": "priority_config_manual", "uri": "config://priority", - "description": "A resource discovered via attributes.\n\nThis will be overridden by a manual registration with the same URI." + "description": "Manually registered resource that overrides a discovered one." } ] } diff --git a/tests/Inspector/Http/snapshots/HttpCombinedRegistrationTest-resources_read-config_priority.json b/tests/Inspector/Http/snapshots/HttpCombinedRegistrationTest-resources_read-config_priority.json index 053dcceb..85eef65a 100644 --- a/tests/Inspector/Http/snapshots/HttpCombinedRegistrationTest-resources_read-config_priority.json +++ b/tests/Inspector/Http/snapshots/HttpCombinedRegistrationTest-resources_read-config_priority.json @@ -3,7 +3,7 @@ { "uri": "config://priority", "mimeType": "text/plain", - "text": "Discovered Priority Config: Low" + "text": "Manual Priority Config: HIGH (overrides discovered)" } ] } diff --git a/tests/Unit/Capability/Registry/RegistryTest.php b/tests/Unit/Capability/Registry/RegistryTest.php new file mode 100644 index 00000000..dd861bf9 --- /dev/null +++ b/tests/Unit/Capability/Registry/RegistryTest.php @@ -0,0 +1,431 @@ +logger = $this->createMock(LoggerInterface::class); + $this->registry = new Registry(null, $this->logger); + } + + public function testRegisterToolWithManualFlag(): void + { + $tool = $this->createValidTool('test_tool'); + $handler = fn () => 'result'; + + $this->registry->registerTool($tool, $handler, true); + + $toolRef = $this->registry->getTool('test_tool'); + $this->assertTrue($toolRef->isManual); + } + + public function testRegisterToolIgnoresDiscoveredWhenManualExists(): void + { + $manualTool = $this->createValidTool('test_tool'); + $discoveredTool = $this->createValidTool('test_tool'); + + $this->registry->registerTool($manualTool, fn () => 'manual', true); + + $this->logger + ->expects($this->once()) + ->method('debug') + ->with('Ignoring discovered tool "test_tool" as it conflicts with a manually registered one.'); + + $this->registry->registerTool($discoveredTool, fn () => 'discovered', false); + + $toolRef = $this->registry->getTool('test_tool'); + $this->assertTrue($toolRef->isManual); + } + + public function testRegisterToolOverridesDiscoveredWithManual(): void + { + $discoveredTool = $this->createValidTool('test_tool'); + $manualTool = $this->createValidTool('test_tool'); + + $this->registry->registerTool($discoveredTool, fn () => 'discovered', false); + $this->registry->registerTool($manualTool, fn () => 'manual', true); + + $toolRef = $this->registry->getTool('test_tool'); + $this->assertTrue($toolRef->isManual); + } + + public function testRegisterResourceWithManualFlag(): void + { + $resource = $this->createValidResource('test://resource'); + $handler = fn () => 'content'; + + $this->registry->registerResource($resource, $handler, true); + + $resourceRef = $this->registry->getResource('test://resource'); + $this->assertTrue($resourceRef->isManual); + } + + public function testRegisterResourceIgnoresDiscoveredWhenManualExists(): void + { + $manualResource = $this->createValidResource('test://resource'); + $discoveredResource = $this->createValidResource('test://resource'); + + $this->registry->registerResource($manualResource, fn () => 'manual', true); + + $this->logger + ->expects($this->once()) + ->method('debug') + ->with('Ignoring discovered resource "test://resource" as it conflicts with a manually registered one.'); + + $this->registry->registerResource($discoveredResource, fn () => 'discovered', false); + + $resourceRef = $this->registry->getResource('test://resource'); + $this->assertTrue($resourceRef->isManual); + } + + public function testRegisterResourceTemplateWithCompletionProviders(): void + { + $template = $this->createValidResourceTemplate('test://{id}'); + $completionProviders = ['id' => EnumCompletionProvider::class]; + + $this->registry->registerResourceTemplate($template, fn () => 'content', $completionProviders); + + $templateRef = $this->registry->getResourceTemplate('test://{id}'); + $this->assertEquals($completionProviders, $templateRef->completionProviders); + } + + public function testRegisterResourceTemplateIgnoresDiscoveredWhenManualExists(): void + { + $manualTemplate = $this->createValidResourceTemplate('test://{id}'); + $discoveredTemplate = $this->createValidResourceTemplate('test://{id}'); + + $this->registry->registerResourceTemplate($manualTemplate, fn () => 'manual', [], true); + + $this->logger + ->expects($this->once()) + ->method('debug') + ->with('Ignoring discovered template "test://{id}" as it conflicts with a manually registered one.'); + + $this->registry->registerResourceTemplate($discoveredTemplate, fn () => 'discovered', [], false); + + $templateRef = $this->registry->getResourceTemplate('test://{id}'); + $this->assertTrue($templateRef->isManual); + } + + public function testRegisterPromptWithCompletionProviders(): void + { + $prompt = $this->createValidPrompt('test_prompt'); + $completionProviders = ['param' => EnumCompletionProvider::class]; + + $this->registry->registerPrompt($prompt, fn () => [], $completionProviders); + + $promptRef = $this->registry->getPrompt('test_prompt'); + $this->assertEquals($completionProviders, $promptRef->completionProviders); + } + + public function testRegisterPromptIgnoresDiscoveredWhenManualExists(): void + { + $manualPrompt = $this->createValidPrompt('test_prompt'); + $discoveredPrompt = $this->createValidPrompt('test_prompt'); + + $this->registry->registerPrompt($manualPrompt, fn () => 'manual', [], true); + + $this->logger + ->expects($this->once()) + ->method('debug') + ->with('Ignoring discovered prompt "test_prompt" as it conflicts with a manually registered one.'); + + $this->registry->registerPrompt($discoveredPrompt, fn () => 'discovered', [], false); + + $promptRef = $this->registry->getPrompt('test_prompt'); + $this->assertTrue($promptRef->isManual); + } + + public function testClearRemovesOnlyDiscoveredElements(): void + { + $manualTool = $this->createValidTool('manual_tool'); + $discoveredTool = $this->createValidTool('discovered_tool'); + $manualResource = $this->createValidResource('test://manual'); + $discoveredResource = $this->createValidResource('test://discovered'); + $manualPrompt = $this->createValidPrompt('manual_prompt'); + $discoveredPrompt = $this->createValidPrompt('discovered_prompt'); + $manualTemplate = $this->createValidResourceTemplate('manual://{id}'); + $discoveredTemplate = $this->createValidResourceTemplate('discovered://{id}'); + + // Register manual elements directly + $this->registry->registerTool($manualTool, fn () => 'manual', true); + $this->registry->registerResource($manualResource, fn () => 'manual', true); + $this->registry->registerPrompt($manualPrompt, fn () => [], [], true); + $this->registry->registerResourceTemplate($manualTemplate, fn () => 'manual', [], true); + + // Import discovered elements via setDiscoveryState + $this->registry->setDiscoveryState(new DiscoveryState( + tools: ['discovered_tool' => new ToolReference($discoveredTool, fn () => 'discovered', false)], + resources: ['test://discovered' => new ResourceReference($discoveredResource, fn () => 'discovered', false)], + prompts: ['discovered_prompt' => new PromptReference($discoveredPrompt, fn () => [], false)], + resourceTemplates: ['discovered://{id}' => new ResourceTemplateReference($discoveredTemplate, fn () => 'discovered', false)], + )); + + $this->registry->clear(); + + // Manual elements survive + $this->assertNotNull($this->registry->getTool('manual_tool')); + $this->assertNotNull($this->registry->getResource('test://manual')); + $this->assertNotNull($this->registry->getPrompt('manual_prompt')); + $this->assertNotNull($this->registry->getResourceTemplate('manual://{id}')); + + // Discovered elements are gone + $this->assertException(ToolNotFoundException::class, fn () => $this->registry->getTool('discovered_tool')); + $this->assertException(ResourceNotFoundException::class, fn () => $this->registry->getResource('test://discovered', false)); + $this->assertException(PromptNotFoundException::class, fn () => $this->registry->getPrompt('discovered_prompt')); + $this->assertException(ResourceNotFoundException::class, fn () => $this->registry->getResourceTemplate('discovered://{id}')); + } + + public function testRegisterToolHandlesStringHandler(): void + { + $tool = $this->createValidTool('test_tool'); + $handler = 'TestClass::testMethod'; + + $this->registry->registerTool($tool, $handler); + + $toolRef = $this->registry->getTool('test_tool'); + $this->assertEquals($handler, $toolRef->handler); + } + + public function testRegisterToolHandlesArrayHandler(): void + { + $tool = $this->createValidTool('test_tool'); + $handler = ['TestClass', 'testMethod']; + + $this->registry->registerTool($tool, $handler); + + $toolRef = $this->registry->getTool('test_tool'); + $this->assertEquals($handler, $toolRef->handler); + } + + public function testRegisterResourceHandlesCallableHandler(): void + { + $resource = $this->createValidResource('test://resource'); + $handler = fn () => 'content'; + + $this->registry->registerResource($resource, $handler); + + $resourceRef = $this->registry->getResource('test://resource'); + $this->assertEquals($handler, $resourceRef->handler); + } + + public function testMultipleRegistrationsOfSameElementWithSameType(): void + { + $tool1 = $this->createValidTool('test_tool'); + $tool2 = $this->createValidTool('test_tool'); + + $this->registry->registerTool($tool1, fn () => 'first', false); + $this->registry->registerTool($tool2, fn () => 'second', false); + + // Second registration should override the first + $toolRef = $this->registry->getTool('test_tool'); + $this->assertEquals('second', ($toolRef->handler)()); + } + + public function testClearPreservesDynamicallyRegisteredElements(): void + { + // 1. Register a manual tool + $manualTool = $this->createValidTool('manual_tool'); + $this->registry->registerTool($manualTool, fn () => 'manual', true); + + // 2. Import discovered tools via setDiscoveryState + $discoveredTool = $this->createValidTool('discovered_tool'); + $this->registry->setDiscoveryState(new DiscoveryState( + tools: ['discovered_tool' => new ToolReference($discoveredTool, fn () => 'discovered', false)], + )); + + // 3. Register a dynamic tool (not manual, not discovered) + $dynamicTool = $this->createValidTool('dynamic_tool'); + $this->registry->registerTool($dynamicTool, fn () => 'dynamic', false); + + // 4. Restore discovery state again (simulates next HTTP request) + $discoveredTool2 = $this->createValidTool('discovered_tool_v2'); + $this->registry->setDiscoveryState(new DiscoveryState( + tools: ['discovered_tool_v2' => new ToolReference($discoveredTool2, fn () => 'discovered_v2', false)], + )); + + // Manual tool survives + $this->assertNotNull($this->registry->getTool('manual_tool')); + // Dynamic tool survives + $this->assertNotNull($this->registry->getTool('dynamic_tool')); + // Old discovered tool is gone + $this->assertException(ToolNotFoundException::class, fn () => $this->registry->getTool('discovered_tool')); + // New discovered tool is present + $this->assertNotNull($this->registry->getTool('discovered_tool_v2')); + } + + public function testGetDiscoveryStateExcludesDynamicTools(): void + { + // Import discovered tool via setDiscoveryState + $discoveredTool = $this->createValidTool('discovered_tool'); + $this->registry->setDiscoveryState(new DiscoveryState( + tools: ['discovered_tool' => new ToolReference($discoveredTool, fn () => 'discovered', false)], + )); + + // Register a dynamic tool + $dynamicTool = $this->createValidTool('dynamic_tool'); + $this->registry->registerTool($dynamicTool, fn () => 'dynamic', false); + + // Register a manual tool + $manualTool = $this->createValidTool('manual_tool'); + $this->registry->registerTool($manualTool, fn () => 'manual', true); + + $state = $this->registry->getDiscoveryState(); + + $this->assertArrayHasKey('discovered_tool', $state->getTools()); + $this->assertArrayNotHasKey('dynamic_tool', $state->getTools()); + $this->assertArrayNotHasKey('manual_tool', $state->getTools()); + } + + public function testSetDiscoveryStateRoundTrip(): void + { + // Import initial discovered state + $tool = $this->createValidTool('round_trip_tool'); + $resource = $this->createValidResource('test://round-trip'); + $prompt = $this->createValidPrompt('round_trip_prompt'); + $template = $this->createValidResourceTemplate('round-trip://{id}'); + + $initialState = new DiscoveryState( + tools: ['round_trip_tool' => new ToolReference($tool, fn () => 'result', false)], + resources: ['test://round-trip' => new ResourceReference($resource, fn () => 'content', false)], + prompts: ['round_trip_prompt' => new PromptReference($prompt, fn () => [], false)], + resourceTemplates: ['round-trip://{id}' => new ResourceTemplateReference($template, fn () => 'tpl', false)], + ); + + $this->registry->setDiscoveryState($initialState); + + // Round-trip: get and set again + $exportedState = $this->registry->getDiscoveryState(); + $this->registry->setDiscoveryState($exportedState); + + // All elements still present + $this->assertNotNull($this->registry->getTool('round_trip_tool')); + $this->assertNotNull($this->registry->getResource('test://round-trip')); + $this->assertNotNull($this->registry->getPrompt('round_trip_prompt')); + $this->assertNotNull($this->registry->getResourceTemplate('round-trip://{id}')); + + // Exported state matches + $reExportedState = $this->registry->getDiscoveryState(); + $this->assertCount(\count($exportedState->getTools()), $reExportedState->getTools()); + $this->assertCount(\count($exportedState->getResources()), $reExportedState->getResources()); + $this->assertCount(\count($exportedState->getPrompts()), $reExportedState->getPrompts()); + $this->assertCount(\count($exportedState->getResourceTemplates()), $reExportedState->getResourceTemplates()); + } + + public function testSetDiscoveryStateDoesNotOverwriteManualOrDynamicTools(): void + { + // Register a manual tool + $manualTool = $this->createValidTool('conflict_tool'); + $this->registry->registerTool($manualTool, fn () => 'manual_result', true); + + // Register a dynamic tool + $dynamicTool = $this->createValidTool('dynamic_conflict'); + $this->registry->registerTool($dynamicTool, fn () => 'dynamic_result', false); + + // Try to import discovered tools with same names + $discoveredConflict = $this->createValidTool('conflict_tool'); + $discoveredDynConflict = $this->createValidTool('dynamic_conflict'); + $this->registry->setDiscoveryState(new DiscoveryState( + tools: [ + 'conflict_tool' => new ToolReference($discoveredConflict, fn () => 'discovered_result', false), + 'dynamic_conflict' => new ToolReference($discoveredDynConflict, fn () => 'discovered_dyn_result', false), + ], + )); + + // Manual tool preserved with original handler + $manualRef = $this->registry->getTool('conflict_tool'); + $this->assertTrue($manualRef->isManual); + $this->assertEquals('manual_result', ($manualRef->handler)()); + + // Dynamic tool preserved with original handler + $dynamicRef = $this->registry->getTool('dynamic_conflict'); + $this->assertEquals('dynamic_result', ($dynamicRef->handler)()); + } + + private function createValidTool(string $name): Tool + { + return new Tool( + name: $name, + inputSchema: [ + 'type' => 'object', + 'properties' => [ + 'param' => ['type' => 'string'], + ], + 'required' => null, + ], + description: "Test tool: {$name}", + annotations: null, + ); + } + + private function createValidResource(string $uri): Resource + { + return new Resource( + uri: $uri, + name: 'test_resource', + description: 'Test resource', + mimeType: 'text/plain', + ); + } + + private function createValidResourceTemplate(string $uriTemplate): ResourceTemplate + { + return new ResourceTemplate( + uriTemplate: $uriTemplate, + name: 'test_template', + description: 'Test resource template', + mimeType: 'text/plain', + ); + } + + private function createValidPrompt(string $name): Prompt + { + return new Prompt( + name: $name, + description: "Test prompt: {$name}", + arguments: [], + ); + } + + private function assertException(string $exceptionClass, callable $callback): void + { + try { + $callback(); + $this->fail(\sprintf('Expected exception %s was not thrown.', $exceptionClass)); + } catch (\Throwable $e) { + $this->assertInstanceOf($exceptionClass, $e); + } + } +} diff --git a/tests/Unit/Capability/RegistryTest.php b/tests/Unit/Capability/RegistryTest.php index e8b19585..eb475c85 100644 --- a/tests/Unit/Capability/RegistryTest.php +++ b/tests/Unit/Capability/RegistryTest.php @@ -12,6 +12,7 @@ namespace Mcp\Tests\Unit\Capability; use Mcp\Capability\Completion\EnumCompletionProvider; +use Mcp\Capability\Discovery\DiscoveryState; use Mcp\Capability\Registry; use Mcp\Capability\Registry\PromptReference; use Mcp\Capability\Registry\ResourceReference; @@ -426,59 +427,73 @@ public function testClearRemovesOnlyDiscoveredElements(): void $manualTemplate = $this->createValidResourceTemplate('manual://{id}'); $discoveredTemplate = $this->createValidResourceTemplate('discovered://{id}'); + // Register manual elements directly $this->registry->registerTool($manualTool, static fn () => 'manual', true); - $this->registry->registerTool($discoveredTool, static fn () => 'discovered'); $this->registry->registerResource($manualResource, static fn () => 'manual', true); - $this->registry->registerResource($discoveredResource, static fn () => 'discovered'); $this->registry->registerPrompt($manualPrompt, static fn () => [], [], true); - $this->registry->registerPrompt($discoveredPrompt, static fn () => []); $this->registry->registerResourceTemplate($manualTemplate, static fn () => 'manual', [], true); - $this->registry->registerResourceTemplate($discoveredTemplate, static fn () => 'discovered'); - // Test that all elements exist - $this->registry->getTool('manual_tool'); - $this->registry->getResource('test://manual'); - $this->registry->getPrompt('manual_prompt'); - $this->registry->getResourceTemplate('manual://{id}'); - $this->registry->getTool('discovered_tool'); - $this->registry->getResource('test://discovered'); - $this->registry->getPrompt('discovered_prompt'); - $this->registry->getResourceTemplate('discovered://{id}'); + // Import discovered elements via setDiscoveryState + $this->registry->setDiscoveryState(new DiscoveryState( + tools: ['discovered_tool' => new ToolReference($discoveredTool, static fn () => 'discovered')], + resources: ['test://discovered' => new ResourceReference($discoveredResource, static fn () => 'discovered')], + prompts: ['discovered_prompt' => new PromptReference($discoveredPrompt, static fn () => [])], + resourceTemplates: ['discovered://{id}' => new ResourceTemplateReference($discoveredTemplate, static fn () => 'discovered')], + )); + + // All elements exist before clear + $this->assertInstanceOf(ToolReference::class, $this->registry->getTool('discovered_tool')); $this->registry->clear(); - // Manual elements should still exist - $this->registry->getTool('manual_tool'); - $this->registry->getResource('test://manual'); - $this->registry->getPrompt('manual_prompt'); - $this->registry->getResourceTemplate('manual://{id}'); + // Manual elements survive + $this->assertInstanceOf(ToolReference::class, $this->registry->getTool('manual_tool')); + $this->assertInstanceOf(ResourceReference::class, $this->registry->getResource('test://manual')); + $this->assertInstanceOf(PromptReference::class, $this->registry->getPrompt('manual_prompt')); + $this->assertInstanceOf(ResourceTemplateReference::class, $this->registry->getResourceTemplate('manual://{id}')); - // Test that all discovered elements throw exceptions + // Discovered elements are gone $this->expectException(ToolNotFoundException::class); $this->registry->getTool('discovered_tool'); + } + + public function testClearRemovesDiscoveredResources(): void + { + $discoveredResource = $this->createValidResource('test://discovered'); + $this->registry->setDiscoveryState(new DiscoveryState( + resources: ['test://discovered' => new ResourceReference($discoveredResource, static fn () => 'discovered')], + )); + + $this->registry->clear(); $this->expectException(ResourceNotFoundException::class); $this->registry->getResource('test://discovered'); + } + + public function testClearRemovesDiscoveredPrompts(): void + { + $discoveredPrompt = $this->createValidPrompt('discovered_prompt'); + $this->registry->setDiscoveryState(new DiscoveryState( + prompts: ['discovered_prompt' => new PromptReference($discoveredPrompt, static fn () => [])], + )); + + $this->registry->clear(); $this->expectException(PromptNotFoundException::class); $this->registry->getPrompt('discovered_prompt'); - - $this->expectException(ResourceNotFoundException::class); - $this->registry->getResourceTemplate('discovered://{id}'); } - public function testClearLogsNothingWhenNoDiscoveredElements(): void + public function testClearRemovesDiscoveredResourceTemplates(): void { - $manualTool = $this->createValidTool('manual_tool'); - $this->registry->registerTool($manualTool, static fn () => 'manual', true); - - $this->logger - ->expects($this->never()) - ->method('debug'); + $discoveredTemplate = $this->createValidResourceTemplate('discovered://{id}'); + $this->registry->setDiscoveryState(new DiscoveryState( + resourceTemplates: ['discovered://{id}' => new ResourceTemplateReference($discoveredTemplate, static fn () => 'discovered')], + )); $this->registry->clear(); - $this->registry->getTool('manual_tool'); + $this->expectException(ResourceNotFoundException::class); + $this->registry->getResourceTemplate('discovered://{id}'); } public function testRegisterToolHandlesStringHandler(): void