From 24e7b328e53390e1ba5dc4f7addb684cdc5512c7 Mon Sep 17 00:00:00 2001 From: John Hunt Date: Wed, 22 Apr 2026 10:37:34 -0400 Subject: [PATCH 1/7] Add optional namePatterns capability to discovery so that the default scan for `*.php` files can be overridden. --- docs/server-builder.md | 36 ++++++++++--------- src/Capability/Discovery/CachedDiscoverer.php | 4 +-- src/Capability/Discovery/Discoverer.php | 4 +-- .../Discovery/DiscovererInterface.php | 3 +- .../Registry/Loader/DiscoveryLoader.php | 4 ++- src/Server/Builder.php | 9 ++++- 6 files changed, 36 insertions(+), 24 deletions(-) diff --git a/docs/server-builder.md b/docs/server-builder.md index 3ec22ebb..04388a98 100644 --- a/docs/server-builder.md +++ b/docs/server-builder.md @@ -99,8 +99,9 @@ $server = Server::builder() ->setDiscovery( basePath: __DIR__, scanDirs: ['.', 'src', 'lib'], // Where to look for MCP attributes - excludeDirs: ['vendor', 'tests'], // Where NOT to look - cache: $cacheInstance // Optional: cache discovered elements + excludeDirs: ['vendor', 'tests'], // Where NOT to look + cache: $cacheInstance, // Optional: cache discovered elements + namePatterns: ['*.php', '*.inc'], // Optional: list of filename patterns to match ); ``` @@ -109,6 +110,7 @@ $server = Server::builder() - `$scanDirs` (array): Directories to recursively scan for `#[McpTool]`, `#[McpResource]`, etc. All subdirectories are included. (default: `['.', 'src']`) - `$excludeDirs` (array): Directory names to exclude **within** the scanned directories during recursive scanning - `$cache` (CacheInterface|null): Optional PSR-16 cache to store discovered elements for performance +- `$namePatterns` (array): Optional list of Finder->name() compatible patterns to match against file names **Basic Discovery (scans current directory and `src/`):** ```php @@ -137,7 +139,7 @@ $server = Server::builder() **How `excludeDirs` works:** - If scanning `src/` and there's `src/vendor/`, it will be excluded -- If scanning `lib/` and there's `lib/tests/`, it will be excluded +- If scanning `lib/` and there's `lib/tests/`, it will be excluded - But if `vendor/` and `tests/` are at the same level as `src/`, they're not scanned anyway (not in `scanDirs`) > **Performance**: Always use a cache in production. The first run scans and caches all discovered MCP elements, making @@ -255,19 +257,19 @@ $server = Server::builder() name: 'add_numbers', description: 'Adds two numbers together' ) - + // Using class method pair ->addTool( handler: [Calculator::class, 'multiply'], name: 'multiply_numbers' // name and description are optional - derived from method name and docblock ) - + // Using instance method ->addTool( handler: [$calculatorInstance, 'divide'] ) - + // Using invokable class ->addTool( handler: InvokableCalculator::class @@ -420,17 +422,17 @@ $server = Server::builder() individual JSON-RPC messages. They do not receive the builder's registry, container, or discovery output unless you pass those dependencies in yourself. -> **Warning**: Custom message handlers bypass discovery, manual capability registration, and container lookups (unless +> **Warning**: Custom message handlers bypass discovery, manual capability registration, and container lookups (unless > you explicitly pass them). Tools, resources, and prompts you register elsewhere will not show up unless your handler -> loads and executes them manually. Reach for this API only when you need that level of control and are comfortable +> loads and executes them manually. Reach for this API only when you need that level of control and are comfortable > taking on the additional plumbing. ### Request Handlers -Handle JSON-RPC requests (messages with an `id` that expect a response). Request handlers **must** return either a +Handle JSON-RPC requests (messages with an `id` that expect a response). Request handlers **must** return either a `Response` or an `Error` object. -Attach request handlers with `addRequestHandler()` (single) or `addRequestHandlers()` (multiple). You can call these +Attach request handlers with `addRequestHandler()` (single) or `addRequestHandlers()` (multiple). You can call these methods as many times as needed; each call prepends the handlers so they execute before the defaults: ```php @@ -507,7 +509,7 @@ interface NotificationHandlerInterface ### Example -Check out `examples/custom-method-handlers/server.php` for a complete example showing how to implement +Check out `examples/custom-method-handlers/server.php` for a complete example showing how to implement custom `tools/list` and `tools/call` request handlers independently of the registry. ## Complete Example @@ -539,25 +541,25 @@ $container->set(DatabaseService::class, new DatabaseService($container->get(\PDO $server = Server::builder() // Server identity ->setServerInfo('Advanced Calculator', '2.1.0') - + // Performance and behavior ->setPaginationLimit(100) ->setInstructions('Use calculate tool for math operations. Check config resource for current settings.') - + // Discovery with caching ->setDiscovery(__DIR__, ['src'], ['vendor', 'tests'], $cache) - + // Session management ->setSession($sessionStore) - + // Services ->setLogger($logger) ->setContainer($container) - + // Manual capability registration ->addTool([Calculator::class, 'advancedCalculation'], 'advanced_calc') ->addResource([Config::class, 'getSettings'], 'config://app/settings', 'app_settings') - + // Build the server ->build(); ``` diff --git a/src/Capability/Discovery/CachedDiscoverer.php b/src/Capability/Discovery/CachedDiscoverer.php index bf039736..290d130a 100644 --- a/src/Capability/Discovery/CachedDiscoverer.php +++ b/src/Capability/Discovery/CachedDiscoverer.php @@ -42,7 +42,7 @@ public function __construct( * @param array $directories list of directories (relative to base path) to scan * @param array $excludeDirs list of directories (relative to base path) to exclude from the scan */ - public function discover(string $basePath, array $directories, array $excludeDirs = []): DiscoveryState + public function discover(string $basePath, array $directories, array $excludeDirs = [], array $namePatterns = ['*.php']): DiscoveryState { $cacheKey = $this->generateCacheKey($basePath, $directories, $excludeDirs); @@ -63,7 +63,7 @@ public function discover(string $basePath, array $directories, array $excludeDir 'directories' => $directories, ]); - $discoveryState = $this->discoverer->discover($basePath, $directories, $excludeDirs); + $discoveryState = $this->discoverer->discover($basePath, $directories, $excludeDirs, $namePatterns); $this->cache->set($cacheKey, $discoveryState); diff --git a/src/Capability/Discovery/Discoverer.php b/src/Capability/Discovery/Discoverer.php index 586b711d..a3805f08 100644 --- a/src/Capability/Discovery/Discoverer.php +++ b/src/Capability/Discovery/Discoverer.php @@ -69,7 +69,7 @@ public function __construct( * @param array $directories list of directories (relative to base path) to scan * @param array $excludeDirs list of directories (relative to base path) to exclude from the scan */ - public function discover(string $basePath, array $directories, array $excludeDirs = []): DiscoveryState + public function discover(string $basePath, array $directories, array $excludeDirs = [], array $namePatterns = ['*.php']): DiscoveryState { $startTime = microtime(true); $discoveredCount = [ @@ -106,7 +106,7 @@ public function discover(string $basePath, array $directories, array $excludeDir $finder->files() ->in($absolutePaths) ->exclude($excludeDirs) - ->name('*.php'); + ->name($namePatterns); foreach ($finder as $file) { $this->processFile($file, $discoveredCount, $tools, $resources, $prompts, $resourceTemplates); diff --git a/src/Capability/Discovery/DiscovererInterface.php b/src/Capability/Discovery/DiscovererInterface.php index 0de02de4..4d93be70 100644 --- a/src/Capability/Discovery/DiscovererInterface.php +++ b/src/Capability/Discovery/DiscovererInterface.php @@ -26,6 +26,7 @@ interface DiscovererInterface * @param string $basePath the base path for resolving directories * @param array $directories list of directories (relative to base path) to scan * @param array $excludeDirs list of directories (relative to base path) to exclude from the scan + * @param array $namePatterns list of file name patterns for the scan. Compatible with Finder->name() */ - public function discover(string $basePath, array $directories, array $excludeDirs = []): DiscoveryState; + public function discover(string $basePath, array $directories, array $excludeDirs = [], array $namePatterns = ['*.php']): DiscoveryState; } diff --git a/src/Capability/Registry/Loader/DiscoveryLoader.php b/src/Capability/Registry/Loader/DiscoveryLoader.php index 25261ff5..c227efc9 100644 --- a/src/Capability/Registry/Loader/DiscoveryLoader.php +++ b/src/Capability/Registry/Loader/DiscoveryLoader.php @@ -24,18 +24,20 @@ final class DiscoveryLoader implements LoaderInterface /** * @param string[] $scanDirs * @param array|string[] $excludeDirs + * @param string[] $namePatterns */ public function __construct( private string $basePath, private array $scanDirs, private array $excludeDirs, private DiscovererInterface $discoverer, + private array $namePatterns = ['*.php'] ) { } public function load(RegistryInterface $registry): void { - $discoveryState = $this->discoverer->discover($this->basePath, $this->scanDirs, $this->excludeDirs); + $discoveryState = $this->discoverer->discover($this->basePath, $this->scanDirs, $this->excludeDirs, $this->namePatterns); $registry->setDiscoveryState($discoveryState); } diff --git a/src/Server/Builder.php b/src/Server/Builder.php index f1c5c053..9440e5f7 100644 --- a/src/Server/Builder.php +++ b/src/Server/Builder.php @@ -159,6 +159,11 @@ final class Builder */ private array $discoveryExcludeDirs = []; + /** + * @var array|string[] + */ + private array $discoveryNamePatterns = ['*.php']; + private ?ServerCapabilities $serverCapabilities = null; /** @@ -353,11 +358,13 @@ public function setDiscovery( array $scanDirs = ['.', 'src'], array $excludeDirs = [], ?CacheInterface $cache = null, + array $namePatterns = ['*.php'], ): self { $this->discoveryBasePath = $basePath; $this->discoveryScanDirs = $scanDirs; $this->discoveryExcludeDirs = $excludeDirs; $this->discoveryCache = $cache; + $this->discoveryNamePatterns = $namePatterns; return $this; } @@ -527,7 +534,7 @@ public function build(): Server if (null !== $this->discoveryBasePath) { if (null !== $this->discoverer || class_exists(Finder::class)) { $discoverer = $this->discoverer ?? $this->createDiscoverer($logger); - $loaders[] = new DiscoveryLoader($this->discoveryBasePath, $this->discoveryScanDirs, $this->discoveryExcludeDirs, $discoverer); + $loaders[] = new DiscoveryLoader($this->discoveryBasePath, $this->discoveryScanDirs, $this->discoveryExcludeDirs, $discoverer, $this->discoveryNamePatterns); } else { $logger->warning('File-based discovery requires symfony/finder. Skipping automatic discovery. Run: composer require symfony/finder'); } From 1e583eee96e56391fa3c4baaa5afbba417b9687b Mon Sep 17 00:00:00 2001 From: John Hunt Date: Wed, 22 Apr 2026 11:06:24 -0400 Subject: [PATCH 2/7] add default to namePatterns docs --- docs/server-builder.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/server-builder.md b/docs/server-builder.md index 04388a98..33b652be 100644 --- a/docs/server-builder.md +++ b/docs/server-builder.md @@ -110,7 +110,7 @@ $server = Server::builder() - `$scanDirs` (array): Directories to recursively scan for `#[McpTool]`, `#[McpResource]`, etc. All subdirectories are included. (default: `['.', 'src']`) - `$excludeDirs` (array): Directory names to exclude **within** the scanned directories during recursive scanning - `$cache` (CacheInterface|null): Optional PSR-16 cache to store discovered elements for performance -- `$namePatterns` (array): Optional list of Finder->name() compatible patterns to match against file names +- `$namePatterns` (array): Optional list of Finder->name() compatible patterns to match against file names (default: `['*.php']`) **Basic Discovery (scans current directory and `src/`):** ```php From 80142980e837b8eb1c3822a2ab0c5056f76c6005 Mon Sep 17 00:00:00 2001 From: John Hunt Date: Wed, 22 Apr 2026 12:12:19 -0400 Subject: [PATCH 3/7] add an error check if we manage to pass in an empty list of patterns --- src/Capability/Discovery/Discoverer.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Capability/Discovery/Discoverer.php b/src/Capability/Discovery/Discoverer.php index a3805f08..78abc46b 100644 --- a/src/Capability/Discovery/Discoverer.php +++ b/src/Capability/Discovery/Discoverer.php @@ -103,6 +103,14 @@ public function discover(string $basePath, array $directories, array $excludeDir return new DiscoveryState(); } + if (empty($namePatterns)) { + $this->logger->warning('No valid file name patterns found to scan.', [ + 'configured_name_patterns' => $namePatterns + ]); + + return new DiscoveryState(); + } + $finder->files() ->in($absolutePaths) ->exclude($excludeDirs) From b46413808bc783a6b803388ff892a5f335d6abc4 Mon Sep 17 00:00:00 2001 From: John Hunt Date: Wed, 22 Apr 2026 13:29:25 -0400 Subject: [PATCH 4/7] add unit tests (and a static entry to classmap for composer.json so it autoloads from a non .php file for the test) --- composer.json | 7 ++++-- .../Capability/Discovery/DiscoveryTest.php | 24 +++++++++++++++++-- .../AlternativeFileNameToolHandler.class.inc | 22 +++++++++++++++++ 3 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 tests/Unit/Capability/Discovery/Fixtures/AlternativeFileNameToolHandler.class.inc diff --git a/composer.json b/composer.json index 46ea2d77..d9f1507f 100644 --- a/composer.json +++ b/composer.json @@ -80,7 +80,10 @@ "Mcp\\Example\\Server\\OAuthMicrosoft\\": "examples/server/oauth-microsoft/", "Mcp\\Example\\Server\\SchemaShowcase\\": "examples/server/schema-showcase/", "Mcp\\Tests\\": "tests/" - } + }, + "classmap": [ + "tests/Unit/Capability/Discovery/Fixtures/AlternativeFileNameToolHandler.class.inc" + ] }, "config": { "allow-plugins": { @@ -89,4 +92,4 @@ }, "sort-packages": true } -} \ No newline at end of file +} diff --git a/tests/Unit/Capability/Discovery/DiscoveryTest.php b/tests/Unit/Capability/Discovery/DiscoveryTest.php index 81081063..5fbf6a8c 100644 --- a/tests/Unit/Capability/Discovery/DiscoveryTest.php +++ b/tests/Unit/Capability/Discovery/DiscoveryTest.php @@ -16,6 +16,7 @@ use Mcp\Capability\Discovery\Discoverer; use Mcp\Capability\Registry\ToolReference; use Mcp\Tests\Unit\Capability\Attribute\CompletionProviderFixture; +use Mcp\Tests\Unit\Capability\Discovery\Fixtures\AlternativeFileNameToolHandler; use Mcp\Tests\Unit\Capability\Discovery\Fixtures\DiscoverableToolHandler; use Mcp\Tests\Unit\Capability\Discovery\Fixtures\InvocablePromptFixture; use Mcp\Tests\Unit\Capability\Discovery\Fixtures\InvocableResourceFixture; @@ -34,10 +35,10 @@ protected function setUp(): void public function testDiscoversAllElementTypesCorrectlyFromFixtureFiles(): void { - $discovery = $this->discoverer->discover(__DIR__, ['Fixtures']); + $discovery = $this->discoverer->discover(__DIR__, ['Fixtures'], [], ['*.php', '*.inc']); $tools = $discovery->getTools(); - $this->assertCount(4, $tools); + $this->assertCount(5, $tools); $this->assertArrayHasKey('greet_user', $tools); $this->assertFalse($tools['greet_user']->isManual); @@ -56,6 +57,9 @@ public function testDiscoversAllElementTypesCorrectlyFromFixtureFiles(): void $this->assertFalse($tools['InvokableCalculator']->isManual); $this->assertEquals([InvocableToolFixture::class, '__invoke'], $tools['InvokableCalculator']->handler); + $this->assertArrayHasKey('inc_file_name_tool', $tools); + $this->assertEquals([AlternativeFileNameToolHandler::class, 'run'], $tools['inc_file_name_tool']->handler); + $this->assertArrayNotHasKey('private_tool_should_be_ignored', $tools); $this->assertArrayNotHasKey('protected_tool_should_be_ignored', $tools); $this->assertArrayNotHasKey('static_tool_should_be_ignored', $tools); @@ -121,6 +125,22 @@ public function testHandlesEmptyDirectoriesOrDirectoriesWithNoPhpFiles(): void $this->assertTrue($discovery->isEmpty()); } + public function testHandlesDefaultAndOverriddenFileNamePatterns(): void + { + $discovery = $this->discoverer->discover(__DIR__, ['Fixtures']); + $this->assertArrayHasKey('greet_user', $discovery->getTools()); + $this->assertArrayNotHasKey('inc_file_name_tool', $discovery->getTools()); + + $discovery = $this->discoverer->discover(__DIR__, ['Fixtures'], [], ['*.php', '*.inc']); + $this->assertArrayHasKey('inc_file_name_tool', $discovery->getTools()); + + $discovery = $this->discoverer->discover(__DIR__, ['Fixtures'], [], ['*.inc']); + $this->assertArrayNotHasKey('greet_user', $discovery->getTools()); + + $discovery = $this->discoverer->discover(__DIR__, ['Fixtures'], [], []); + $this->assertTrue($discovery->isEmpty()); + } + public function testCorrectlyInfersNamesAndDescriptionsFromMethodsOrClassesIfNotSetInAttribute(): void { $discovery = $this->discoverer->discover(__DIR__, ['Fixtures']); diff --git a/tests/Unit/Capability/Discovery/Fixtures/AlternativeFileNameToolHandler.class.inc b/tests/Unit/Capability/Discovery/Fixtures/AlternativeFileNameToolHandler.class.inc new file mode 100644 index 00000000..67c78eb0 --- /dev/null +++ b/tests/Unit/Capability/Discovery/Fixtures/AlternativeFileNameToolHandler.class.inc @@ -0,0 +1,22 @@ + Date: Wed, 22 Apr 2026 14:00:21 -0400 Subject: [PATCH 5/7] run php-cs-fixer --- src/Capability/Discovery/Discoverer.php | 2 +- src/Capability/Discovery/DiscovererInterface.php | 6 +++--- src/Capability/Registry/Loader/DiscoveryLoader.php | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Capability/Discovery/Discoverer.php b/src/Capability/Discovery/Discoverer.php index 78abc46b..213a541b 100644 --- a/src/Capability/Discovery/Discoverer.php +++ b/src/Capability/Discovery/Discoverer.php @@ -105,7 +105,7 @@ public function discover(string $basePath, array $directories, array $excludeDir if (empty($namePatterns)) { $this->logger->warning('No valid file name patterns found to scan.', [ - 'configured_name_patterns' => $namePatterns + 'configured_name_patterns' => $namePatterns, ]); return new DiscoveryState(); diff --git a/src/Capability/Discovery/DiscovererInterface.php b/src/Capability/Discovery/DiscovererInterface.php index 4d93be70..a29a42ef 100644 --- a/src/Capability/Discovery/DiscovererInterface.php +++ b/src/Capability/Discovery/DiscovererInterface.php @@ -23,9 +23,9 @@ interface DiscovererInterface /** * Discover MCP elements in the specified directories and return the discovery state. * - * @param string $basePath the base path for resolving directories - * @param array $directories list of directories (relative to base path) to scan - * @param array $excludeDirs list of directories (relative to base path) to exclude from the scan + * @param string $basePath the base path for resolving directories + * @param array $directories list of directories (relative to base path) to scan + * @param array $excludeDirs list of directories (relative to base path) to exclude from the scan * @param array $namePatterns list of file name patterns for the scan. Compatible with Finder->name() */ public function discover(string $basePath, array $directories, array $excludeDirs = [], array $namePatterns = ['*.php']): DiscoveryState; diff --git a/src/Capability/Registry/Loader/DiscoveryLoader.php b/src/Capability/Registry/Loader/DiscoveryLoader.php index c227efc9..2d33f813 100644 --- a/src/Capability/Registry/Loader/DiscoveryLoader.php +++ b/src/Capability/Registry/Loader/DiscoveryLoader.php @@ -24,14 +24,14 @@ final class DiscoveryLoader implements LoaderInterface /** * @param string[] $scanDirs * @param array|string[] $excludeDirs - * @param string[] $namePatterns + * @param string[] $namePatterns */ public function __construct( private string $basePath, private array $scanDirs, private array $excludeDirs, private DiscovererInterface $discoverer, - private array $namePatterns = ['*.php'] + private array $namePatterns = ['*.php'], ) { } From 258cf5e7f2c2e7c69a4878f4caf79f6281b4b9a7 Mon Sep 17 00:00:00 2001 From: John Hunt Date: Wed, 22 Apr 2026 19:50:23 -0400 Subject: [PATCH 6/7] change how default namePatterns are set and make sure all the annotations are correct --- src/Capability/Discovery/CachedDiscoverer.php | 9 +++++---- src/Capability/Discovery/Discoverer.php | 19 +++++++------------ .../Discovery/DiscovererInterface.php | 10 +++++----- .../Registry/Loader/DiscoveryLoader.php | 4 ++-- src/Server/Builder.php | 11 ++++++----- .../Capability/Discovery/DiscoveryTest.php | 9 ++++++--- 6 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/Capability/Discovery/CachedDiscoverer.php b/src/Capability/Discovery/CachedDiscoverer.php index 290d130a..e1450ac1 100644 --- a/src/Capability/Discovery/CachedDiscoverer.php +++ b/src/Capability/Discovery/CachedDiscoverer.php @@ -38,11 +38,12 @@ public function __construct( /** * Discover MCP elements in the specified directories with caching. * - * @param string $basePath the base path for resolving directories - * @param array $directories list of directories (relative to base path) to scan - * @param array $excludeDirs list of directories (relative to base path) to exclude from the scan + * @param string $basePath the base path for resolving directories + * @param array $directories list of directories (relative to base path) to scan + * @param array $excludeDirs list of directories (relative to base path) to exclude from the scan + * @param array|null $namePatterns list of file name patterns for the scan. Compatible with Finder->name() */ - public function discover(string $basePath, array $directories, array $excludeDirs = [], array $namePatterns = ['*.php']): DiscoveryState + public function discover(string $basePath, array $directories, array $excludeDirs = [], ?array $namePatterns = ['*.php']): DiscoveryState { $cacheKey = $this->generateCacheKey($basePath, $directories, $excludeDirs); diff --git a/src/Capability/Discovery/Discoverer.php b/src/Capability/Discovery/Discoverer.php index 213a541b..2e06fc46 100644 --- a/src/Capability/Discovery/Discoverer.php +++ b/src/Capability/Discovery/Discoverer.php @@ -65,11 +65,12 @@ public function __construct( /** * Discover MCP elements in the specified directories and return the discovery state. * - * @param string $basePath the base path for resolving directories - * @param array $directories list of directories (relative to base path) to scan - * @param array $excludeDirs list of directories (relative to base path) to exclude from the scan + * @param string $basePath the base path for resolving directories + * @param array $directories list of directories (relative to base path) to scan + * @param array $excludeDirs list of directories (relative to base path) to exclude from the scan + * @param array|null $namePatterns list of file name patterns for the scan. Compatible with Finder->name() */ - public function discover(string $basePath, array $directories, array $excludeDirs = [], array $namePatterns = ['*.php']): DiscoveryState + public function discover(string $basePath, array $directories, array $excludeDirs = [], ?array $namePatterns = null): DiscoveryState { $startTime = microtime(true); $discoveredCount = [ @@ -84,6 +85,8 @@ public function discover(string $basePath, array $directories, array $excludeDir $prompts = []; $resourceTemplates = []; + $namePatterns = !empty($namePatterns) ? $namePatterns : ['*.php']; + try { $finder = new Finder(); $absolutePaths = []; @@ -103,14 +106,6 @@ public function discover(string $basePath, array $directories, array $excludeDir return new DiscoveryState(); } - if (empty($namePatterns)) { - $this->logger->warning('No valid file name patterns found to scan.', [ - 'configured_name_patterns' => $namePatterns, - ]); - - return new DiscoveryState(); - } - $finder->files() ->in($absolutePaths) ->exclude($excludeDirs) diff --git a/src/Capability/Discovery/DiscovererInterface.php b/src/Capability/Discovery/DiscovererInterface.php index a29a42ef..a86a0516 100644 --- a/src/Capability/Discovery/DiscovererInterface.php +++ b/src/Capability/Discovery/DiscovererInterface.php @@ -23,10 +23,10 @@ interface DiscovererInterface /** * Discover MCP elements in the specified directories and return the discovery state. * - * @param string $basePath the base path for resolving directories - * @param array $directories list of directories (relative to base path) to scan - * @param array $excludeDirs list of directories (relative to base path) to exclude from the scan - * @param array $namePatterns list of file name patterns for the scan. Compatible with Finder->name() + * @param string $basePath the base path for resolving directories + * @param array $directories list of directories (relative to base path) to scan + * @param array $excludeDirs list of directories (relative to base path) to exclude from the scan + * @param array|null $namePatterns list of file name patterns for the scan. Compatible with Finder->name() */ - public function discover(string $basePath, array $directories, array $excludeDirs = [], array $namePatterns = ['*.php']): DiscoveryState; + public function discover(string $basePath, array $directories, array $excludeDirs = [], ?array $namePatterns = null): DiscoveryState; } diff --git a/src/Capability/Registry/Loader/DiscoveryLoader.php b/src/Capability/Registry/Loader/DiscoveryLoader.php index 2d33f813..3f169326 100644 --- a/src/Capability/Registry/Loader/DiscoveryLoader.php +++ b/src/Capability/Registry/Loader/DiscoveryLoader.php @@ -24,14 +24,14 @@ final class DiscoveryLoader implements LoaderInterface /** * @param string[] $scanDirs * @param array|string[] $excludeDirs - * @param string[] $namePatterns + * @param string[]|null $namePatterns */ public function __construct( private string $basePath, private array $scanDirs, private array $excludeDirs, private DiscovererInterface $discoverer, - private array $namePatterns = ['*.php'], + private ?array $namePatterns = null, ) { } diff --git a/src/Server/Builder.php b/src/Server/Builder.php index 9440e5f7..8d4ae2b2 100644 --- a/src/Server/Builder.php +++ b/src/Server/Builder.php @@ -160,9 +160,9 @@ final class Builder private array $discoveryExcludeDirs = []; /** - * @var array|string[] + * @var string[]|null */ - private array $discoveryNamePatterns = ['*.php']; + private ?array $discoveryNamePatterns = null; private ?ServerCapabilities $serverCapabilities = null; @@ -350,15 +350,16 @@ public function setSession( } /** - * @param string[] $scanDirs - * @param string[] $excludeDirs + * @param string[] $scanDirs + * @param string[] $excludeDirs + * @param string[]|null $namePatterns */ public function setDiscovery( string $basePath, array $scanDirs = ['.', 'src'], array $excludeDirs = [], ?CacheInterface $cache = null, - array $namePatterns = ['*.php'], + ?array $namePatterns = null, ): self { $this->discoveryBasePath = $basePath; $this->discoveryScanDirs = $scanDirs; diff --git a/tests/Unit/Capability/Discovery/DiscoveryTest.php b/tests/Unit/Capability/Discovery/DiscoveryTest.php index 5fbf6a8c..1451361d 100644 --- a/tests/Unit/Capability/Discovery/DiscoveryTest.php +++ b/tests/Unit/Capability/Discovery/DiscoveryTest.php @@ -131,14 +131,17 @@ public function testHandlesDefaultAndOverriddenFileNamePatterns(): void $this->assertArrayHasKey('greet_user', $discovery->getTools()); $this->assertArrayNotHasKey('inc_file_name_tool', $discovery->getTools()); + $discovery = $this->discoverer->discover(__DIR__, ['Fixtures'], [], []); + $this->assertArrayHasKey('greet_user', $discovery->getTools()); + $this->assertArrayNotHasKey('inc_file_name_tool', $discovery->getTools()); + $discovery = $this->discoverer->discover(__DIR__, ['Fixtures'], [], ['*.php', '*.inc']); + $this->assertArrayHasKey('greet_user', $discovery->getTools()); $this->assertArrayHasKey('inc_file_name_tool', $discovery->getTools()); $discovery = $this->discoverer->discover(__DIR__, ['Fixtures'], [], ['*.inc']); $this->assertArrayNotHasKey('greet_user', $discovery->getTools()); - - $discovery = $this->discoverer->discover(__DIR__, ['Fixtures'], [], []); - $this->assertTrue($discovery->isEmpty()); + $this->assertArrayHasKey('inc_file_name_tool', $discovery->getTools()); } public function testCorrectlyInfersNamesAndDescriptionsFromMethodsOrClassesIfNotSetInAttribute(): void From c3ebd2ef7e1632a34bb267b56b54ccd0461ffe78 Mon Sep 17 00:00:00 2001 From: johnhunt-lc Date: Mon, 27 Apr 2026 08:27:36 -0400 Subject: [PATCH 7/7] Fix bad whitespace Co-authored-by: Christopher Hertel --- docs/server-builder.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/server-builder.md b/docs/server-builder.md index 33b652be..87d060e0 100644 --- a/docs/server-builder.md +++ b/docs/server-builder.md @@ -101,7 +101,7 @@ $server = Server::builder() scanDirs: ['.', 'src', 'lib'], // Where to look for MCP attributes excludeDirs: ['vendor', 'tests'], // Where NOT to look cache: $cacheInstance, // Optional: cache discovered elements - namePatterns: ['*.php', '*.inc'], // Optional: list of filename patterns to match + namePatterns: ['*.php', '*.inc'], // Optional: list of filename patterns to match ); ```