Symfony Integration
Complete Symfony integration with bundle configuration, console commands, and dependency injection patterns.
Overview
The PHP MCP SDK integrates seamlessly with Symfony through:
- Bundle configuration and services
- Console commands for MCP management
- Event system integration
- Dependency injection patterns
- Twig extensions for templating
Bundle Setup
Create MCP Bundle
Create src/Bundle/McpBundle/McpBundle.php
:
php
<?php
namespace App\Bundle\McpBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use App\Bundle\McpBundle\DependencyInjection\McpExtension;
class McpBundle extends Bundle
{
public function build(ContainerBuilder $container): void
{
parent::build($container);
}
public function getContainerExtension()
{
return new McpExtension();
}
}
Dependency Injection Extension
Create src/Bundle/McpBundle/DependencyInjection/McpExtension.php
:
php
<?php
namespace App\Bundle\McpBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\Config\FileLocator;
class McpExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container): void
{
$loader = new YamlFileLoader(
$container,
new FileLocator(__DIR__.'/../Resources/config')
);
$loader->load('services.yaml');
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$container->setParameter('mcp.server.name', $config['server']['name']);
$container->setParameter('mcp.server.version', $config['server']['version']);
}
}
Service Configuration
services.yaml
Create src/Bundle/McpBundle/Resources/config/services.yaml
:
yaml
services:
mcp.server:
class: MCP\Server\McpServer
arguments:
- "@mcp.implementation"
calls:
- ["setLogger", ["@logger"]]
tags:
- { name: monolog.logger, channel: mcp }
mcp.implementation:
class: MCP\Types\Implementation
arguments:
- "%mcp.server.name%"
- "%mcp.server.version%"
- "Symfony MCP Server"
mcp.tool.user_manager:
class: App\Mcp\Tools\UserManagerTool
arguments:
- "@doctrine.orm.entity_manager"
tags:
- { name: mcp.tool }
mcp.tool.cache_manager:
class: App\Mcp\Tools\CacheManagerTool
arguments:
- "@cache.app"
tags:
- { name: mcp.tool }
Console Commands
MCP Server Command
Create src/Command/McpServerCommand.php
:
php
<?php
namespace App\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use MCP\Server\McpServer;
use MCP\Server\Transport\StdioServerTransport;
use MCP\Server\Transport\HttpServerTransport;
#[AsCommand(
name: 'mcp:server',
description: 'Start MCP server'
)]
class McpServerCommand extends Command
{
public function __construct(
private McpServer $mcpServer
) {
parent::__construct();
}
protected function configure(): void
{
$this
->addOption('transport', 't', InputOption::VALUE_OPTIONAL, 'Transport type', 'stdio')
->addOption('host', null, InputOption::VALUE_OPTIONAL, 'HTTP host', '127.0.0.1')
->addOption('port', 'p', InputOption::VALUE_OPTIONAL, 'HTTP port', 3000);
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$transport = $input->getOption('transport');
$output->writeln("Starting MCP server with {$transport} transport...");
try {
if ($transport === 'http') {
$httpTransport = new HttpServerTransport([
'host' => $input->getOption('host'),
'port' => (int) $input->getOption('port'),
]);
$output->writeln("HTTP server starting on {$input->getOption('host')}:{$input->getOption('port')}");
$this->mcpServer->connect($httpTransport)->await();
} else {
$stdioTransport = new StdioServerTransport();
$this->mcpServer->connect($stdioTransport)->await();
}
return Command::SUCCESS;
} catch (\Exception $e) {
$output->writeln("<error>Failed to start MCP server: {$e->getMessage()}</error>");
return Command::FAILURE;
}
}
}
MCP Tools as Services
User Manager Tool
Create src/Mcp/Tools/UserManagerTool.php
:
php
<?php
namespace App\Mcp\Tools;
use Doctrine\ORM\EntityManagerInterface;
use MCP\Server\McpServer;
use MCP\Types\McpError;
use MCP\Types\ErrorCode;
class UserManagerTool
{
public function __construct(
private EntityManagerInterface $entityManager
) {}
public function register(McpServer $server): void
{
$server->tool(
'get_users',
'Get users from database',
[
'type' => 'object',
'properties' => [
'limit' => ['type' => 'integer', 'default' => 10],
'search' => ['type' => 'string']
]
],
[$this, 'getUsers']
);
$server->tool(
'create_user',
'Create new user',
[
'type' => 'object',
'properties' => [
'email' => ['type' => 'string', 'format' => 'email'],
'name' => ['type' => 'string', 'minLength' => 1]
],
'required' => ['email', 'name']
],
[$this, 'createUser']
);
}
public function getUsers(array $params): array
{
$repository = $this->entityManager->getRepository(\App\Entity\User::class);
$queryBuilder = $repository->createQueryBuilder('u');
if (!empty($params['search'])) {
$queryBuilder->where('u.name LIKE :search OR u.email LIKE :search')
->setParameter('search', "%{$params['search']}%");
}
$users = $queryBuilder->setMaxResults($params['limit'] ?? 10)
->getQuery()
->getResult();
return [
'content' => [[
'type' => 'text',
'text' => json_encode(array_map(function($user) {
return [
'id' => $user->getId(),
'name' => $user->getName(),
'email' => $user->getEmail(),
'created_at' => $user->getCreatedAt()->format('c')
];
}, $users), JSON_PRETTY_PRINT)
]]
];
}
public function createUser(array $params): array
{
$user = new \App\Entity\User();
$user->setName($params['name']);
$user->setEmail($params['email']);
$user->setCreatedAt(new \DateTime());
$this->entityManager->persist($user);
$this->entityManager->flush();
return [
'content' => [[
'type' => 'text',
'text' => "User created successfully with ID: {$user->getId()}"
]]
];
}
}
Configuration
Bundle Configuration
Create config/packages/mcp.yaml
:
yaml
mcp:
server:
name: "%env(MCP_SERVER_NAME)%"
version: "%env(MCP_SERVER_VERSION)%"
description: "Symfony MCP Server"
transport:
default: "%env(MCP_TRANSPORT)%"
stdio:
buffer_size: 8192
http:
host: "%env(MCP_HTTP_HOST)%"
port: "%env(MCP_HTTP_PORT)%"
ssl: "%env(bool:MCP_HTTP_SSL)%"
auth:
enabled: "%env(bool:MCP_AUTH_ENABLED)%"
provider: "symfony_security"
tools:
enabled:
user_management: true
cache_management: true
console_commands: false
Event Integration
MCP Event Listener
Create src/EventListener/McpEventListener.php
:
php
<?php
namespace App\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use App\Event\McpToolCalledEvent;
use Psr\Log\LoggerInterface;
class McpEventListener implements EventSubscriberInterface
{
public function __construct(
private LoggerInterface $logger
) {}
public static function getSubscribedEvents(): array
{
return [
McpToolCalledEvent::class => 'onToolCalled',
];
}
public function onToolCalled(McpToolCalledEvent $event): void
{
$this->logger->info('MCP tool called', [
'tool' => $event->getToolName(),
'params' => $event->getParameters(),
'duration' => $event->getDuration(),
'success' => $event->isSuccess()
]);
}
}
Twig Integration
MCP Twig Extension
Create src/Twig/McpExtension.php
:
php
<?php
namespace App\Twig;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
use MCP\Client\Client;
class McpExtension extends AbstractExtension
{
public function __construct(
private Client $mcpClient
) {}
public function getFunctions(): array
{
return [
new TwigFunction('mcp_call_tool', [$this, 'callTool']),
new TwigFunction('mcp_read_resource', [$this, 'readResource']),
];
}
public function callTool(string $name, array $params = []): array
{
try {
return $this->mcpClient->callTool($name, $params)->await();
} catch (\Exception $e) {
return ['error' => $e->getMessage()];
}
}
public function readResource(string $uri): array
{
try {
return $this->mcpClient->readResource($uri)->await();
} catch (\Exception $e) {
return ['error' => $e->getMessage()];
}
}
}
Usage in Templates
twig
{# templates/dashboard.html.twig #}
<div class="mcp-dashboard">
<h2>MCP Integration Dashboard</h2>
<div class="tools-section">
<h3>Available Tools</h3>
{% set tools_result = mcp_call_tool('list_tools') %}
{% if tools_result.error is not defined %}
<ul>
{% for tool in tools_result.tools %}
<li>{{ tool.name }} - {{ tool.description }}</li>
{% endfor %}
</ul>
{% else %}
<p class="error">Error: {{ tools_result.error }}</p>
{% endif %}
</div>
<div class="resources-section">
<h3>System Information</h3>
{% set system_info = mcp_read_resource('system://info') %}
{% if system_info.error is not defined %}
<pre>{{ system_info.contents[0].text }}</pre>
{% endif %}
</div>
</div>
Testing
Symfony Test Case
php
<?php
namespace App\Tests\Integration;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use MCP\Server\McpServer;
class McpIntegrationTest extends WebTestCase
{
public function testMcpServerService(): void
{
$client = static::createClient();
$container = $client->getContainer();
$mcpServer = $container->get(McpServer::class);
$this->assertInstanceOf(McpServer::class, $mcpServer);
}
public function testMcpApiEndpoint(): void
{
$client = static::createClient();
$client->request('GET', '/api/mcp/tools');
$this->assertResponseIsSuccessful();
$this->assertResponseHeaderSame('Content-Type', 'application/json');
}
}
Best Practices
1. Service Organization
- Register MCP tools as separate services
- Use dependency injection for tool dependencies
- Implement proper service tagging
- Follow Symfony service conventions
2. Configuration Management
- Use environment variables for sensitive data
- Implement proper configuration validation
- Use Symfony's configuration component
- Support multiple environments
3. Security
- Integrate with Symfony Security component
- Use proper authentication and authorization
- Validate all inputs using Symfony Validator
- Implement CSRF protection for web endpoints
4. Performance
- Use Symfony's cache component for MCP results
- Implement proper logging with Monolog
- Use async processing where appropriate
- Monitor performance with Symfony Profiler