Give Your Laravel App an MCP Server: Let AI Assistants Use Your Product Safely
How to expose a Laravel product to Claude, ChatGPT and Cursor with the official laravel/mcp package: servers, tools, OAuth, testing, and the security rules that keep customer data safe.
Usama Munawar5 min read

The short answer
The Model Context Protocol (MCP) is how AI assistants such as Claude, ChatGPT, Cursor and VS Code call tools in other systems. If your customers live in those assistants, an MCP server lets them ask "which of my invoices are overdue?" or "open a support ticket for this" and get an answer from your product, not from the model's imagination.
With Laravel's official laravel/mcp package, stable since version 1.0 in September 2026, an MCP server is a few classes and a route file, protected by the same authentication and policies as the rest of your app. My advice: start with two or three read-only tools, behind OAuth, scoped to the signed-in user, and add write actions only once you can see how people use them.
MCP, Boost and the AI SDK are different things
Laravel now ships several AI packages, and they get mixed up. Laravel's own one-line summary is the clearest: Boost helps AI agents write better Laravel code for you, and MCP helps external AI tools interact with your app.
- Laravel Boost is a development tool. It runs a local MCP server that gives your coding agent your schema, logs and the Laravel docs. It never belongs in production.
- Laravel MCP (
laravel/mcp) is what this post is about: a production MCP server that exposes your product to your users' assistants. - The Laravel AI SDK, introduced with Laravel 13, is for calling models from inside your app.
Setting it up
composer require laravel/mcp
php artisan vendor:publish --tag=ai-routes # creates routes/ai.php
php artisan make:mcp-server BillingServer
php artisan make:mcp-tool ListInvoicesTool
A server is a class that lists its tools, with its name and instructions as attributes:
namespace App\Mcp\Servers;
use App\Mcp\Tools\ListInvoicesTool;
use Laravel\Mcp\Server;
use Laravel\Mcp\Server\Attributes\Instructions;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Version;
#[Name('Acme Billing')]
#[Version('1.0.0')]
#[Instructions('Answers questions about the signed-in customer\'s invoices and payments.')]
class BillingServer extends Server
{
protected array $tools = [
ListInvoicesTool::class,
];
}
A tool describes its input with a JSON schema and does its work in handle(). The description matters more than it looks: it is what the model reads to decide when to call the tool.
namespace App\Mcp\Tools;
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('Lists the signed-in customer\'s 20 most recent invoices. Optionally filter by status: paid, unpaid or overdue.')]
#[IsReadOnly]
class ListInvoicesTool extends Tool
{
public function handle(Request $request): Response
{
$validated = $request->validate([
'status' => 'nullable|in:paid,unpaid,overdue',
]);
$invoices = $request->user()->invoices()
->when($validated['status'] ?? null, fn ($query, $status) => $query->where('status', $status))
->latest()
->limit(20)
->get(['number', 'status', 'total', 'due_date']);
return Response::text($invoices->toJson());
}
public function schema(JsonSchema $schema): array
{
return [
'status' => $schema->string()
->description('Optional filter: paid, unpaid or overdue.'),
];
}
}
Then register the server in routes/ai.php:
use App\Mcp\Servers\BillingServer;
use Laravel\Mcp\Facades\Mcp;
Mcp::oauthRoutes();
Mcp::web('/mcp/billing', BillingServer::class)
->middleware(['auth:api', 'throttle:60,1']);
Authentication: OAuth for customers, Sanctum for your team
The MCP specification uses OAuth 2.1 for remote servers, and it is what most clients support, which is why the Laravel docs recommend Passport: Mcp::oauthRoutes() adds the discovery and authorization routes, and the customer approves access on a screen you control. Two limits to know: the package uses a single mcp:use scope (custom scopes are not supported yet), and Claude connects from Anthropic's cloud, so the server must be reachable from the public internet.
If you already use Sanctum, ->middleware('auth:sanctum') with a bearer token works for clients that let you set headers, such as Claude Code or your own scripts. It is a good fit for internal tools.
The security rules that actually matter
An MCP server is a new API, used by a client that follows instructions from text it reads. Treat it with the same care as your public API, and then some.
- Every tool runs as the user. Scope every query to
$request->user(), and use your existing policies ($request->user()->can(...)) before returning or changing anything. - Annotations are hints, not security.
#[IsReadOnly]and#[IsDestructive]tell the client how careful to be; the specification says clients should treat them as untrusted. Your authorization code is the only real guard. - Hide what a user cannot use.
shouldRegister()lets a tool disappear for users who lack the plan or role, so the model is never tempted to try it. - Return the minimum. Whatever a tool returns goes into the model's context. Select columns explicitly, as in the example, and leave out internal IDs and personal data the question does not need.
- Be careful with write tools. Make them idempotent, require clear arguments, and expect the client to ask the user for confirmation (ChatGPT does this for write actions). Text from your own database, such as a support ticket, can contain instructions aimed at the model; never let one tool's output silently trigger another tool's write.
- Rate-limit and log. Record who called which tool with which arguments. When a customer asks "why did the assistant say that?", the log is your answer.
Testing before anyone connects
The package has an inspector for poking at a server by hand, and test helpers for your test suite:
php artisan mcp:inspector mcp/billing
$response = BillingServer::actingAs($customer)
->tool(ListInvoicesTool::class, ['status' => 'unpaid']);
$response->assertOk()->assertSee('INV-1042');
Write one test per tool that proves a user cannot see another user's data. It is the test that matters most and the one most often missing.
Connecting a client
- Claude: add it as a custom connector with the server's URL.
- Claude Code:
claude mcp add --transport http billing https://example.com/mcp/billing. - ChatGPT: developer mode supports remote MCP servers; which plans get full write access is still changing, so check OpenAI's current help pages.
- Cursor and VS Code: both connect to remote HTTP servers from their MCP settings.
Version notes
laravel/mcp 1.0 speaks the July 2026 revision of the MCP specification, which is stateless (no session handshake), and still answers older clients. Laravel 13, released in March 2026, requires PHP 8.3 or newer. If you are following a tutorial that uses protected string $description properties or MCP sessions, it was written for the 0.x releases and is out of date.
Planning an MCP server or AI features for a Laravel product? See AI integration and Laravel development, or email devusamaworks@gmail.com or WhatsApp +92 303 8004684 for a free 30-minute call.
A question about this article?Ask me on WhatsApp


