Install the Bundle
composer require ajtis/mcp-bundle
Ensure your config/bundles.php includes:
return [
// ...
Ajtis\McpBundle\McpBundle::class => ['all' => true],
];
Configure MCP SDK
Add MCP credentials to .env:
MCP_API_KEY=your_api_key_here
MCP_ENDPOINT=https://api.modelcontextprotocol.io
First Use Case: Basic Tool Registration Define a tool in a Symfony service:
use Ajtis\McpBundle\Tool\ToolInterface;
class MyTool implements ToolInterface {
public function execute(array $input): array {
return ['result' => 'processed'];
}
}
Register it in services.yaml:
services:
Ajtis\McpBundle\Tool\ToolInterface $my_tool: '@App\Tool\MyTool'
Trigger MCP via CLI Use the Symfony console command:
php bin/console mcp:run --tool=my_tool --input='{"param":"value"}'
Tool Development
Extend ToolInterface and implement execute():
class DatabaseQueryTool implements ToolInterface {
public function execute(array $input): array {
$query = $input['query'];
// Execute DB logic
return ['results' => $data];
}
}
Resource Templates (Experimental) Define templates in YAML (awaiting SDK support):
# config/packages/mcp_resources.yaml
mcp_resources:
templates:
user_profile:
type: "user"
fields:
- name
- email
HTTP Transport Integration Configure routes for MCP server mode:
# config/routes/mcp.yaml
mcp_server:
path: /mcp
controller: Ajtis\McpBundle\Controller\McpController::handleRequest
Prompt Management
Use the PromptManager service to define reusable prompts:
$promptManager = $container->get('mcp.prompt_manager');
$promptManager->addPrompt('generate_report', 'Generate a report for {user}');
Dependency Injection
Inject McpClient for programmatic usage:
use Ajtis\McpBundle\Client\McpClient;
class MyService {
public function __construct(private McpClient $mcpClient) {}
public function fetchData() {
$response = $this->mcpClient->callTool('data_fetcher', ['query' => '...']);
}
}
Event Listeners: Subscribe to mcp.tool.executed events for post-processing:
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Ajtis\McpBundle\Event\ToolExecutedEvent;
class ToolListener implements EventSubscriberInterface {
public static function getSubscribedEvents() {
return [ToolExecutedEvent::class => 'onToolExecuted'];
}
public function onToolExecuted(ToolExecutedEvent $event) {
// Log or transform results
}
}
Configuration Overrides: Extend MCP config in config/packages/mcp.yaml:
mcp:
tools:
my_tool:
class: '%kernel.project_dir%/src/Tool/MyTool'
options:
timeout: 30
Testing: Use McpTestClient for unit tests:
$testClient = new McpTestClient();
$response = $testClient->callTool('test_tool', ['input' => 'data']);
$this->assertEquals(['output'], $response);
Experimental Status
if ($this->mcpClient->isSupported()) {
// Safe to use MCP
}
Resource Templates
HTTP Transport Quirks
php-http/discovery is configured for custom clients:
putenv('HTTP_CLIENT=custom');
Dependency Conflicts
mcp/sdk@^0.4 may conflict with other HTTP clients. Isolate dependencies:
composer require --with-all-dependencies ajtis/mcp-bundle
Enable Verbose Logging
php bin/console debug:config mcp | grep logging
Set level: debug in config/packages/monolog.yaml.
Tool Execution Logs
Check var/log/dev.log for:
[MCP] Tool "my_tool" executed with input: {"param":"value"}
SDK-Specific Errors Wrap MCP calls in try-catch:
try {
$result = $this->mcpClient->callTool('tool_name', $input);
} catch (\MCP\SDK\Exception\TransportException $e) {
// Handle network issues
}
Custom Transports
Implement Ajtis\McpBundle\Transport\TransportInterface for non-HTTP/STDIO:
class RabbitMqTransport implements TransportInterface {
public function send(array $payload): string {
// RabbitMQ logic
}
}
Prompt Preprocessors
Extend Ajtis\McpBundle\Prompt\PromptPreprocessorInterface to modify prompts:
class SecurityPromptPreprocessor implements PromptPreprocessorInterface {
public function preprocess(string $prompt, array $context): string {
return str_replace('{user}', $this->sanitize($context['user']), $prompt);
}
}
Tool Validation Add validation rules to tools via annotations or YAML:
mcp:
tools:
my_tool:
validation:
input:
- required: true
- type: object
Performance Optimization
Cache tool responses with symfony/cache:
$cache = $container->get('cache.app');
$cacheKey = 'mcp:tool:my_tool:'.md5(json_encode($input));
if (!$cache->has($cacheKey)) {
$result = $this->mcpClient->callTool('my_tool', $input);
$cache->set($cacheKey, $result, 300); // 5-minute cache
}
How can I help you explore Laravel packages today?