Installation:
composer require mcp/sdk
First Use Case - Exposing a Laravel Controller as an MCP Tool:
use Mcp\Server;
use Mcp\Server\Transport\StdioTransport;
use Mcp\Capability\Attribute\McpTool;
class CalculatorController {
#[McpTool]
public function add(int $a, int $b): int {
return $a + $b;
}
}
$server = Server::builder()
->setServerInfo('Laravel MCP Server', '1.0.0')
->setDiscovery(__DIR__, ['App/Http/Controllers'])
->build();
$transport = new StdioTransport();
$server->run($transport);
First Use Case - Consuming an MCP Server from Laravel:
use Mcp\Client;
use Mcp\Client\Transport\HttpTransport;
$client = Client::builder()
->setClientInfo('Laravel Client', '1.0.0')
->build();
$transport = new HttpTransport('http://localhost:8000');
$client->connect($transport);
$result = $client->callTool('add', ['a' => 5, 'b' => 3]);
$client->disconnect();
docs/server-builder.md for configuration optionsdocs/client.md for API referencedocs/examples.md for Laravel-specific patternssymfony/mcp-bundle for Symfony/Laravel-specific extensions// app/Providers/McpServiceProvider.php
public function register()
{
$this->app->singleton('mcp.server', function ($app) {
return Server::builder()
->setServerInfo('Laravel MCP Server', '1.0.0')
->setDiscovery(__DIR__.'/../Http/Controllers', ['.php'])
->setSession(new FileSessionStore(storage_path('framework/sessions')))
->build();
});
}
// app/Console/Commands/RunMcpServer.php
public function handle()
{
$server = app('mcp.server');
$transport = new StreamableHttpTransport(
$this->laravel->make(Request::class),
$this->laravel->make(ResponseFactory::class),
$this->laravel->make(StreamFactory::class)
);
$response = $server->run($transport);
$response->send();
}
// Register tools from Laravel database
$server = Server::builder()
->setServerInfo('Dynamic MCP Server', '1.0.0')
->setDiscovery(__DIR__.'/../Http/Controllers')
->build();
foreach (Tool::all() as $tool) {
$server->getRegistry()->addTool(
[$this->app->make($tool->handler), $tool->method],
$tool->name,
$tool->description
);
}
// Use Laravel HTTP client as transport
$client = Client::builder()
->setClientInfo('Laravel Client', '1.0.0')
->build();
$transport = new HttpTransport(
$this->http->withOptions(['timeout' => 30])
);
$client->connect($transport);
// Create middleware to validate MCP requests
public function handle($request, Closure $next)
{
$transport = new StreamableHttpTransport(
$request,
$this->app->make(ResponseFactory::class),
$this->app->make(StreamFactory::class)
);
$server = app('mcp.server');
$response = $server->run($transport);
return $response->send();
}
// Listen to MCP server events
public function handle(ServerEvent $event)
{
switch ($event->getType()) {
case ServerEvent::CAPABILITIES_DISCOVERED:
$this->logCapabilities($event->getCapabilities());
break;
case ServerEvent::SESSION_CREATED:
$this->createSessionRecord($event->getSessionId());
break;
}
}
#[McpResourceTemplate(uri: 'data://users/{id}')]
public function getUserResourceTemplate(int $id): array
{
return [
'title' => 'User Data',
'description' => 'Retrieve user information',
'schema' => [
'type' => 'object',
'properties' => [
'id' => ['type' => 'integer'],
'name' => ['type' => 'string']
]
]
];
}
#[McpResource(uri: 'data://users/{id}')]
public function getUserResource(int $id): array
{
return User::findOrFail($id)->toArray();
}
Attribute Discovery Limitations:
#[McpTool], #[McpResource], etc. attributes in the discovery paths will be registeredSession Management:
FileSessionStore or Psr16SessionStore for production:
$server->setSession(new FileSessionStore(storage_path('framework/sessions')));
HTTP Transport Quirks:
Content-Type vs content-type)StreamableHttpTransport with proper middleware:
$transport = new StreamableHttpTransport(
$request,
$responseFactory,
$streamFactory,
defaultMiddleware: [
new NormalizeHeadersMiddleware()
]
);
Tool Argument Validation:
Concurrency Issues:
Enable Verbose Logging:
$server = Server::builder()
->setLogger(new MonologLogger([
new StreamHandler(storage_path('logs/mcp.log'), Logger::DEBUG)
]))
->build();
Inspect Capabilities:
$capabilities = $server->getCapabilities();
dump($capabilities->getTools());
dump($capabilities->getResources());
Validate Protocol Compliance:
composer test:conformance
Check Transport Errors:
try {
$client->connect($transport);
} catch (TransportException $e) {
Log::error('MCP Transport Error', ['error' => $e->getMessage()]);
}
Custom Transports:
TransportInterface for custom protocols (e.g., WebSocket, gRPC):
class CustomTransport implements TransportInterface {
public function send(Request $request): Response {
// Custom implementation
}
}
Protocol Extensions:
$server->getCapabilities()->addExtension('my-extension', [
'version' => '1.0',
'features' => ['custom-feature']
]);
Custom Validation:
$server->getRegistry()->setArgumentValidator(
new CustomArgumentValidator()
);
Session Storage:
SessionStoreInterface for custom storage backends:
class DatabaseSessionStore implements SessionStoreInterface {
public function save(Session $session): void {
// Save to database
}
// ... other methods
}
Event Subscribers:
$server->addEventSubscriber(new class implements EventSubscriberInterface {
public function onCapabilitiesDiscovered(CapabilitiesDiscoveredEvent $event) {
// Custom logic
}
});
Service Container Integration:
$this->app->bind(TransportInterface::class, function ($app) {
return new HttpTransport('http://mcp-server');
});
**Queue Jobs for Long-R
How can I help you explore Laravel packages today?