To build an MCP server in Laravel, install the official laravel/mcp package, generate a server class with php artisan make:mcp-server, add tools and resources, and register the server in routes/ai.php. AI clients like Claude, Cursor, and ChatGPT can then interact with your app's code through the Model Context Protocol (MCP).
Imagine your support team asking an AI assistant, “Where is order #4821?” and getting a real answer from your database instead of a polite guess. That's what an MCP server makes possible. The AI stops relying only on its existing knowledge and starts working with your app.
You probably already know what MCP is. If not, our Laravel MCP explainer covers the concept. This tutorial is the hands-on part: we'll build a working server for a small online store, step by step, using code from the official documentation.
You'll build an Orders server with two tools, one resource, Sanctum authentication, tests, and a connection to Claude Code. The tutorial takes about 30 minutes.
Key Takeaways
Laravel has an official MCP package.
laravel/mcp(v1.1, 47M+ Packagist downloads) supports Laravel 11.45+, 12.41+, and 13 on PHP 8.2+.A server is one class plus a route. Generate it with
php artisan make:mcp-server, then register it withMcp::web()for HTTP clients orMcp::local()for local agents.Tools do the work. Each tool defines a JSON schema for its inputs and returns a
Response. Laravel validation, dependency injection, and$request->user()work inside tools.Protect web servers like any other route. Add
auth:sanctumfor bearer tokens, or use Passport withMcp::oauthRoutes()for OAuth 2.1.Test it in two ways. Use
php artisan mcp:inspectorfor manual checks and Pest tests withOrdersServer::tool(...)->assertOk().
What You'll Build
The Model Context Protocol is an open standard for connecting AI clients to applications and data. A Laravel MCP server exposes three kinds of capabilities to AI clients:
Tools: Actions the AI can call, such as searching orders or checking stock.
Resources: Read-only content the AI can load, such as a policy document.
Prompts: Reusable prompt templates that the AI client can offer to users.
For this tutorial, we'll build an MCP server called OrdersServer for an online store, with:
A
SearchOrdersToolthat finds orders by customer email or status.A
GetOrderStatusToolthat returns an order's status as structured data.A
RefundPolicyResourcethat provides the AI with your refund rules.
Requirements: A Laravel 11.45+, 12.41+, or 13 application running PHP 8.2 or higher. The official Laravel MCP documentation covers the available options, and the source code is on GitHub.
The Order model, its columns, and its status values in this tutorial are examples. Adapt them to your application.
Step 1: Install Laravel MCP
Install the package with Composer:
composer require laravel/mcpThen publish the AI routes file:
php artisan vendor:publish --tag=ai-routesThis creates routes/ai.php. It works like routes/web.php, but for MCP servers. Keeping AI endpoints in a separate file makes them easier to find and audit.
Step 2: Create Your Laravel MCP Server
Generate the server class:
php artisan make:mcp-server OrdersServerThis creates app/Mcp/Servers/OrdersServer.php. Fill it in like this:
<?php
namespace App\Mcp\Servers;
use App\Mcp\Resources\RefundPolicyResource;
use App\Mcp\Tools\GetOrderStatusTool;
use App\Mcp\Tools\SearchOrdersTool;
use Laravel\Mcp\Server\Attributes\Instructions;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Version;
use Laravel\Mcp\Server;
#[Name('Orders Server')]
#[Version('1.0.0')]
#[Instructions('Look up customer orders and their status. Use the refund policy resource before answering refund questions.')]
class OrdersServer extends Server
{
protected array $tools = [
SearchOrdersTool::class,
GetOrderStatusTool::class,
];
protected array $resources = [
RefundPolicyResource::class,
];
protected array $prompts = [];
}The #[Instructions] text matters more than it might seem. AI clients read it to understand when and how to use your server, so write it like a short brief for a new colleague.
Building the rest of the app, too? StackMivo generates complete Laravel apps from plain-English descriptions, including models, policies, APIs, and tests. That leaves you more time to work on features like this MCP server.
Step 3: Add Your First Tool
Tools are at the heart of any MCP server you build in Laravel. Generate one:
php artisan make:mcp-tool SearchOrdersToolEach tool has two main parts: a schema() method that describes its inputs and a handle() method that does the work.
<?php
namespace App\Mcp\Tools;
use App\Models\Order;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Tool;
use Laravel\Mcp\Server\Tools\Annotations\IsReadOnly;
#[Description('Searches recent orders by customer email and, optionally, by status.')]
#[IsReadOnly]
class SearchOrdersTool extends Tool
{
public function handle(Request $request): Response
{
if ($request->user()?->cannot('viewAny', Order::class)) {
return Response::error('Permission denied.');
}
$validated = $request->validate([
'email' => ['required', 'email'],
'status' => 'in:pending,shipped,delivered,cancelled',
], [
'email.required' => 'Provide the customer email address, for example "[email protected]".',
'status.in' => 'Status must be one of: pending, shipped, delivered or cancelled.',
]);
$orders = Order::query()
->where('customer_email', $validated['email'])
->when($validated['status'] ?? null, fn ($query, $status) => $query->where('status', $status))
->latest()
->limit(10)
->get(['number', 'status', 'total', 'created_at']);
if ($orders->isEmpty()) {
return Response::text('No orders found for that customer.');
}
return Response::text($orders->toJson());
}
public function schema(JsonSchema $schema): array
{
return [
'email' => $schema->string()
->description('The customer email address.')
->required(),
'status' => $schema->string()
->enum(['pending', 'shipped', 'delivered', 'cancelled'])
->description('Only return orders with this status.'),
];
}
}A few details are worth copying:
Write clear validation messages. When validation fails, the AI client can read the error and try again. “Provide the customer email” gives it a clearer direction than “The email field is required.”
Check permissions first. Without the
viewAnycheck, someone with a valid token could potentially look up any customer's orders by email.Limit what you return. The
limit(10)and selected columns keep responses small and avoid exposing fields the AI doesn't need.Mark read-only tools.
#[IsReadOnly]tells clients that the tool doesn't change anything. Use#[IsDestructive]for tools that delete or overwrite data.
Elif runs a five-person support team for a skincare brand in Istanbul. In September, she added a similar tool to their Laravel store. Agents can now ask Claude, “What did this customer order last month?” instead of switching to the admin panel. Average reply time for order questions fell from about six minutes to under two.
Elif is an illustrative composite based on common support-team scenarios.
Step 4: Return Structured Data
Text works well for conversational answers. When the AI needs specific fields, return structured data instead:
php artisan make:mcp-tool GetOrderStatusTool<?php
namespace App\Mcp\Tools;
use App\Models\Order;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Tool;
use Laravel\Mcp\Server\Tools\Annotations\IsIdempotent;
use Laravel\Mcp\Server\Tools\Annotations\IsReadOnly;
#[Description('Returns the status, carrier and tracking number for a single order.')]
#[IsReadOnly]
#[IsIdempotent]
class GetOrderStatusTool extends Tool
{
public function handle(Request $request): Response
{
$order = Order::where('number', $request->get('number'))->first();
if (! $order) {
return Response::error('Order not found. Check the order number and try again.');
}
if ($request->user()?->cannot('view', $order)) {
return Response::error('Permission denied.');
}
return Response::structured([
'number' => $order->number,
'status' => $order->status,
'carrier' => $order->carrier,
'tracking_number' => $order->tracking_number,
]);
}
public function schema(JsonSchema $schema): array
{
return [
'number' => $schema->string()
->description('The order number, for example "4821".')
->required(),
];
}
}Two things to notice:
Response::error()tells the client that the call failed, so it doesn't present a missing order as a successful answer.$request->user()returns the authenticated user, allowing your normal Laravel policies to work inside MCP tools. Both tools assume anOrderPolicywithviewAnyandviewmethods. Create one withphp artisan make:policy OrderPolicy --model=Order. Without a suitable policy, the authorization check will not grant access.Local servers don't have a signed-in user by default. The
?->operator skips the policy check when the user isnull. Only register a local server on machines you trust, and make sure local access is appropriate for your use case.
Tools are resolved through Laravel's service container, so you can also type-hint services in the constructor or in handle(), just as you would in a controller.
Step 5: Add a Resource
Resources give the AI read-only context. A refund policy is a good example:
php artisan make:mcp-resource RefundPolicyResource<?php
namespace App\Mcp\Resources;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\MimeType;
use Laravel\Mcp\Server\Attributes\Uri;
use Laravel\Mcp\Server\Resource;
#[Description('The store refund and returns policy.')]
#[Uri('orders://resources/refund-policy')]
#[MimeType('text/markdown')]
class RefundPolicyResource extends Resource
{
public function handle(Request $request): Response
{
return Response::text(file_get_contents(resource_path('policies/refunds.md')));
}
}Now, when someone asks, “Can this customer get a refund?”, the AI can read your actual policy before answering. If you omit #[Uri] and #[MimeType], Laravel generates a URI from the class name and defaults to text/plain.
Prompts work similarly. Run php artisan make:mcp-prompt, define the arguments, and return messages from handle(). They're optional, so we'll skip them here.
Step 6: Register and Secure Your MCP Server in Laravel
Open routes/ai.php and register the server. You have two options, and you can use both.
Web server, for remote clients over HTTP:
use App\Mcp\Servers\OrdersServer;
use Laravel\Mcp\Facades\Mcp;
Mcp::web('/mcp/orders', OrdersServer::class)
->middleware(['auth:sanctum', 'throttle:mcp']);Local server, run as an Artisan command for AI agents on your own machine:
Mcp::local('orders', OrdersServer::class);Follow these security rules for any web MCP server:
Always add authentication.
auth:sanctumrequires each client to send anAuthorization: Bearer <token>header. For OAuth 2.1, install Passport, callMcp::oauthRoutes(), and configure the appropriate authentication middleware. Our Sanctum vs. Passport guide can help you choose.Rate-limit the endpoint.
throttle:mcponly works after you define anmcprate limiter in a service provider, just as you would for an API.Scope tokens. Issue a dedicated token for each AI client so you can revoke one without affecting the others.
Start read-only. Ship lookup tools first. Add tools that change data only after you've observed how clients use them.
Step 7: Test Your Laravel MCP Server
With the MCP Inspector
Laravel wraps the official MCP Inspector, an interactive UI for calling your tools manually:
# Web server
php artisan mcp:inspector mcp/orders
# Local server
php artisan mcp:inspector ordersIt also prints client settings you can copy into your AI client. For a web server protected by Sanctum, add your bearer token to the Inspector's connection headers.
With Pest
Automated tests can call tools directly on the server class:
use App\Mcp\Servers\OrdersServer;
use App\Mcp\Tools\GetOrderStatusTool;
use App\Mcp\Tools\SearchOrdersTool;
use App\Models\Order;
use App\Models\User;
test('it finds a customer order', function () {
Order::factory()->create(['number' => '4821', 'customer_email' => '[email protected]']);
$response = OrdersServer::tool(SearchOrdersTool::class, [
'email' => '[email protected]',
]);
$response->assertOk()->assertSee('4821');
});
test('it rejects a missing email', function () {
$response = OrdersServer::tool(SearchOrdersTool::class, []);
$response->assertHasErrors();
});
test('a signed-in user can check an order', function () {
$user = User::factory()->create();
Order::factory()->for($user)->create(['number' => '4821']);
$response = OrdersServer::actingAs($user)->tool(GetOrderStatusTool::class, [
'number' => '4821',
]);
$response->assertOk();
});These tests run quickly and catch common mistakes, including broken queries, missing validation, and permission leaks. The factory setup assumes that an Order model belongs to a user and that an OrderPolicy allows owners to view their own orders. Adjust both to match your schema.
Ready to ship AI features faster? Start free with StackMivo and generate the models, policies, and tests your MCP tools depend on.
Step 8: Connect Your MCP Server to Claude Code
Most MCP clients connect in a similar way. Here's Claude Code as an example.
For the web server, provide the URL and your Sanctum token:
claude mcp add --transport http orders https://your-app.test/mcp/orders \
--header "Authorization: Bearer YOUR_TOKEN"For the local server, let Claude Code start it with Artisan:
claude mcp add orders -- php artisan mcp:start ordersRun that command from your Laravel project's root directory so php artisan resolves correctly. Then ask Claude something like, “What's the status of order 4821?” and watch it call GetOrderStatusTool.
Cursor, VS Code, and other clients use similar settings. The Inspector also prints a ready-made configuration you can adapt.
Bongani, a freelance Laravel developer in Cape Town, built a similar MCP server for a client's warehouse app in August. The client's operations manager can now ask Claude for low-stock reports in plain English. Bongani billed it as a two-day project, and it became his most-requested add-on for other clients.
Bongani is an illustrative composite based on common freelance scenarios.
Where Laravel MCP Fits in Your AI Stack
Laravel has three first-party AI tools that solve different problems:
Tool | What it does | Use it when |
|---|---|---|
Laravel MCP | Lets external AI clients call your app | You want Claude, Cursor, or ChatGPT to use your data and actions |
Laravel AI SDK | Lets your app call AI models | You're building AI features inside your product |
Laravel Boost | Provides an MCP server for your development environment | You want your coding agent to understand your codebase |
In fact, Laravel Boost is itself built on Laravel MCP. If you want AI inside your app instead, start with our Laravel AI SDK guide. For a broader overview, see our roundup of Laravel AI agent packages.
<!-- TODO BEFORE PUBLISHING: Insert a complete YouTube embed block (opening and closing wp:embed tags) showing a Laravel MCP server being built or tested with the MCP Inspector. If none is available, delete this comment. -->
Frequently Asked Questions
How do I build an MCP server in Laravel?
Install laravel/mcp, publish routes/ai.php, generate a server with php artisan make:mcp-server, add tools with make:mcp-tool, and register the server with Mcp::web() or Mcp::local(). Test it with php artisan mcp:inspector.
Is there an official Laravel MCP package?
Yes. laravel/mcp is maintained by the Laravel team and documented in the official Laravel documentation. It supports Laravel 11.45+, 12.41+, and 13.
Can I build an MCP server in plain PHP?
Yes. MCP is a protocol, so any language can implement it, and PHP libraries exist outside Laravel, too. In a Laravel app, laravel/mcp saves you from implementing the protocol layer yourself and provides validation, authentication, and testing helpers.
What's the difference between a web and a local MCP server?
A web server is an HTTP endpoint that remote AI clients call, so it needs authentication. A local server runs as an Artisan command on your machine, making it suitable for coding agents and personal tools.
How do I secure a Laravel MCP server?
Add middleware to the Mcp::web() route. Use auth:sanctum for bearer tokens, or configure Passport and Mcp::oauthRoutes() for OAuth 2.1. Then check permissions inside tools with $request->user() and rate-limit the route.
Does Laravel MCP work with ChatGPT and Cursor?
Any client that supports the Model Context Protocol can connect, provided its connection and authentication requirements are met. Hosted clients may require a remote server with OAuth 2.1, while local agents such as Claude Code and Cursor can also use a local server.
The Bottom Line
To build an MCP server in Laravel, you need one package, one server class, and a few tools. Install laravel/mcp, keep your first tools read-only, protect web servers with Sanctum or Passport, and test everything with the Inspector and Pest.
Start with the question your team asks most often, such as “Where is this order?”, and turn it into a tool. Once it works, you'll discover more questions your app can answer for AI clients.