laravel/mcp
Build MCP (Model Context Protocol) servers in Laravel so AI clients can securely interact with your app. Expose tools, resources, and prompts using familiar Laravel patterns, with docs and integrations designed for rapid setup and deployment.
## Getting Started
### Minimal Setup
1. **Installation**:
```bash
composer require laravel/mcp
Publish the MCP configuration:
php artisan vendor:publish --provider="Laravel\MCP\MCPServiceProvider" --tag="mcp-config"
Register MCP Server:
Define a server in config/mcp.php:
'servers' => [
'primary' => [
'uri' => 'https://your-app.com/mcp',
'port' => env('MCP_PORT', 8000),
'protocol_version' => '2025-11-25',
],
],
First Use Case:
Create a tool using the McpTool trait or attribute:
use Laravel\MCP\Attributes\Tool;
#[Tool]
class UserTool
{
public function getUser(string $id): array
{
return User::findOrFail($id)->toArray();
}
}
Register the tool in App\Providers\MCPServiceProvider:
public function registerTools(): array
{
return [
UserTool::class,
];
}
Run the MCP Server:
php artisan mcp:serve
#[Tool(name: 'search_products', description: 'Search for products in the catalog')]
class ProductTool
{
#[InputSchema(type: 'object', properties: [
'query' => ['type' => 'string', 'description' => 'Search query']
])]
public function search(string $query): array
{
return Product::where('name', 'like', "%{$query}%")->get()->toArray();
}
}
MCP::registerTool(new class {
public function __invoke(array $input): array {
return SomeService::handle($input);
}
});
use Laravel\MCP\Attributes\Resource;
#[Resource(
uri: '/api/users/{id}',
description: 'User resource endpoint'
)]
class UserResource
{
public function get(string $id): array
{
return User::findOrFail($id)->toArray();
}
}
public function streamLogs(string $userId): \Generator
{
foreach (Log::where('user_id', $userId)->cursor() as $log) {
yield $log->toArray();
}
}
MCP::registerOAuthClient(
name: 'github',
redirectUri: 'https://your-app.com/mcp/oauth/callback',
scopes: ['read:user']
);
use Laravel\MCP\Attributes\Authorize;
#[Authorize(roles: [Role::User])]
public function sensitiveData(): array
{
return [...];
}
$response = MCP::fake()
->tool('get_user', fn($id) => ['id' => $id])
->assertToolCalled('get_user', ['1']);
php artisan mcp:inspect --host=localhost --port=8000
#[Tool]
class PostTool
{
public function createPost(array $data): array
{
return Post::create($data)->toArray();
}
}
InputSchema:
#[InputSchema(properties: [
'title' => ['type' => 'string', 'minLength' => 3],
'content' => ['type' => 'string', 'maxLength' => 1000]
])]
public function getCachedData(string $key): array
{
return Cache::remember("mcp_{$key}", now()->addHours(1), fn() => $this->fetchData($key));
}
MCP::middleware(function ($request, $next) {
if ($request->input('debug')) {
\Log::debug('MCP Request', $request->all());
}
return $next($request);
});
MCP::listen('session.initialized', function ($session) {
\Log::info("New MCP session started: {$session->id}");
});
registerTools() or annotated with #[Tool].php artisan mcp:tools to list registered tools.invalid_redirect_uri.
redirectUri matches the MCP server’s base URL (e.g., https://your-app.com/mcp/oauth/callback).MCP::registerOAuthClient() with absolute URIs./mcp/{resource}.
uri in #[Resource] is relative to the MCP server’s base path (e.g., /api/users/{id} becomes /mcp/api/users/{id}).MCP::resourceUri('users', ['id' => 1]) to generate full URIs.MCP::session() to access the current session or middleware to set custom session data.session.initialized to initialize session-specific data.500 Internal Server Error instead of structured JSON-RPC errors.
MCP::error() to throw structured errors:
if (!$user) {
MCP::error('user_not_found', 'User not found');
}
php artisan mcp:inspect to interactively test tools/resources:
php artisan mcp:inspect --host=localhost --port=8000
config/mcp.php:
'logging' => [
'enabled' => true,
'channel' => 'single',
],
storage/logs/mcp.log for detailed request/response cycles.MCP::fake()
->tool('search', fn($query) => ['results' => [$query]])
->assertToolCalled('search', ['test']);
MCP::assertStructuredContent() to validate response schemas.localhost:3000) for local development.protocol_version in config/mcp.php to avoid compatibility issues.Laravel\MCP\Content\Content:
class PdfContent extends Content
{
public static function type(): string
{
return 'application/pdf';
}
public function toResponse(): Response
{
return response()->file(storage_path("app/{$this->path}"));
}
}
MCPServiceProvider:
MCP::contentType(PdfContent::class);
How can I help you explore Laravel packages today?