Skip to content

Commit 7adba8f

Browse files
committed
feature #1091 [MCP Bundle] autoconfigure request and notification handlers (soyuka)
This PR was merged into the main branch. Discussion ---------- [MCP Bundle] autoconfigure request and notification handlers | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | Docs? | no | Issues | na | License | MIT related to modelcontextprotocol/php-sdk#183 Commits ------- e0e849f [MCP Bundle] autoconfigure request and notification handlers
2 parents 6c51b9f + e0e849f commit 7adba8f

File tree

5 files changed

+71
-42
lines changed

5 files changed

+71
-42
lines changed

src/mcp-bundle/config/services.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
3434
->call('setEventDispatcher', [service('event_dispatcher')])
3535
->call('setRegistry', [service('mcp.registry')])
3636
->call('setSession', [service('mcp.session.store')])
37+
->call('addRequestHandlers', [tagged_iterator('mcp.request_handler')])
38+
->call('addNotificationHandlers', [tagged_iterator('mcp.notification_handler')])
39+
->call('addLoaders', [tagged_iterator('mcp.loader')])
3740
->call('setDiscovery', [param('kernel.project_dir'), param('mcp.discovery.scan_dirs'), param('mcp.discovery.exclude_dirs')])
3841

3942
->set('mcp.server', Server::class)

src/mcp-bundle/src/DependencyInjection/McpPass.php

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,6 @@ public function process(ContainerBuilder $container): void
2727
return;
2828
}
2929

30-
$definition = $container->getDefinition('mcp.server.builder');
31-
32-
$loaderReferences = $this->findAndSortTaggedServices('mcp.loader', $container);
33-
if ([] !== $loaderReferences) {
34-
$definition->addMethodCall('addLoaders', $loaderReferences);
35-
}
36-
3730
$allMcpServices = [];
3831
$mcpTags = ['mcp.tool', 'mcp.prompt', 'mcp.resource', 'mcp.resource_template'];
3932

@@ -52,6 +45,6 @@ public function process(ContainerBuilder $container): void
5245
}
5346

5447
$serviceLocatorRef = ServiceLocatorTagPass::register($container, $serviceReferences);
55-
$definition->addMethodCall('setContainer', [$serviceLocatorRef]);
48+
$container->getDefinition('mcp.server.builder')->addMethodCall('setContainer', [$serviceLocatorRef]);
5649
}
5750
}

src/mcp-bundle/src/McpBundle.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
use Mcp\Capability\Attribute\McpResourceTemplate;
1818
use Mcp\Capability\Attribute\McpTool;
1919
use Mcp\Capability\Registry\Loader\LoaderInterface;
20+
use Mcp\Server\Handler\Notification\NotificationHandlerInterface;
21+
use Mcp\Server\Handler\Request\RequestHandlerInterface;
2022
use Mcp\Server\Session\FileSessionStore;
2123
use Mcp\Server\Session\InMemorySessionStore;
2224
use Symfony\AI\McpBundle\Command\McpCommand;
@@ -62,6 +64,12 @@ public function loadExtension(array $config, ContainerConfigurator $container, C
6264
$builder->registerForAutoconfiguration(LoaderInterface::class)
6365
->addTag('mcp.loader');
6466

67+
$builder->registerForAutoconfiguration(RequestHandlerInterface::class)
68+
->addTag('mcp.request_handler');
69+
70+
$builder->registerForAutoconfiguration(NotificationHandlerInterface::class)
71+
->addTag('mcp.notification_handler');
72+
6573
if ($builder->getParameter('kernel.debug')) {
6674
$traceableRegistry = (new Definition('mcp.traceable_registry'))
6775
->setClass(TraceableRegistry::class)

src/mcp-bundle/tests/DependencyInjection/McpBundleTest.php

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@
1212
namespace Symfony\AI\McpBundle\Tests\DependencyInjection;
1313

1414
use Mcp\Capability\Registry\Loader\LoaderInterface;
15+
use Mcp\Server\Handler\Notification\NotificationHandlerInterface;
16+
use Mcp\Server\Handler\Request\RequestHandlerInterface;
1517
use PHPUnit\Framework\Attributes\DataProvider;
1618
use PHPUnit\Framework\TestCase;
1719
use Symfony\AI\McpBundle\McpBundle;
20+
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
1821
use Symfony\Component\DependencyInjection\ContainerBuilder;
1922

2023
class McpBundleTest extends TestCase
@@ -150,13 +153,50 @@ public function testServerServices()
150153
$methodCalls = $builderDefinition->getMethodCalls();
151154

152155
$hasEventDispatcherCall = false;
156+
$hasRequestHandlers = false;
157+
$hasNotificationHandlers = false;
158+
$hasLoaders = false;
159+
153160
foreach ($methodCalls as $call) {
154161
if ('setEventDispatcher' === $call[0]) {
155162
$hasEventDispatcherCall = true;
156-
break;
163+
}
164+
165+
if ('addRequestHandlers' === $call[0]) {
166+
$argument = $call[1][0];
167+
if (
168+
$argument instanceof TaggedIteratorArgument
169+
&& 'mcp.request_handler' === $argument->getTag()
170+
) {
171+
$hasRequestHandlers = true;
172+
}
173+
}
174+
175+
if ('addNotificationHandlers' === $call[0]) {
176+
$argument = $call[1][0];
177+
if (
178+
$argument instanceof TaggedIteratorArgument
179+
&& 'mcp.notification_handler' === $argument->getTag()
180+
) {
181+
$hasNotificationHandlers = true;
182+
}
183+
}
184+
185+
if ('addLoaders' === $call[0]) {
186+
$argument = $call[1][0];
187+
if (
188+
$argument instanceof TaggedIteratorArgument
189+
&& 'mcp.loader' === $argument->getTag()
190+
) {
191+
$hasLoaders = true;
192+
}
157193
}
158194
}
195+
159196
$this->assertTrue($hasEventDispatcherCall, 'ServerBuilder should have setEventDispatcher method call');
197+
$this->assertTrue($hasRequestHandlers, 'ServerBuilder should have addRequestHandlers with mcp.request_handler tag');
198+
$this->assertTrue($hasNotificationHandlers, 'ServerBuilder should have addNotificationHandlers with mcp.notification_handler tag');
199+
$this->assertTrue($hasLoaders, 'ServerBuilder should have addLoaders with mcp.loader tag');
160200
}
161201

162202
public function testMcpToolAttributeAutoconfiguration()
@@ -359,6 +399,24 @@ public function testLoaderInterfaceAutoconfiguration()
359399
$this->assertTrue($definition->hasTag('mcp.loader'));
360400
}
361401

402+
public function testRequestHandlerInterfaceAutoconfiguration()
403+
{
404+
$container = $this->buildContainer([]);
405+
$autoconfigured = $container->getAutoconfiguredInstanceof();
406+
$this->assertArrayHasKey(RequestHandlerInterface::class, $autoconfigured);
407+
$definition = $autoconfigured[RequestHandlerInterface::class];
408+
$this->assertTrue($definition->hasTag('mcp.request_handler'));
409+
}
410+
411+
public function testNotificationHandlerInterfaceAutoconfiguration()
412+
{
413+
$container = $this->buildContainer([]);
414+
$autoconfigured = $container->getAutoconfiguredInstanceof();
415+
$this->assertArrayHasKey(NotificationHandlerInterface::class, $autoconfigured);
416+
$definition = $autoconfigured[NotificationHandlerInterface::class];
417+
$this->assertTrue($definition->hasTag('mcp.notification_handler'));
418+
}
419+
362420
private function buildContainer(array $configuration): ContainerBuilder
363421
{
364422
$container = new ContainerBuilder();

src/mcp-bundle/tests/DependencyInjection/McpPassTest.php

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -138,37 +138,4 @@ public function testHandlesPartialMcpServices()
138138
$this->assertInstanceOf(Reference::class, $services['tool_service']->getValues()[0]);
139139
$this->assertInstanceOf(Reference::class, $services['prompt_service']->getValues()[0]);
140140
}
141-
142-
public function testInjectsLoadersIntoBuilder()
143-
{
144-
$container = new ContainerBuilder();
145-
$container->setDefinition('mcp.server.builder', new Definition());
146-
147-
$container->setDefinition('loader_one', (new Definition())->addTag('mcp.loader'));
148-
$container->setDefinition('loader_two', (new Definition())->addTag('mcp.loader'));
149-
150-
$pass = new McpPass();
151-
$pass->process($container);
152-
153-
$builderDefinition = $container->getDefinition('mcp.server.builder');
154-
$methodCalls = $builderDefinition->getMethodCalls();
155-
156-
$addLoadersCall = null;
157-
foreach ($methodCalls as $call) {
158-
if ('addLoaders' === $call[0]) {
159-
$addLoadersCall = $call;
160-
break;
161-
}
162-
}
163-
164-
$this->assertNotNull($addLoadersCall, 'Builder should have addLoaders method call');
165-
166-
// Verify arguments are References
167-
$args = $addLoadersCall[1];
168-
$this->assertContainsOnlyInstancesOf(Reference::class, $args);
169-
170-
$ids = array_map(fn (Reference $ref) => (string) $ref, $args);
171-
$this->assertContains('loader_one', $ids);
172-
$this->assertContains('loader_two', $ids);
173-
}
174141
}

0 commit comments

Comments
 (0)