Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions demo/src/Mcp/Tools/CurrentTimeTool.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,18 @@
namespace App\Mcp\Tools;

use Mcp\Capability\Attribute\McpTool;
use Psr\Log\LoggerInterface;

/**
* @author Tom Hart <tom.hart.221@gmail.com>
*/
class CurrentTimeTool
{
public function __construct(
private readonly LoggerInterface $logger,
) {
}

/**
* Returns the current time in UTC.
*
Expand All @@ -26,6 +32,8 @@ class CurrentTimeTool
#[McpTool(name: 'current-time')]
public function getCurrentTime(string $format = 'Y-m-d H:i:s'): string
{
$this->logger->info('CurrentTimeTool called', ['format' => $format]);

return (new \DateTime('now', new \DateTimeZone('UTC')))->format($format);
}
}
27 changes: 27 additions & 0 deletions docs/bundles/mcp-bundle.rst
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,33 @@ Dynamic resources with parameters:

All capabilities are automatically discovered in the ``src/`` directory when the server starts.

Attribute Placement Patterns
^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The MCP SDK, and therefore the MCP Bundle, supports two patterns for placing attributes on your capabilities:

**Invokable Pattern** - Attribute on a class with ``__invoke()`` method::

#[McpTool(name: 'my-tool')]
class MyTool
{
public function __invoke(string $param): string
{
// Implementation
}
}

**Method-Based Pattern** - Multiple attributes on individual methods::

class MyTools
{
#[McpTool(name: 'tool-one')]
public function toolOne(): string { }

#[McpTool(name: 'tool-two')]
public function toolTwo(): string { }
}

Transport Types
...............

Expand Down
8 changes: 7 additions & 1 deletion src/mcp-bundle/src/DependencyInjection/McpPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;

final class McpPass implements CompilerPassInterface
{
Expand All @@ -35,7 +36,12 @@ public function process(ContainerBuilder $container): void
return;
}

$serviceLocatorRef = ServiceLocatorTagPass::register($container, $allMcpServices);
$serviceReferences = [];
foreach (array_keys($allMcpServices) as $serviceId) {
$serviceReferences[$serviceId] = new Reference($serviceId);
}

$serviceLocatorRef = ServiceLocatorTagPass::register($container, $serviceReferences);

$container->getDefinition('mcp.server.builder')
->addMethodCall('setContainer', [$serviceLocatorRef]);
Expand Down
2 changes: 1 addition & 1 deletion src/mcp-bundle/src/McpBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ private function registerMcpAttributes(ContainerBuilder $builder): void
foreach ($mcpAttributes as $attributeClass => $tag) {
$builder->registerAttributeForAutoconfiguration(
$attributeClass,
static function (ChildDefinition $definition) use ($tag): void {
static function (ChildDefinition $definition, object $attribute, \Reflector $reflector) use ($tag): void {
$definition->addTag($tag);
}
);
Expand Down
22 changes: 22 additions & 0 deletions src/mcp-bundle/tests/DependencyInjection/McpPassTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@

use PHPUnit\Framework\TestCase;
use Symfony\AI\McpBundle\DependencyInjection\McpPass;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;

/**
* @covers \Symfony\AI\McpBundle\DependencyInjection\McpPass
Expand Down Expand Up @@ -53,6 +55,18 @@ public function testCreatesServiceLocatorForAllMcpServices()
$this->assertArrayHasKey('prompt_service', $services);
$this->assertArrayHasKey('resource_service', $services);
$this->assertArrayHasKey('template_service', $services);

// Verify services are ServiceClosureArguments wrapping References
$this->assertInstanceOf(ServiceClosureArgument::class, $services['tool_service']);
$this->assertInstanceOf(ServiceClosureArgument::class, $services['prompt_service']);
$this->assertInstanceOf(ServiceClosureArgument::class, $services['resource_service']);
$this->assertInstanceOf(ServiceClosureArgument::class, $services['template_service']);

// Verify the underlying values are References
$this->assertInstanceOf(Reference::class, $services['tool_service']->getValues()[0]);
$this->assertInstanceOf(Reference::class, $services['prompt_service']->getValues()[0]);
$this->assertInstanceOf(Reference::class, $services['resource_service']->getValues()[0]);
$this->assertInstanceOf(Reference::class, $services['template_service']->getValues()[0]);
}

public function testDoesNothingWhenNoMcpServicesTagged()
Expand Down Expand Up @@ -115,5 +129,13 @@ public function testHandlesPartialMcpServices()
$this->assertArrayHasKey('prompt_service', $services);
$this->assertArrayNotHasKey('resource_service', $services);
$this->assertArrayNotHasKey('template_service', $services);

// Verify services are ServiceClosureArguments wrapping References
$this->assertInstanceOf(ServiceClosureArgument::class, $services['tool_service']);
$this->assertInstanceOf(ServiceClosureArgument::class, $services['prompt_service']);

// Verify the underlying values are References
$this->assertInstanceOf(Reference::class, $services['tool_service']->getValues()[0]);
$this->assertInstanceOf(Reference::class, $services['prompt_service']->getValues()[0]);
}
}