diff --git a/src/mcp-bundle/config/services.php b/src/mcp-bundle/config/services.php index 3b406381b7..ff58b25e5a 100644 --- a/src/mcp-bundle/config/services.php +++ b/src/mcp-bundle/config/services.php @@ -34,6 +34,9 @@ ->call('setEventDispatcher', [service('event_dispatcher')]) ->call('setRegistry', [service('mcp.registry')]) ->call('setSession', [service('mcp.session.store')]) + ->call('addRequestHandlers', [tagged_iterator('mcp.request_handler')]) + ->call('addNotificationHandlers', [tagged_iterator('mcp.notification_handler')]) + ->call('addLoaders', [tagged_iterator('mcp.loader')]) ->call('setDiscovery', [param('kernel.project_dir'), param('mcp.discovery.scan_dirs'), param('mcp.discovery.exclude_dirs')]) ->set('mcp.server', Server::class) diff --git a/src/mcp-bundle/src/DependencyInjection/McpPass.php b/src/mcp-bundle/src/DependencyInjection/McpPass.php index 0643cf3dae..dda9471d35 100644 --- a/src/mcp-bundle/src/DependencyInjection/McpPass.php +++ b/src/mcp-bundle/src/DependencyInjection/McpPass.php @@ -27,13 +27,6 @@ public function process(ContainerBuilder $container): void return; } - $definition = $container->getDefinition('mcp.server.builder'); - - $loaderReferences = $this->findAndSortTaggedServices('mcp.loader', $container); - if ([] !== $loaderReferences) { - $definition->addMethodCall('addLoaders', $loaderReferences); - } - $allMcpServices = []; $mcpTags = ['mcp.tool', 'mcp.prompt', 'mcp.resource', 'mcp.resource_template']; @@ -52,6 +45,6 @@ public function process(ContainerBuilder $container): void } $serviceLocatorRef = ServiceLocatorTagPass::register($container, $serviceReferences); - $definition->addMethodCall('setContainer', [$serviceLocatorRef]); + $container->getDefinition('mcp.server.builder')->addMethodCall('setContainer', [$serviceLocatorRef]); } } diff --git a/src/mcp-bundle/src/McpBundle.php b/src/mcp-bundle/src/McpBundle.php index 1db83882d0..89cd187a80 100644 --- a/src/mcp-bundle/src/McpBundle.php +++ b/src/mcp-bundle/src/McpBundle.php @@ -17,6 +17,8 @@ use Mcp\Capability\Attribute\McpResourceTemplate; use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Registry\Loader\LoaderInterface; +use Mcp\Server\Handler\Notification\NotificationHandlerInterface; +use Mcp\Server\Handler\Request\RequestHandlerInterface; use Mcp\Server\Session\FileSessionStore; use Mcp\Server\Session\InMemorySessionStore; use Symfony\AI\McpBundle\Command\McpCommand; @@ -62,6 +64,12 @@ public function loadExtension(array $config, ContainerConfigurator $container, C $builder->registerForAutoconfiguration(LoaderInterface::class) ->addTag('mcp.loader'); + $builder->registerForAutoconfiguration(RequestHandlerInterface::class) + ->addTag('mcp.request_handler'); + + $builder->registerForAutoconfiguration(NotificationHandlerInterface::class) + ->addTag('mcp.notification_handler'); + if ($builder->getParameter('kernel.debug')) { $traceableRegistry = (new Definition('mcp.traceable_registry')) ->setClass(TraceableRegistry::class) diff --git a/src/mcp-bundle/tests/DependencyInjection/McpBundleTest.php b/src/mcp-bundle/tests/DependencyInjection/McpBundleTest.php index 11f2885f8b..9b7ca74d4b 100644 --- a/src/mcp-bundle/tests/DependencyInjection/McpBundleTest.php +++ b/src/mcp-bundle/tests/DependencyInjection/McpBundleTest.php @@ -12,9 +12,12 @@ namespace Symfony\AI\McpBundle\Tests\DependencyInjection; use Mcp\Capability\Registry\Loader\LoaderInterface; +use Mcp\Server\Handler\Notification\NotificationHandlerInterface; +use Mcp\Server\Handler\Request\RequestHandlerInterface; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\AI\McpBundle\McpBundle; +use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use Symfony\Component\DependencyInjection\ContainerBuilder; class McpBundleTest extends TestCase @@ -150,13 +153,50 @@ public function testServerServices() $methodCalls = $builderDefinition->getMethodCalls(); $hasEventDispatcherCall = false; + $hasRequestHandlers = false; + $hasNotificationHandlers = false; + $hasLoaders = false; + foreach ($methodCalls as $call) { if ('setEventDispatcher' === $call[0]) { $hasEventDispatcherCall = true; - break; + } + + if ('addRequestHandlers' === $call[0]) { + $argument = $call[1][0]; + if ( + $argument instanceof TaggedIteratorArgument + && 'mcp.request_handler' === $argument->getTag() + ) { + $hasRequestHandlers = true; + } + } + + if ('addNotificationHandlers' === $call[0]) { + $argument = $call[1][0]; + if ( + $argument instanceof TaggedIteratorArgument + && 'mcp.notification_handler' === $argument->getTag() + ) { + $hasNotificationHandlers = true; + } + } + + if ('addLoaders' === $call[0]) { + $argument = $call[1][0]; + if ( + $argument instanceof TaggedIteratorArgument + && 'mcp.loader' === $argument->getTag() + ) { + $hasLoaders = true; + } } } + $this->assertTrue($hasEventDispatcherCall, 'ServerBuilder should have setEventDispatcher method call'); + $this->assertTrue($hasRequestHandlers, 'ServerBuilder should have addRequestHandlers with mcp.request_handler tag'); + $this->assertTrue($hasNotificationHandlers, 'ServerBuilder should have addNotificationHandlers with mcp.notification_handler tag'); + $this->assertTrue($hasLoaders, 'ServerBuilder should have addLoaders with mcp.loader tag'); } public function testMcpToolAttributeAutoconfiguration() @@ -359,6 +399,24 @@ public function testLoaderInterfaceAutoconfiguration() $this->assertTrue($definition->hasTag('mcp.loader')); } + public function testRequestHandlerInterfaceAutoconfiguration() + { + $container = $this->buildContainer([]); + $autoconfigured = $container->getAutoconfiguredInstanceof(); + $this->assertArrayHasKey(RequestHandlerInterface::class, $autoconfigured); + $definition = $autoconfigured[RequestHandlerInterface::class]; + $this->assertTrue($definition->hasTag('mcp.request_handler')); + } + + public function testNotificationHandlerInterfaceAutoconfiguration() + { + $container = $this->buildContainer([]); + $autoconfigured = $container->getAutoconfiguredInstanceof(); + $this->assertArrayHasKey(NotificationHandlerInterface::class, $autoconfigured); + $definition = $autoconfigured[NotificationHandlerInterface::class]; + $this->assertTrue($definition->hasTag('mcp.notification_handler')); + } + private function buildContainer(array $configuration): ContainerBuilder { $container = new ContainerBuilder(); diff --git a/src/mcp-bundle/tests/DependencyInjection/McpPassTest.php b/src/mcp-bundle/tests/DependencyInjection/McpPassTest.php index 7262aa3151..1f3a1e0882 100644 --- a/src/mcp-bundle/tests/DependencyInjection/McpPassTest.php +++ b/src/mcp-bundle/tests/DependencyInjection/McpPassTest.php @@ -138,37 +138,4 @@ public function testHandlesPartialMcpServices() $this->assertInstanceOf(Reference::class, $services['tool_service']->getValues()[0]); $this->assertInstanceOf(Reference::class, $services['prompt_service']->getValues()[0]); } - - public function testInjectsLoadersIntoBuilder() - { - $container = new ContainerBuilder(); - $container->setDefinition('mcp.server.builder', new Definition()); - - $container->setDefinition('loader_one', (new Definition())->addTag('mcp.loader')); - $container->setDefinition('loader_two', (new Definition())->addTag('mcp.loader')); - - $pass = new McpPass(); - $pass->process($container); - - $builderDefinition = $container->getDefinition('mcp.server.builder'); - $methodCalls = $builderDefinition->getMethodCalls(); - - $addLoadersCall = null; - foreach ($methodCalls as $call) { - if ('addLoaders' === $call[0]) { - $addLoadersCall = $call; - break; - } - } - - $this->assertNotNull($addLoadersCall, 'Builder should have addLoaders method call'); - - // Verify arguments are References - $args = $addLoadersCall[1]; - $this->assertContainsOnlyInstancesOf(Reference::class, $args); - - $ids = array_map(fn (Reference $ref) => (string) $ref, $args); - $this->assertContains('loader_one', $ids); - $this->assertContains('loader_two', $ids); - } }