4949 */
5050final class Discoverer implements DiscovererInterface
5151{
52+ private readonly DocBlockParser $ docBlockParser ;
53+ private readonly SchemaGeneratorInterface $ schemaGenerator ;
54+
5255 public function __construct (
5356 private readonly LoggerInterface $ logger = new NullLogger (),
54- private ?DocBlockParser $ docBlockParser = null ,
55- private ?SchemaGeneratorInterface $ schemaGenerator = null ,
57+ ?DocBlockParser $ docBlockParser = null ,
58+ ?SchemaGeneratorInterface $ schemaGenerator = null ,
5659 ) {
5760 if (!class_exists (Finder::class)) {
5861 throw new RuntimeException ('File-based discovery requires symfony/finder. Run: composer require symfony/finder ' );
@@ -223,76 +226,65 @@ private function processMethod(\ReflectionMethod $method, array &$discoveredCoun
223226 try {
224227 $ instance = $ attribute ->newInstance ();
225228
226- switch ($ attributeClassName ) {
227- case McpTool::class:
228- $ name = ElementMetadataResolver::resolveName ($ method , $ instance ->name );
229- $ description = ElementMetadataResolver::resolveDescription ($ method , $ instance ->description , $ this ->docBlockParser );
230- $ inputSchema = $ this ->schemaGenerator ->generate ($ method );
231- $ outputSchema = $ this ->schemaGenerator ->generateOutputSchema ($ method );
232- $ tool = new Tool (
233- name: $ name ,
234- title: $ instance ->title ,
235- inputSchema: $ inputSchema ,
236- description: $ description ,
237- annotations: $ instance ->annotations ,
238- icons: $ instance ->icons ,
239- meta: $ instance ->meta ,
240- outputSchema: $ outputSchema ,
241- );
242- $ tools [$ name ] = new ToolReference ($ tool , [$ className , $ methodName ]);
243- ++$ discoveredCount ['tools ' ];
244- break ;
245-
246- case McpResource::class:
247- $ name = ElementMetadataResolver::resolveName ($ method , $ instance ->name );
248- $ description = ElementMetadataResolver::resolveDescription ($ method , $ instance ->description , $ this ->docBlockParser );
249- $ resource = new ResourceDefinition (
250- $ instance ->uri ,
251- $ name ,
252- $ instance ->title ,
253- $ description ,
254- $ instance ->mimeType ,
255- $ instance ->annotations ,
256- $ instance ->size ,
257- $ instance ->icons ,
258- $ instance ->meta ,
259- );
260- $ resources [$ instance ->uri ] = new ResourceReference ($ resource , [$ className , $ methodName ]);
261-
262- ++$ discoveredCount ['resources ' ];
263- break ;
264-
265- case McpPrompt::class:
266- $ docBlock = $ this ->docBlockParser ->parseDocBlock ($ method ->getDocComment () ?? null );
267- $ name = ElementMetadataResolver::resolveName ($ method , $ instance ->name );
268- $ description = ElementMetadataResolver::resolveDescription ($ method , $ instance ->description , $ this ->docBlockParser );
269- $ arguments = [];
270- $ paramTags = $ this ->docBlockParser ->getParamTags ($ docBlock );
271- foreach ($ method ->getParameters () as $ param ) {
272- $ reflectionType = $ param ->getType ();
273- if ($ reflectionType instanceof \ReflectionNamedType && !$ reflectionType ->isBuiltin ()) {
274- continue ;
275- }
276- $ paramTag = $ paramTags ['$ ' .$ param ->getName ()] ?? null ;
277- $ arguments [] = new PromptArgument ($ param ->getName (), $ paramTag ? trim ((string ) $ paramTag ->getDescription ()) : null , !$ param ->isOptional () && !$ param ->isDefaultValueAvailable ());
229+ if ($ instance instanceof McpTool) {
230+ $ name = ElementMetadataResolver::resolveName ($ method , $ instance ->name );
231+ $ description = ElementMetadataResolver::resolveDescription ($ method , $ instance ->description , $ this ->docBlockParser );
232+ $ inputSchema = $ this ->schemaGenerator ->generate ($ method );
233+ $ outputSchema = $ this ->schemaGenerator ->generateOutputSchema ($ method );
234+ $ tool = new Tool (
235+ name: $ name ,
236+ title: $ instance ->title ,
237+ inputSchema: $ inputSchema ,
238+ description: $ description ,
239+ annotations: $ instance ->annotations ,
240+ icons: $ instance ->icons ,
241+ meta: $ instance ->meta ,
242+ outputSchema: $ outputSchema ,
243+ );
244+ $ tools [$ name ] = new ToolReference ($ tool , [$ className , $ methodName ]);
245+ ++$ discoveredCount ['tools ' ];
246+ } elseif ($ instance instanceof McpResource) {
247+ $ name = ElementMetadataResolver::resolveName ($ method , $ instance ->name );
248+ $ description = ElementMetadataResolver::resolveDescription ($ method , $ instance ->description , $ this ->docBlockParser );
249+ $ resource = new ResourceDefinition (
250+ $ instance ->uri ,
251+ $ name ,
252+ $ instance ->title ,
253+ $ description ,
254+ $ instance ->mimeType ,
255+ $ instance ->annotations ,
256+ $ instance ->size ,
257+ $ instance ->icons ,
258+ $ instance ->meta ,
259+ );
260+ $ resources [$ instance ->uri ] = new ResourceReference ($ resource , [$ className , $ methodName ]);
261+
262+ ++$ discoveredCount ['resources ' ];
263+ } elseif ($ instance instanceof McpPrompt) {
264+ $ docBlock = $ this ->docBlockParser ->parseDocBlock ($ method ->getDocComment () ?? null );
265+ $ name = ElementMetadataResolver::resolveName ($ method , $ instance ->name );
266+ $ description = ElementMetadataResolver::resolveDescription ($ method , $ instance ->description , $ this ->docBlockParser );
267+ $ arguments = [];
268+ $ paramTags = $ this ->docBlockParser ->getParamTags ($ docBlock );
269+ foreach ($ method ->getParameters () as $ param ) {
270+ $ reflectionType = $ param ->getType ();
271+ if ($ reflectionType instanceof \ReflectionNamedType && !$ reflectionType ->isBuiltin ()) {
272+ continue ;
278273 }
279- $ prompt = new Prompt ($ name , $ instance ->title , $ description , $ arguments , $ instance ->icons , $ instance ->meta );
280- $ completionProviders = $ this ->getCompletionProviders ($ method );
281- $ prompts [$ name ] = new PromptReference ($ prompt , [$ className , $ methodName ], $ completionProviders );
282- ++$ discoveredCount ['prompts ' ];
283- break ;
284-
285- case McpResourceTemplate::class:
286- $ name = ElementMetadataResolver::resolveName ($ method , $ instance ->name );
287- $ description = ElementMetadataResolver::resolveDescription ($ method , $ instance ->description , $ this ->docBlockParser );
288- $ mimeType = $ instance ->mimeType ;
289- $ annotations = $ instance ->annotations ;
290- $ meta = $ instance ->meta ?? null ;
291- $ resourceTemplate = new ResourceTemplate ($ instance ->uriTemplate , $ name , $ instance ->title , $ description , $ mimeType , $ annotations , $ meta );
292- $ completionProviders = $ this ->getCompletionProviders ($ method );
293- $ resourceTemplates [$ instance ->uriTemplate ] = new ResourceTemplateReference ($ resourceTemplate , [$ className , $ methodName ], $ completionProviders );
294- ++$ discoveredCount ['resourceTemplates ' ];
295- break ;
274+ $ paramTag = $ paramTags ['$ ' .$ param ->getName ()] ?? null ;
275+ $ arguments [] = new PromptArgument ($ param ->getName (), $ paramTag ? trim ((string ) $ paramTag ->getDescription ()) : null , !$ param ->isOptional () && !$ param ->isDefaultValueAvailable ());
276+ }
277+ $ prompt = new Prompt ($ name , $ instance ->title , $ description , $ arguments , $ instance ->icons , $ instance ->meta );
278+ $ completionProviders = $ this ->getCompletionProviders ($ method );
279+ $ prompts [$ name ] = new PromptReference ($ prompt , [$ className , $ methodName ], $ completionProviders );
280+ ++$ discoveredCount ['prompts ' ];
281+ } elseif ($ instance instanceof McpResourceTemplate) {
282+ $ name = ElementMetadataResolver::resolveName ($ method , $ instance ->name );
283+ $ description = ElementMetadataResolver::resolveDescription ($ method , $ instance ->description , $ this ->docBlockParser );
284+ $ resourceTemplate = new ResourceTemplate ($ instance ->uriTemplate , $ name , $ instance ->title , $ description , $ instance ->mimeType , $ instance ->annotations , $ instance ->meta );
285+ $ completionProviders = $ this ->getCompletionProviders ($ method );
286+ $ resourceTemplates [$ instance ->uriTemplate ] = new ResourceTemplateReference ($ resourceTemplate , [$ className , $ methodName ], $ completionProviders );
287+ ++$ discoveredCount ['resourceTemplates ' ];
296288 }
297289 } catch (ExceptionInterface $ e ) {
298290 $ this ->logger ->error ("Failed to process MCP attribute on {$ className }:: {$ methodName }" , [
@@ -308,7 +300,7 @@ private function processMethod(\ReflectionMethod $method, array &$discoveredCoun
308300 }
309301
310302 /**
311- * @return array<string, string|ProviderInterface>
303+ * @return array<string, class- string<ProviderInterface> |ProviderInterface>
312304 */
313305 private function getCompletionProviders (\ReflectionMethod $ reflectionMethod ): array
314306 {
@@ -431,17 +423,13 @@ private function getClassFromFile(SplFileInfo $file): ?string
431423 }
432424
433425 foreach ($ potentialClasses as $ potentialClass ) {
434- if (class_exists ($ potentialClass , true )) {
426+ if (class_exists ($ potentialClass , true ) || interface_exists ( $ potentialClass , true ) || trait_exists ( $ potentialClass , true ) ) {
435427 return $ potentialClass ;
436428 }
437429 }
438430
439431 if (!empty ($ potentialClasses )) {
440- if (!class_exists ($ potentialClasses [0 ], false )) {
441- $ this ->logger ->debug ('getClassFromFile returning potential non-class type. Are you sure this class has been autoloaded? ' , ['file ' => $ file ->getPathname (), 'type ' => $ potentialClasses [0 ]]);
442- }
443-
444- return $ potentialClasses [0 ];
432+ $ this ->logger ->debug ('getClassFromFile found no loadable type. Are you sure this class has been autoloaded? ' , ['file ' => $ file ->getPathname (), 'type ' => $ potentialClasses [0 ]]);
445433 }
446434
447435 return null ;
0 commit comments