|
14 | 14 | use Mcp\Capability\Completion\EnumCompletionProvider; |
15 | 15 | use Mcp\Capability\Completion\ListCompletionProvider; |
16 | 16 | use Mcp\Capability\Discovery\Discoverer; |
17 | | -use Mcp\Capability\Registry; |
18 | | -use Mcp\Capability\Registry\PromptReference; |
19 | | -use Mcp\Capability\Registry\ResourceReference; |
20 | | -use Mcp\Capability\Registry\ResourceTemplateReference; |
21 | 17 | use Mcp\Capability\Registry\ToolReference; |
22 | 18 | use Mcp\Tests\Unit\Capability\Attribute\CompletionProviderFixture; |
23 | 19 | use Mcp\Tests\Unit\Capability\Discovery\Fixtures\DiscoverableToolHandler; |
|
29 | 25 |
|
30 | 26 | class DiscoveryTest extends TestCase |
31 | 27 | { |
32 | | - private Registry $registry; |
33 | 28 | private Discoverer $discoverer; |
34 | 29 |
|
35 | 30 | protected function setUp(): void |
36 | 31 | { |
37 | | - $this->registry = new Registry(); |
38 | | - $this->discoverer = new Discoverer($this->registry); |
| 32 | + $this->discoverer = new Discoverer(); |
39 | 33 | } |
40 | 34 |
|
41 | 35 | public function testDiscoversAllElementTypesCorrectlyFromFixtureFiles() |
42 | 36 | { |
43 | | - $this->discoverer->discover(__DIR__, ['Fixtures']); |
| 37 | + $discovery = $this->discoverer->discover(__DIR__, ['Fixtures']); |
44 | 38 |
|
45 | | - $tools = $this->registry->getTools(); |
| 39 | + $tools = $discovery->getTools(); |
46 | 40 | $this->assertCount(4, $tools); |
47 | 41 |
|
48 | | - $greetUserTool = $this->registry->getTool('greet_user'); |
49 | | - $this->assertInstanceOf(ToolReference::class, $greetUserTool); |
50 | | - $this->assertFalse($greetUserTool->isManual); |
51 | | - $this->assertEquals('greet_user', $greetUserTool->tool->name); |
52 | | - $this->assertEquals('Greets a user by name.', $greetUserTool->tool->description); |
53 | | - $this->assertEquals([DiscoverableToolHandler::class, 'greet'], $greetUserTool->handler); |
54 | | - $this->assertArrayHasKey('name', $greetUserTool->tool->inputSchema['properties'] ?? []); |
55 | | - |
56 | | - $repeatActionTool = $this->registry->getTool('repeatAction'); |
57 | | - $this->assertInstanceOf(ToolReference::class, $repeatActionTool); |
58 | | - $this->assertEquals('A tool with more complex parameters and inferred name/description.', $repeatActionTool->tool->description); |
59 | | - $this->assertTrue($repeatActionTool->tool->annotations->readOnlyHint); |
60 | | - $this->assertEquals(['count', 'loudly', 'mode'], array_keys($repeatActionTool->tool->inputSchema['properties'] ?? [])); |
61 | | - |
62 | | - $invokableCalcTool = $this->registry->getTool('InvokableCalculator'); |
63 | | - $this->assertInstanceOf(ToolReference::class, $invokableCalcTool); |
64 | | - $this->assertFalse($invokableCalcTool->isManual); |
65 | | - $this->assertEquals([InvocableToolFixture::class, '__invoke'], $invokableCalcTool->handler); |
66 | | - |
67 | | - $this->assertNull($this->registry->getTool('private_tool_should_be_ignored')); |
68 | | - $this->assertNull($this->registry->getTool('protected_tool_should_be_ignored')); |
69 | | - $this->assertNull($this->registry->getTool('static_tool_should_be_ignored')); |
70 | | - |
71 | | - $resources = $this->registry->getResources(); |
| 42 | + $this->assertArrayHasKey('greet_user', $tools); |
| 43 | + $this->assertFalse($tools['greet_user']->isManual); |
| 44 | + $this->assertEquals('greet_user', $tools['greet_user']->tool->name); |
| 45 | + $this->assertEquals('Greets a user by name.', $tools['greet_user']->tool->description); |
| 46 | + $this->assertEquals([DiscoverableToolHandler::class, 'greet'], $tools['greet_user']->handler); |
| 47 | + $this->assertArrayHasKey('name', $tools['greet_user']->tool->inputSchema['properties'] ?? []); |
| 48 | + |
| 49 | + $this->assertArrayHasKey('repeatAction', $tools); |
| 50 | + $this->assertEquals('A tool with more complex parameters and inferred name/description.', $tools['repeatAction']->tool->description); |
| 51 | + $this->assertTrue($tools['repeatAction']->tool->annotations->readOnlyHint); |
| 52 | + $this->assertEquals(['count', 'loudly', 'mode'], array_keys($tools['repeatAction']->tool->inputSchema['properties'] ?? [])); |
| 53 | + |
| 54 | + $this->assertArrayHasKey('InvokableCalculator', $tools); |
| 55 | + $this->assertInstanceOf(ToolReference::class, $tools['InvokableCalculator']); |
| 56 | + $this->assertFalse($tools['InvokableCalculator']->isManual); |
| 57 | + $this->assertEquals([InvocableToolFixture::class, '__invoke'], $tools['InvokableCalculator']->handler); |
| 58 | + |
| 59 | + $this->assertArrayNotHasKey('private_tool_should_be_ignored', $tools); |
| 60 | + $this->assertArrayNotHasKey('protected_tool_should_be_ignored', $tools); |
| 61 | + $this->assertArrayNotHasKey('static_tool_should_be_ignored', $tools); |
| 62 | + |
| 63 | + $resources = $discovery->getResources(); |
72 | 64 | $this->assertCount(3, $resources); |
73 | 65 |
|
74 | | - $appVersionRes = $this->registry->getResource('app://info/version'); |
75 | | - $this->assertInstanceOf(ResourceReference::class, $appVersionRes); |
76 | | - $this->assertFalse($appVersionRes->isManual); |
77 | | - $this->assertEquals('app_version', $appVersionRes->schema->name); |
78 | | - $this->assertEquals('text/plain', $appVersionRes->schema->mimeType); |
| 66 | + $this->assertArrayHasKey('app://info/version', $resources); |
| 67 | + $this->assertFalse($resources['app://info/version']->isManual); |
| 68 | + $this->assertEquals('app_version', $resources['app://info/version']->schema->name); |
| 69 | + $this->assertEquals('text/plain', $resources['app://info/version']->schema->mimeType); |
79 | 70 |
|
80 | | - $invokableStatusRes = $this->registry->getResource('invokable://config/status'); |
81 | | - $this->assertInstanceOf(ResourceReference::class, $invokableStatusRes); |
82 | | - $this->assertFalse($invokableStatusRes->isManual); |
83 | | - $this->assertEquals([InvocableResourceFixture::class, '__invoke'], $invokableStatusRes->handler); |
| 71 | + $this->assertArrayHasKey('invokable://config/status', $resources); |
| 72 | + $this->assertFalse($resources['invokable://config/status']->isManual); |
| 73 | + $this->assertEquals([InvocableResourceFixture::class, '__invoke'], $resources['invokable://config/status']->handler); |
84 | 74 |
|
85 | | - $prompts = $this->registry->getPrompts(); |
| 75 | + $prompts = $discovery->getPrompts(); |
86 | 76 | $this->assertCount(4, $prompts); |
87 | 77 |
|
88 | | - $storyPrompt = $this->registry->getPrompt('creative_story_prompt'); |
89 | | - $this->assertInstanceOf(PromptReference::class, $storyPrompt); |
90 | | - $this->assertFalse($storyPrompt->isManual); |
91 | | - $this->assertCount(2, $storyPrompt->prompt->arguments); |
92 | | - $this->assertEquals(CompletionProviderFixture::class, $storyPrompt->completionProviders['genre']); |
| 78 | + $this->assertArrayHasKey('creative_story_prompt', $prompts); |
| 79 | + $this->assertFalse($prompts['creative_story_prompt']->isManual); |
| 80 | + $this->assertCount(2, $prompts['creative_story_prompt']->prompt->arguments); |
| 81 | + $this->assertEquals(CompletionProviderFixture::class, $prompts['creative_story_prompt']->completionProviders['genre']); |
93 | 82 |
|
94 | | - $simplePrompt = $this->registry->getPrompt('simpleQuestionPrompt'); |
95 | | - $this->assertInstanceOf(PromptReference::class, $simplePrompt); |
96 | | - $this->assertFalse($simplePrompt->isManual); |
| 83 | + $this->assertArrayHasKey('simpleQuestionPrompt', $prompts); |
| 84 | + $this->assertFalse($prompts['simpleQuestionPrompt']->isManual); |
97 | 85 |
|
98 | | - $invokableGreeter = $this->registry->getPrompt('InvokableGreeterPrompt'); |
99 | | - $this->assertInstanceOf(PromptReference::class, $invokableGreeter); |
100 | | - $this->assertFalse($invokableGreeter->isManual); |
101 | | - $this->assertEquals([InvocablePromptFixture::class, '__invoke'], $invokableGreeter->handler); |
| 86 | + $this->assertArrayHasKey('InvokableGreeterPrompt', $prompts); |
| 87 | + $this->assertFalse($prompts['InvokableGreeterPrompt']->isManual); |
| 88 | + $this->assertEquals([InvocablePromptFixture::class, '__invoke'], $prompts['InvokableGreeterPrompt']->handler); |
102 | 89 |
|
103 | | - $contentCreatorPrompt = $this->registry->getPrompt('content_creator'); |
104 | | - $this->assertInstanceOf(PromptReference::class, $contentCreatorPrompt); |
105 | | - $this->assertFalse($contentCreatorPrompt->isManual); |
106 | | - $this->assertCount(3, $contentCreatorPrompt->completionProviders); |
| 90 | + $this->assertArrayHasKey('content_creator', $prompts); |
| 91 | + $this->assertFalse($prompts['content_creator']->isManual); |
| 92 | + $this->assertCount(3, $prompts['content_creator']->completionProviders); |
107 | 93 |
|
108 | | - $templates = $this->registry->getResourceTemplates(); |
| 94 | + $templates = $discovery->getResourceTemplates(); |
109 | 95 | $this->assertCount(4, $templates); |
110 | 96 |
|
111 | | - $productTemplate = $this->registry->getResourceTemplate('product://{region}/details/{productId}'); |
112 | | - $this->assertInstanceOf(ResourceTemplateReference::class, $productTemplate); |
113 | | - $this->assertFalse($productTemplate->isManual); |
114 | | - $this->assertEquals('product_details_template', $productTemplate->resourceTemplate->name); |
115 | | - $this->assertEquals(CompletionProviderFixture::class, $productTemplate->completionProviders['region']); |
116 | | - $this->assertEqualsCanonicalizing(['region', 'productId'], $productTemplate->getVariableNames()); |
117 | | - |
118 | | - $invokableUserTemplate = $this->registry->getResourceTemplate('invokable://user-profile/{userId}'); |
119 | | - $this->assertInstanceOf(ResourceTemplateReference::class, $invokableUserTemplate); |
120 | | - $this->assertFalse($invokableUserTemplate->isManual); |
121 | | - $this->assertEquals([InvocableResourceTemplateFixture::class, '__invoke'], $invokableUserTemplate->handler); |
| 97 | + $this->assertArrayHasKey('product://{region}/details/{productId}', $templates); |
| 98 | + $this->assertFalse($templates['product://{region}/details/{productId}']->isManual); |
| 99 | + $this->assertEquals('product_details_template', $templates['product://{region}/details/{productId}']->resourceTemplate->name); |
| 100 | + $this->assertEquals(CompletionProviderFixture::class, $templates['product://{region}/details/{productId}']->completionProviders['region']); |
| 101 | + $this->assertEqualsCanonicalizing(['region', 'productId'], $templates['product://{region}/details/{productId}']->getVariableNames()); |
| 102 | + |
| 103 | + $this->assertArrayHasKey('invokable://user-profile/{userId}', $templates); |
| 104 | + $this->assertFalse($templates['invokable://user-profile/{userId}']->isManual); |
| 105 | + $this->assertEquals([InvocableResourceTemplateFixture::class, '__invoke'], $templates['invokable://user-profile/{userId}']->handler); |
122 | 106 | } |
123 | 107 |
|
124 | 108 | public function testDoesNotDiscoverElementsFromExcludedDirectories() |
125 | 109 | { |
126 | | - $this->discoverer->discover(__DIR__, ['Fixtures']); |
127 | | - $this->assertInstanceOf(ToolReference::class, $this->registry->getTool('hidden_subdir_tool')); |
128 | | - |
129 | | - $this->registry->clear(); |
| 110 | + $discovery = $this->discoverer->discover(__DIR__, ['Fixtures']); |
| 111 | + $this->assertArrayHasKey('hidden_subdir_tool', $discovery->getTools()); |
130 | 112 |
|
131 | | - $this->discoverer->discover(__DIR__, ['Fixtures'], ['SubDir']); |
132 | | - $this->assertNull($this->registry->getTool('hidden_subdir_tool')); |
| 113 | + $discovery = $this->discoverer->discover(__DIR__, ['Fixtures'], ['SubDir']); |
| 114 | + $this->assertArrayNotHasKey('hidden_subdir_tool', $discovery->getTools()); |
133 | 115 | } |
134 | 116 |
|
135 | 117 | public function testHandlesEmptyDirectoriesOrDirectoriesWithNoPhpFiles() |
136 | 118 | { |
137 | | - $this->discoverer->discover(__DIR__, ['EmptyDir']); |
138 | | - $tools = $this->registry->getTools(); |
139 | | - $this->assertEmpty($tools->references); |
| 119 | + $discovery = $this->discoverer->discover(__DIR__, ['EmptyDir']); |
| 120 | + |
| 121 | + $this->assertTrue($discovery->isEmpty()); |
140 | 122 | } |
141 | 123 |
|
142 | 124 | public function testCorrectlyInfersNamesAndDescriptionsFromMethodsOrClassesIfNotSetInAttribute() |
143 | 125 | { |
144 | | - $this->discoverer->discover(__DIR__, ['Fixtures']); |
| 126 | + $discovery = $this->discoverer->discover(__DIR__, ['Fixtures']); |
145 | 127 |
|
146 | | - $repeatActionTool = $this->registry->getTool('repeatAction'); |
147 | | - $this->assertEquals('repeatAction', $repeatActionTool->tool->name); |
148 | | - $this->assertEquals('A tool with more complex parameters and inferred name/description.', $repeatActionTool->tool->description); |
| 128 | + $this->assertArrayHasKey('repeatAction', $tools = $discovery->getTools()); |
| 129 | + $this->assertEquals('repeatAction', $tools['repeatAction']->tool->name); |
| 130 | + $this->assertEquals('A tool with more complex parameters and inferred name/description.', $tools['repeatAction']->tool->description); |
149 | 131 |
|
150 | | - $simplePrompt = $this->registry->getPrompt('simpleQuestionPrompt'); |
151 | | - $this->assertEquals('simpleQuestionPrompt', $simplePrompt->prompt->name); |
152 | | - $this->assertNull($simplePrompt->prompt->description); |
| 132 | + $this->assertArrayHasKey('simpleQuestionPrompt', $prompts = $discovery->getPrompts()); |
| 133 | + $this->assertEquals('simpleQuestionPrompt', $prompts['simpleQuestionPrompt']->prompt->name); |
| 134 | + $this->assertNull($prompts['simpleQuestionPrompt']->prompt->description); |
153 | 135 |
|
154 | | - $invokableCalc = $this->registry->getTool('InvokableCalculator'); |
155 | | - $this->assertEquals('InvokableCalculator', $invokableCalc->tool->name); |
156 | | - $this->assertEquals('An invokable calculator tool.', $invokableCalc->tool->description); |
| 136 | + $this->assertArrayHasKey('InvokableCalculator', $tools); |
| 137 | + $this->assertEquals('InvokableCalculator', $tools['InvokableCalculator']->tool->name); |
| 138 | + $this->assertEquals('An invokable calculator tool.', $tools['InvokableCalculator']->tool->description); |
157 | 139 | } |
158 | 140 |
|
159 | 141 | public function testDiscoversEnhancedCompletionProvidersWithValuesAndEnumAttributes() |
160 | 142 | { |
161 | | - $this->discoverer->discover(__DIR__, ['Fixtures']); |
| 143 | + $discovery = $this->discoverer->discover(__DIR__, ['Fixtures']); |
162 | 144 |
|
163 | | - $contentPrompt = $this->registry->getPrompt('content_creator'); |
164 | | - $this->assertInstanceOf(PromptReference::class, $contentPrompt); |
165 | | - $this->assertCount(3, $contentPrompt->completionProviders); |
| 145 | + $this->assertArrayHasKey('content_creator', $prompts = $discovery->getPrompts()); |
| 146 | + $this->assertCount(3, $prompts['content_creator']->completionProviders); |
166 | 147 |
|
167 | | - $typeProvider = $contentPrompt->completionProviders['type']; |
| 148 | + $typeProvider = $prompts['content_creator']->completionProviders['type']; |
168 | 149 | $this->assertInstanceOf(ListCompletionProvider::class, $typeProvider); |
169 | 150 |
|
170 | | - $statusProvider = $contentPrompt->completionProviders['status']; |
| 151 | + $statusProvider = $prompts['content_creator']->completionProviders['status']; |
171 | 152 | $this->assertInstanceOf(EnumCompletionProvider::class, $statusProvider); |
172 | 153 |
|
173 | | - $priorityProvider = $contentPrompt->completionProviders['priority']; |
| 154 | + $priorityProvider = $prompts['content_creator']->completionProviders['priority']; |
174 | 155 | $this->assertInstanceOf(EnumCompletionProvider::class, $priorityProvider); |
175 | 156 |
|
176 | | - $contentTemplate = $this->registry->getResourceTemplate('content://{category}/{slug}'); |
177 | | - $this->assertInstanceOf(ResourceTemplateReference::class, $contentTemplate); |
178 | | - $this->assertCount(1, $contentTemplate->completionProviders); |
| 157 | + $this->assertArrayHasKey('content://{category}/{slug}', $templates = $discovery->getResourceTemplates()); |
| 158 | + $this->assertCount(1, $templates['content://{category}/{slug}']->completionProviders); |
179 | 159 |
|
180 | | - $categoryProvider = $contentTemplate->completionProviders['category']; |
| 160 | + $categoryProvider = $templates['content://{category}/{slug}']->completionProviders['category']; |
181 | 161 | $this->assertInstanceOf(ListCompletionProvider::class, $categoryProvider); |
182 | 162 | } |
183 | 163 | } |
0 commit comments