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 with Mcp::web() for HTTP clients or Mcp::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:sanctum for bearer tokens, or use Passport with Mcp::oauthRoutes() for OAuth 2.1.

  • Test it in two ways. Use php artisan mcp:inspector for manual checks and Pest tests with OrdersServer::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:

  1. A SearchOrdersTool that finds orders by customer email or status.

  2. A GetOrderStatusTool that returns an order's status as structured data.

  3. A RefundPolicyResource that 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/mcp

Then publish the AI routes file:

php artisan vendor:publish --tag=ai-routes

This 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 OrdersServer

This 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 SearchOrdersTool

Each 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 viewAny check, 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 an OrderPolicy with viewAny and view methods. Create one with php 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 is null. 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:sanctum requires each client to send an Authorization: Bearer <token> header. For OAuth 2.1, install Passport, call Mcp::oauthRoutes(), and configure the appropriate authentication middleware. Our Sanctum vs. Passport guide can help you choose.

  • Rate-limit the endpoint. throttle:mcp only works after you define an mcp rate 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 orders

It 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 orders

Run 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.

Build your next Laravel app with StackMivo, free to start →