laravel/mcp
Build MCP servers inside Laravel so AI clients can safely interact with your app via the Model Context Protocol. Includes tools to expose app capabilities, run requests, and integrate quickly using Laravel’s conventions and docs.
Installation:
composer require laravel/mcp
Publish the configuration:
php artisan vendor:publish --provider="Laravel\MCP\McpServiceProvider"
Configure MCP Server:
Edit config/mcp.php to define your server's metadata (e.g., name, description, version).
Define a Tool:
Create a tool using the mcp:tool Artisan command:
php artisan mcp:tool make:tool GetUserData
This generates a stub in app/Mcp/Tools/GetUserData.php. Implement the handle() method to define the tool's logic:
public function handle(ToolRequest $request): ToolResponse
{
$user = User::find($request->id);
return new ToolResponse($user->toArray());
}
Register the Tool:
Bind the tool in app/Providers/McpServiceProvider.php:
public function registerTools()
{
$this->mcp->tool('get_user_data', GetUserData::class);
}
Run the MCP Server: Start the server with:
php artisan mcp:serve
By default, it runs on http://localhost:8000/.well-known/mcp.
Create a Tool:
php artisan mcp:tool make:tool GetPost
Implement the tool to fetch a blog post:
public function handle(ToolRequest $request): ToolResponse
{
$post = Post::findOrFail($request->post_id);
return new ToolResponse($post->toArray());
}
Test with an AI Client: Use an AI client (e.g., LangChain) to call the tool:
{
"jsonrpc": "2.0",
"method": "get_post",
"params": { "post_id": 1 },
"id": 1
}
config/mcp.php:
'server' => [
'name' => 'My Laravel App',
'description' => 'A Laravel-powered MCP server',
'version' => '1.0.0',
],
config/mcp.php under the oauth key if using authentication.Use the mcp:inspect command to debug tool registrations:
php artisan mcp:inspect
This lists all registered tools and their metadata.
mcp:tool Artisan commands to scaffold tools:
php artisan mcp:tool make:tool CreateUser
php artisan mcp:tool make:resource UserResource
Laravel\MCP\Tools\Tool and implement handle(ToolRequest): ToolResponse.mcp:resource:
public function toResponse(ToolRequest $request): ToolResponse
{
return new ToolResponse($this->user->toArray());
}
/users/{id}).public function handle(ToolRequest $request): ToolResponse
{
$request->authenticate(); // Throws if unauthenticated
// ...
}
config/mcp.php and use the withOAuth() helper.config/mcp.php:
'clients' => [
'default' => [
'url' => 'http://localhost:8000/.well-known/mcp',
'timeout' => 5.0,
],
],
$response = Mcp::client('default')->call('get_user_data', ['id' => 1]);
StreamableResponse:
public function handle(ToolRequest $request): ToolResponse
{
return new ToolResponse(
new StreamableResponse(
Storage::disk('public')->readStream('large-file.pdf')
)
);
}
assertStructuredContent() to validate tool responses:
public function handle(ToolRequest $request): ToolResponse
{
return new ToolResponse($request->assertStructuredContent(function ($data) {
return User::validate($data);
}));
}
Mcp\Resources\HtmlResource.ToolTestCase:
use Laravel\MCP\Testing\ToolTestCase;
class GetUserDataTest extends ToolTestCase
{
public function test_handle()
{
$response = $this->callTool('get_user_data', ['id' => 1]);
$response->assertSuccessful();
}
}
public function test_client_calls_tool()
{
$response = Mcp::client('test')->call('get_user_data', ['id' => 1]);
$this->assertEquals(['name' => 'John'], $response->data());
}
public function registerTools()
{
foreach (config('mcp.tools') as $name => $class) {
$this->mcp->tool($name, $class);
}
}
public function handle(ToolRequest $request): ToolResponse
{
$user = $this->mcp->call('get_user_data', ['id' => $request->user_id]);
return new ToolResponse($user->data()['posts']);
}
public function handle(ToolRequest $request, Closure $next)
{
try {
return $next($request);
} catch (\Exception $e) {
return new ToolResponse([
'error' => $e->getMessage(),
], 500);
}
}
config/mcp.php:
'cache' => [
'enabled' => true,
'ttl' => 60, // Cache for 60 seconds
],
php artisan octane:start --server=swoole
Circular References in Tool Responses:
User with a posts relationship that loops back to User). Use ->toArray() or ->makeHidden() to break cycles.Laravel\MCP\Tools\Resource::makeHidden() or ->toArray():
return new ToolResponse($user->makeHidden(['password'])->toArray());
OAuth Redirect URIs:
config/mcp.php match the AI client's callback URL exactly (including http vs https).'oauth' => [
'clients' => [
'client_id' => [
'redirect_uri' => 'http://localhost:3000/callback',
],
],
],
JSON-RPC ID Handling:
id field in JSON-RPC requests must be a string or integer. Non-string/non-integer IDs (e.g., null or objects) will cause TypeError.if (!is_string($request->id) && !is_int($request->id)) {
throw new \InvalidArgumentException('JSON-RPC id must be a string or integer.');
}
Tool Registration Order:
How can I help you explore Laravel packages today?