This guide breaks down how to build a Laravel MCP Server from scratch, covering core concepts, package selection, and real implementation steps with tools, resources, and prompts. It explains how MCP fits into AI agent workflows and how it differs from REST and GraphQL for modern integrations. You will also learn how to secure, test, and scale your MCP server for production-ready Laravel applications.
Table of Contents
Introduction
The AI landscape is shifting. We are moving beyond simple chat prompts toward autonomous, AI-driven ecosystems that require deep integration with our backends. For Laravel developers, the challenge isn’t just building an API; it’s providing AI models with a structured, secure way to “reason” over application data.
Here, a Laravel MCP (Model Context Protocol) Server plays a crucial role in app development.
It enables you to use AI chat applications to process over 20 billion messages weekly. MCP is emerging as the gold-standard integration layer. It allows Laravel applications to expose tools, resources, and prompts to LLMs through a unified, standardized interface.
In this post, we’ll deep dive into how to build a Laravel MCP server that is both smart and scalable, unlocking a new level of intelligent automation for your projects.
What is MCP and Why Does It Matter for Laravel Backends?
Model Context Protocol (MCP) is a standardized way for AI systems like agents or copilots to discover, understand, and safely call backend tools and API without relying on brittle, hardcoded integrations.
Before writing a single line of implementation code, understanding what MCP actually solves, and why it solves it differently from REST or GraphQL, saves you from building the wrong abstraction entirely.
The Core Problem MCP Solves: Why AI Agents Guessing at Your REST API Schema Causes Real Tool Call Failures
The AI agent of Laravel MCP will fetch details from your Laravel app. Your REST API has a GET /orders/{id} endpoint. The agent operating without a schema context calls POST /orders with a JSON body instead. Your API returns 405.
The agent retries with GET /order/9942 and a wrong path format. Another error is if you try a third variation and the whole tool call chain collapses, and your AI feature silently stops working. This often happens when you point a probability at an interface designed for deterministic clients.
REST APIs communicate behavior through documentation, naming conventions, and developer intuition, none of which an AI agent reliably infers at runtime. GraphQL helps with typed queries, but still puts the burden on the agent to construct valid operations against a schema it must first parse and understand.
MCP changes the contract entirely. Instead of the agent guessing, the server tells it exactly what’s available, how to call it, and what to expect back. That’s the engineering shift that makes building a Laravel MCP Server worth understanding before writing a line of code.
The Three MCP Primitives Every Laravel Developer Needs Before Writing Any Code
Understanding these 3 primitives is non-negotiable before you set up your MCP server in Laravel.
Tools are callable functions your AI client invokes to perform actions, things that change state, or fetch live data on demand. Think GetOrderDetails, CreateInvoice, or RefundOrder. If it does something, it’s a tool.
Resources are readable data exposed by a URI, scoped, read-only context that an agent can pull without triggering an action. Something like orders://9942/history gives the agent an order’s full audit trail without letting it accidentally mutate anything.
Prompts are reusable conversation templates that standardize how the agent interacts with specific workflows. Without a Prompt, the same request phrased two different ways yields two completely different output formats. A summarize_order Prompt ensures the agent always returns data in the structure your downstream system expects.
Build Smarter AI Workflows with Laravel MCP Expertise from Bacancy!
Hire Laravel developers who know how to translate MCP primitives into real systems with secure tools, well-scope resources, and reusable prompts that keep your AI responses consistent.
Laravel MCP Packages: Choosing the Right Foundation Before You Install Anything
The official Laravel/mcp package is built and supported by the Laravel team. It’s documented for Laravel 12.x and Laravel 13.x, requires PHP 8.2+, and integrates natively with Artisan commands, Facades, service container resolution, and both Sanctum and Passport authentication.
Out of the box: Artisan scaffolding via make:mcp-server, a Mcp Facade for route registration, SSE and Stdio transport support, directory-based tool/resource/prompt discovery, and a dedicated routes/ai.php file that keeps your MCP routes cleanly separated from web and API routes.
One feature that consistently gets overlooked: the shouldRegister() method. It lets you conditionally gate tool visibility at the protocol level before the agent even receives the capability manifest. In a multi-tenant SaaS app, this means a RefundOrder tool simply doesn’t appear for free-tier users. It’s not just a runtime check. It’s a protocol-level gate on capability exposure.
Two community Laravel packages are worth knowing, depending on your constraints.
opgginc/laravel-mcp-server takes a route-first approach, registering MCP endpoints in existing route files via Route::mcp(). As of v2.0, Streamable HTTP is the only supported transport (legacy SSE transport was removed), it supports protocol version pinning via setProtocolVersion(), and auto-generates OpenAPI specs.
php-mcp/laravel leans into PHP 8 attributes for element discovery. Instead of manually registering tools in routes/ai.php, you annotate classes with #[McpTool], #[McpResource], or #[McpPrompt] and let the package discover them automatically. It also supports multiple session backends, file, database, cache, and Redis, which matters when you’re running a Laravel MCP Server at scale with many concurrent agent connections.
Laravel MCP Package Comparison Table
Feature
Laravel MCP
opgginc/laravel-mcp-server
php-mcp/laravel
Transport
SSE, Stdio
Streamable HTTP, SSE
Stdio, HTTP (Integrated + Dedicated)
Authentication
Native Sanctum / Passport
Custom Middleware
Config-based and Middleware
Tool Discovery
Manual / Directory-based
Route-based / OpenAPI Auto-gen
Manual / PHP 8 Attribute-based
Session Storage
Default
Default
File, Database, Cache, and Redis
Best For
Standard Laravel apps
Legacy API/ OpenAI migrations
Attribute-heavy codebases, high concurrency
For most developers building a Laravel MCP Server from scratch, the official package is the right call. Reach for community alternatives when you have specific constraints, OpenAPI migration, or Redis-backed session management that the official package doesn’t address natively.
Step-by-Step Laravel MCP Server Setup - From Composer Install to First Successful Tool Call
Step 1: Installing laravel/mcp and Publishing the Configuration File
This creates routes/ai.php, the dedicated route file where you register your MCP servers. Think of it the same way you think about routes/api.php, it’s the surface for AI-client-facing routes, separated from everything else.
Step 2: Generating Your First MCP Server Class with php artisan make:mcp-server
php artisan make:mcp-server OrderServer
This drops app/Mcp/Servers/OrderServer.php into your project, extending the base Laravel\Mcp\Server class:
namespace App\Mcp\Servers;
use Laravel\Mcp\Server;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Version;
use Laravel\Mcp\Server\Attributes\Instructions;
use App\Mcp\Tools\GetOrderDetails;
use App\Mcp\Resources\OrderHistoryResource;
use App\Mcp\Prompts\SummarizeOrderPrompt;
#[Name('Order Management Server')]
#[Version('1.0.0')]
#[Instructions('Use this server to retrieve order data, view status history, and generate summaries for SaaS order workflows.')]
class OrderServer extends Server
{
protected array $tools = [
GetOrderDetails::class,
];
protected array $resources = [
OrderHistoryResource::class,
];
protected array $prompts = [
SummarizeOrderPrompt::class,
];
}
The #[Instructions] attribute is more important than it looks. It’s the text your AI client reads before deciding which tool to call. Write it like a brief for a developer who’s never seen your codebase, specific, unambiguous, scoped to what this server actually does.
Register it in routes/ai.php:
use App\Mcp\Servers\OrderServer;
use Laravel\Mcp\Facades\Mcp;
// Remote cloud agents (ChatGPT, LangChain) — HTTP/SSE
Mcp::web('/mcp/orders', OrderServer::class)
->middleware(['auth:sanctum', 'throttle:mcp']);
// Local desktop clients (Claude Desktop, Cursor) — Stdio
Mcp::local('orders', OrderServer::class);
Step 3: Registering Tools in a Laravel MCP Server and Exposing Callable Functions for SaaS Order Management
A Tool is where the actual work happens. Here’s a GetOrderDetails tool built for a SaaS order management context:
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;
#[Description('Fetches full details for a specific order including line items, status, and customer info.')]
class GetOrderDetails extends Tool
{
public function schema(): JsonSchema
{
return JsonSchema::object([
'order_id' => JsonSchema::integer()
->description('The numeric ID of the order to retrieve'),
])->required(['order_id']);
}
public function handle(Request $request): Response
{
$orderId = $request->validated(['order_id']);
$order = Order::with(['lineItems', 'customer', 'statusHistory'])
->findOrFail($orderId);
return Response::text(json_encode([
'order_id' => $order->id,
'status' => $order->status,
'customer' => $order->customer->name,
'total' => $order->total_amount,
'line_items' => $order->lineItems->toArray(),
]));
}
}
A few things worth noting. Input validation runs in $request->validated(), not left to the AI client. The JSON schema in schema() is machine-readable by the agent, so it knows exactly what order_id expects before calling. Order::with() eager-loads relationships in a single query, keeping the tool response fast under concurrent agent traffic.
Step 4: Error Handling Inside MCP Tool Classes and When a Tool Call Fails
This is absent from nearly every MCP tutorial, and it’s critical for any Laravel MCP Server running in production.
When a tool call fails, the instinct is to return an empty response or silently catch exceptions. Don’t. AI agents interpret silence as ambiguous success. They’ll retry, loop, or continue the workflow with wrong assumptions. Fail loudly instead:
public function handle(Request $request): Response
{
$orderId = $request->validated(['order_id']);
$order = Order::find($orderId);
if (!$order) {
return Response::error(
"Order {$orderId} not found. Verify the order ID and retry."
);
}
if ($order->status === 'archived') {
return Response::error(
"Order {$orderId} is archived. Use the archive resource endpoint instead."
);
}
// continue with data retrieval
}
Response::error() passes the message back as a structured tool error. The agent receives it, understands the call failed, and can either surface it to the user or route it to a different tool. Clean failure handling is what separates a demo from a Laravel MCP Server you can actually trust in production.
Step 5: Registering Resources and Exposing Readable URI-Scoped Data with uriTemplate
Resources give the agent read-only access to data without the risk of triggering side effects:
namespace App\Mcp\Resources;
use App\Models\Order;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Resource;
class OrderHistoryResource extends Resource
{
public string $uriTemplate = 'orders://{order_id}/history';
public string $name = 'Order Status History';
public string $description = 'Returns the full status audit trail for a given order.';
public function read(Request $request): Response
{
$orderId = $request->uriParam('order_id');
$history = Order::findOrFail($orderId)
->statusHistory()
->orderBy('created_at')
->get(['status', 'changed_by', 'created_at']);
return Response::text($history->toJson());
}
}
The practical rule: if the agent needs to read context before deciding what action to take, that’s a Resource. If it needs to perform an action or mutate state, that’s a Tool. Mixing the two creates unpredictable agent behavior.
Step 6: Registering Prompts and Reusable Conversation Templates for Predictable AI Output
Without a Prompt, two agents asked to “summarize order 9942” returned data in two completely different formats. One returns a bullet list. Another returns prose. Another returns raw JSON with inconsistent key names. Prompts standardize this:
namespace App\Mcp\Prompts;
use Laravel\Mcp\Server\Prompt;
use Laravel\Mcp\Server\Attributes\Description;
#[Description('Generates a structured order summary formatted for support and billing workflows.')]
class SummarizeOrderPrompt extends Prompt
{
public string $name = 'summarize_order';
public function template(): string
{
return 'Summarize order {{order_id}} using the following fields: '
. 'Status, Customer Name, Total Amount, Number of Line Items, '
. 'Last Status Change. Return as a JSON object with these exact keys.';
}
}
The agent selects this Prompt, fills in the order_id variable, and always returns a response in the structure your frontend or billing system expects. Consistent output is one of the most common pain points when integrating Laravel AI into production workflows, and Prompts fixes it directly.
Step 7: Conditional Tool Registration with shouldRegister() for Multi-Tenant SaaS Apps
This is one of the most underused features in the Laravel MCP package, and it solves a real problem in Laravel SaaS products: not every user should see every tool in the agent’s capability manifest.
namespace App\Mcp\Tools;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Tool;
use Laravel\Mcp\Server\Attributes\Description;
#[Description('Issues a full or partial refund for an order. Available on Pro and Enterprise plans only.')]
class RefundOrder extends Tool
{
public function shouldRegister(Request $request): bool
{
return $request->user()?->subscription?->plan_name !== 'free';
}
public function handle(Request $request): Response
{
// refund logic
}
}
When a free-tier user’s agent connects, RefundOrder doesn’t appear in the tools/list manifest. It’s invisible at the protocol level – not just blocked at runtime. That’s a proper capability boundary, not just a permission check that can be reasoned around.
Step 8: Testing Your Laravel MCP Server with the MCP Inspector Before Connecting a Real Client
Before you connect Claude Desktop or Cursor, use the MCP Inspector. It connects to your MCP server in Laravel locally, lists every registered tool, resource, and prompt, lets you call each one manually, and shows the raw JSON-RPC request and response for every interaction.
How a Laravel MCP Server Fits Into an AI Agent Workflow: The Full Request Lifecycle
First, the Four-Step MCP Request Lifecycle
Step 1: Discovery – The AI client calls tools/list (and optionally resources/list, prompts/list). The server returns a full capability manifest with available tools, schemas, and descriptions.
Step 2: Planning – The agent reads the user request (e.g., “What’s the status of order 9942?”) and selects the appropriate tool based on the manifest, generating valid input using the provided schema.
Step 3: Execution – The agent calls tools/calls with {“order_id”: 9942}. The Laravel Tool validates input, fetches data, and returns a structured response.
Step 4: Synthesis – The agent converts the response into a natural language answer, like: “Order 9942 is in Shipped status, placed by Acme Corp for $1,240 with 3 line items.”
How an AI Agent Queries Live Order Data Through a Laravel MCP Tool
A support agent integrated with your product receives the message: “What happened to order 9942? The customer says it’s stuck.”
Here’s the full exchange:
The agent issues tools/lists and identifies GetOrderDetails as the right tool.
It calls the Laravel MCP Server with {“order_id”: 9942}.
The server validates the input, runs Order::with([‘statusHistory’, ‘lineItems’, ‘customer’])->findOrFail(9942), and returns structured JSON.
The agent also fetches the orders://9942/history resource for the full status audit trail.
It synthesizes: “Order 9942 has been stuck in Processing for 4 days. The last status change was from Payment Confirmed on the mentioned date. No shipment event has been logged, which suggests a fulfillment hold.”
Real-World Use Cases Where Laravel MCP Servers Add Immediate Value
A Laravel MCP Server that brings immediate value by letting AI agents securely interact with Laravel backends through structured tools, resources, and prompts.
eCommerce: When a customer asks an AI assistant, “Where is my order?” the agent will put out live data from your backend and provide the outcome. A Laravel MCP Server gives the agent a direct and structured line to your code history, product catalogue, and inventory to offer you accurate answers in seconds.
SaaS Dashboard: MCP server in Laravel connects to your dashboard, and the AI agent pulls live figures directly to the database and answers the question in simple language. The language is always current because the agent is reading in real-time and not from any previously generated reports.
Internal Tools: HR teams spend hours answering the same questions, such as remaining leave balance, payroll dates, and reimbursement status. An internal AI assistant connected to a Laravel MCP Server handles these queries instantly, fetching the right data for the right person.
Healthcare: A clinical staff needs fast access to patient records, but that access has to be controlled, auditable, and compliant. MCP Server in Laravel exposes only the records a practitioner is authorized to view, logs every access automatically, and keeps a complete trail of who accessed what and when. The AI assists with the healthcare development workflow. The compliance requirements are met by design and not by manual process.
Laravel MCP Server vs REST API vs GraphQL: Why LLMs Need a Different Protocol
Dimension
Laravel MCP Server
REST API
GraphQL
Designed for
AI agents (probabilistic)
Human developers
Human developers (typed)
Capability discovery
Built-in tools/list manifest
Docs / OpenAPI spec
Schema introspection
Input validation
Schema-enforced + pre-call
Server-side, post-request
Schema-enforced
Failure semantics
Response::error() with agent-readable messages
HTTP status codes
errors array in response
Session state
Stateful SSE sessions
Stateless (typically)
Stateless
When to use
AI agent integrations
Mobile apps and public APIs
Internal tooling and flexible queries
How to Secure Your Laravel MCP Server?
Follow this code to protect your project:
In routes/ai.php:
php
Mcp::web('/mcp/orders', OrderServer::class)
->middleware(['auth: sanctum', 'throttle:mcp']);
Issue each AI agent integration its own Sanctum token with explicit abilities. When a token is compromised, assume one eventually will be, and you revoke it cleanly without touching anything else.
Authentication tells you who is connecting. Authorization tells you what they are allowed to do. Don’t rely on the AI agent to self-limit, enforce authorization inside every Tool that touches sensitive data:
public function handle(Request $request): Response
{
$orderId = $request->validated(['order_id']);
$order = Order::findOrFail($orderId);
if ($request->user()->cannot('view', $order)) {
return Response::error('You do not have permission to view this order.');
}
// proceed with retrieval
}
The AI agent cannot override a cannot() check. That’s the point. You’re applying the same authorization layer you’d apply to any sensitive endpoint, because a production Laravel MCP Server endpoint is a sensitive endpoint.
Rate Limiting MCP Routes to Prevent Runaway Agent Loop Attacks
Runaway agent loops are a real production failure mode. An AI agent stuck in an error-retry cycle can issue hundreds of tool calls per minute. Without rate limiting, that kind of loop can saturate your database in under 60 seconds.
In App\Providers\AppServiceProvider:
php
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Facades\RateLimiter;
RateLimiter::for('mcp', function ($request) {
return Limit::perMinute(60)
->by($request->user()?->id ?: $request->ip());
});
60 requests per minute is generous for legitimate workflows and restrictive enough to stop a loop before it does real damage. Tune downward for tools that trigger expensive queries or external API calls.
Audit Logging MCP Tool Calls for Compliance and Debugging
For any Laravel MCP Server that handles sensitive business data, including orders, invoices, and customer records, audit logging is not optional. It’s what you pull when a compliance auditor asks “who accessed what and when,” and it’s what saves you when an agent produces unexpected output, and you need to trace exactly which tool call triggered it.
Create a dedicated log channel in config/logging.php:
A separate mcp log channel keeps AI traffic isolated from your application logs, makes SOC 2 audit trails straightforward to produce, and gives you a clean signal when something behaves unexpectedly in production.
Conclusion
The Laravel MCP Server changes how developers approach integrations and makes it easier to expose application logic in a structured and reusable way. It reduces the need for repetitive API development and prepares your Laravel applications for AI-driven and interconnected ecosystems.
As systems become more integration-heavy, adopting MCP helps you create cleaner and more scalable architectures that are ready for the future. With our expertise in Laravel development services, we help businesses adopt MCP-driven architectures that are secure, scalable, and aligned with real-world use cases.
Whether you aim to modernize your existing Laravel application or build a future-ready platform from scratch, our team ensures you stay ahead with solutions designed for performance, flexibility, and long-term growth.
Frequently Asked Questions (FAQs)
Yes, and you should. A Laravel MCP Server tool class is just a PHP class with a handle() method. You can inject any service class through the constructor; Laravel’s service container resolves dependencies automatically. If you have an OrderService that already handles fetching and formatting order data, inject it directly into your Tool and call its methods.
It is, if you’ve applied authentication, authorization, and rate limiting correctly. A bare SSE endpoint with no auth:sanctum middleware, no Policy checks inside Tools, and no rate limiting is not production-secure, but that’s a configuration gap, not a protocol problem. A properly secured MCP server in Laravel is no more exposed than a properly secured REST API handling the same data.
Yes. MCP is a protocol standard, not a client-specific integration. The same Laravel MCP Server can serve Claude Desktop over Stdio locally and ChatGPT Custom GPTs or LangChain agents over SSE in production at the same time. Register both transport endpoints in routes/ai.php using Mcp::local() for Stdio and Mcp::web() for HTTP/SSE.
The opgginc/laravel-mcp-server package exposes a setProtocolVersion() method directly on the route registration: ->setProtocolVersion(ProtocolVersion::V2025_06_18). Use this when connecting to clients that haven’t updated to the 2025-11-25 protocol version. The official laravel/mcp package handles protocol version negotiation automatically during the initial handshake for standard clients like Claude and Cursor.
You can, but SSE transport requires configuration attention. SSE connections are long-lived, and they hold an open HTTP connection for the session duration. PHP-FPM ties up a worker process per connection, which limits concurrency under load. Laravel Octane with Swoole or RoadRunner handles long-lived connections more efficiently because it doesn’t block a worker per connection.
Nikunj Padhiyar
Director of Engineering at Bacancy
Senior Software Engineer building robust, high-performing PHP web applications.