redberry/mcp-client-laravel
Laravel client for the Model Context Protocol (MCP). Supports JSON-RPC 2.0 over Streamable HTTP (including SSE) and STDIO. Configure multiple servers and use a single facade to list/call tools and read resources, with per-request content negotiation.
composer require redberry/mcp-client-laravel
php artisan vendor:publish --tag="mcp-client-config"
config/mcp-client.php (e.g., GitHub Copilot or a local memory server):
'servers' => [
'github' => [
'type' => \Redberry\MCPClient\Enums\Transporters::HTTP,
'base_url' => 'https://api.githubcopilot.com/mcp',
'token' => env('GITHUB_API_TOKEN'),
],
],
use Redberry\MCPClient\Facades\MCPClient;
$tools = MCPClient::connect('github')->tools();
$toolNames = $tools->pluck('name'); // ['search', 'create_issue', ...]
Inject the MCPClient contract or facade into services:
use Redberry\MCPClient\Contracts\MCPClient;
class GitHubService {
public function __construct(private MCPClient $client) {}
public function listTools() {
return $this->client->connect('github')->tools();
}
}
Cache handles for multiple servers:
$github = MCPClient::connect('github');
$memory = MCPClient::connect('memory');
// Reuse handles; no re-initialization
$github->callTool('search', ['query' => 'laravel']);
Observe intermediate events (e.g., logs, progress):
$result = MCPClient::connect('github')
->callTool('long_task', ['input' => 'data'], function (array $event) {
Log::debug('MCP Event:', $event);
});
Fetch resources by URI:
$fileContent = MCPClient::connect('memory')
->readResource('file:///project/src/AppServiceProvider.php');
Configure a local subprocess (e.g., @modelcontextprotocol/server-memory):
'memory' => [
'type' => \Redberry\MCPClient\Enums\Transporters::STDIO,
'command' => ['npx', '-y', '@modelcontextprotocol/server-memory'],
'cwd' => base_path(),
],
STDIO Transport Limitation:
php artisan serve (process killed between requests).Session Retries:
max_session_retries).mcp-session-id headers in responses.STDIO Timeouts:
timeout (legacy) vs. request_timeout/process_timeout:
request_timeout: Wait for response (default: 30s).process_timeout: Kill subprocess (default: null).process_timeout if you need a hard cap.JSON-RPC id Handling:
id for notifications (e.g., notifications/initialized).id_type: 'string' in config if IDs clash with numeric keys.Enable Guzzle Middleware (HTTP transport):
$client = new \GuzzleHttp\Client([
'handler' => \GuzzleHttp\HandlerStack::create([
new \GuzzleHttp\Middleware::tap(function ($request) {
Log::debug('MCP Request:', $request->getBody());
}),
]),
]);
Pass via HttpTransporter constructor.
STDIO Logs: Capture subprocess output:
$transporter = new \Redberry\MCPClient\Transporters\StdioTransporter(
command: ['npx', 'server'],
cwd: base_path(),
onOutput: function ($type, $buffer) {
Log::debug("STDIO [{$type}]:", $buffer);
}
);
Custom Transports:
Redberry\MCPClient\Core\Transporters\Transporter.Transporters::case() enum and TransporterFactory::make().Override Default Client:
$this->app->bind(\Redberry\MCPClient\Contracts\MCPClient::class, function () {
return new \Redberry\MCPClient\MCPClient(
config('mcp-client.servers'),
new \Your\Custom\TransporterFactory()
);
});
Stream Parsing:
SseStreamParser to handle custom SSE formats.headers key:
'headers' => ['X-Custom-Header' => 'value'],
Authorization: Bearer {token} for HTTP.inherit_env: false to isolate the subprocess.MCPClient::connect('server') to avoid re-initialization.callTool with arrays for parallelizable operations (if supported by the server).onEvent callbacks for large responses to avoid memory spikes.How can I help you explore Laravel packages today?