Backend Development

Building an AI MCP Server with Laravel: A Step-by-Step Tutorial

Building an AI MCP Server with Laravel

Modern AI systems increasingly rely on structured, tool-aware backends to interact with data, services, and external APIs. One emerging pattern is the Model Context Protocol (MCP) server — a backend that exposes tools, resources, and context in a standardized way for AI models.

 

 

In this tutorial, you’ll learn how to build a simple AI MCP server using the Laravel framework. By the end, you’ll have a working backend that can:

  • Expose tools/functions to AI agents
  • Serve structured context data
  • Handle requests in a clean, scalable way

 


What is an MCP Server?

An MCP (Model Context Protocol) server is a backend that: Defines tools (functions AI can call) Provides structured context (data/resources) Handles execution requests Think of it as a bridge between AI and your application logic.

 

Using MCP in Laravel

We will use the laravel/mcp package. This package facilitates building MCP servers, and contains the needed features like creating tools, creating, prompts, resources, etc.

Let’s create a brand new laravel project:

laravel new mcp_server_laravel_demo

Next install the laravel/mcp:

composer require laravel/mcp

After successful install, publish the ai-routes file:

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

This will create the web/ai.php file, where you will register the MCP servers.

 

Creating new MCP Server

Create a new MCP server using the make:mcp-server command:

php artisan make:mcp-server DashboardServer

Once created a new directory app/Mcp will created inside of the app/ directory. This directory will serve as the container for MCP related files. MCP server classes created inside of app/Mcp/Servers directory.

Open the generated MCP server and set the Name and Instructions like so:

#[Name('Dashboard Server')]
#[Version('0.0.1')]
#[Instructions('This server provides website dashboard information and how to automate dashboard tasks.')]
class DashboardServer extends Server
{
    protected array $tools = [
        //
    ];

    protected array $resources = [
        //
    ];

    protected array $prompts = [
        //
    ];
}

It’s important to set a proper Name and set the Instructions about what this server do, this tells AI Agents about the general context of this server.

Setting up the Name and Instructions done by using #[Name] and #[Instructions] attributes. If you you don’t want to use attributes syntax you can declared them using class properties like so:

class DashboardServer extends Server
{
    protected string $name = 'Dashboard Server';

    protected string $version = '0.0.1';

    protected string $instructions = 'This server provides website dashboard information and how to automate dashboard tasks.';

    ....

}

 

Registering the MCP Server

Register the new created sever in web/ai.php:

<?php

use Laravel\Mcp\Facades\Mcp;

Mcp::web('/mcp/dashboard', \App\Mcp\Servers\DashboardServer::class);

This server will be used by the Ai clients using the /mcp/dashboard route. This kind of route is called a MCP web server route and is suitable for remote AI clients and will be accessed via HTTP POST request. In that case this server will be used from any web application which can use the MCP server.

However there is another type of MCP server which is the local servers using the Mcp::local() method:

Mcp::local('/mcp/dashboard', \App\Mcp\Servers\DashboardServer::class);

Local servers run as Artisan commands which is suitable for building local AI assistants.

 

Inpecting the MCP Server

You can inspect the MCP server using the MCP inspector. The MCP inspector used to debug and test the MCP Server:

php artisan mcp:inspector mcp/dashboard

I passed the MCP server route to the mcp:inspector command. Once run this command it will give you instructions of how to connect to your server by Ai clients, and will launch a web-passed interface to connect to MCP server.

 php artisan mcp:inspector mcp/dashboard

   INFO  Starting the MCP Inspector for server [mcp/dashboard].  

Transport Type => Streamable HTTP
URL => http://localhost:8000/mcp/dashboard
Secure => Your project must be accessible on HTTP for this to work due to how node manages SSL trust

The example

Let’s apply a real world example with the MCP server. Suppose that you need a way to automate tasks in the admin dashboard of your website.

Let’s consider we have these modules in the admin:

  • Products
  • Posts
  • Users

Let’s create the migrations for these entities:

php artisan make:model Product -m
php artisan make:model Post -m

XXX_create_products_table.php

Schema::create('products', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->text('description')->nullable();
            $table->decimal('price', 10, 2);
            $table->timestamps();
        });

XXX_create_posts_table.php

Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->text('content')->nullable();
            $table->timestamps();
        });

Migrate the tables:

php artisan migrate

Update models fillables:

app/Models/Product.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Model;

#[Fillable(['name', 'price', 'description'])]
class Product extends Model
{
    //
}

app/Models/Post.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Model;

#[Fillable(['title', 'content'])]
class Post extends Model
{
    //
}

Creating MCP tool

Create an MCP tool that inserts a new product into DB:

php artisan make:mcp-tool CreateProductTool

The make:mcp-tool command creates a new tool class in the app/Mcp/Tools/ directory.

Tools Represents the operations to implement in the MCP server by AI Clients. Open the Tools/CreateProductTool.php and update it like so:

<?php

namespace App\Mcp\Tools;

use App\Models\Product;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;
use Laravel\Mcp\Server\Tool;

#[Name('create-product')]
#[Title('Create Product')]
#[Description('Create a new product into DB using the given data.')]
class CreateProductTool extends Tool
{
    /**
     * Handle the tool request.
     */
    public function handle(Request $request): Response
    {
        $validated = $request->validate([
           'name' => 'required|string',
           'price' => 'required|numeric',
           'description' => 'nullable|string'
        ]);

        $product = Product::create($validated);

        return Response::text("Product created with id: {$product->id}");
    }

    /**
     * Get the tool's input schema.
     *
     * @return array<string, JsonSchema>
     */
    public function schema(JsonSchema $schema): array
    {
        return [
            'name' => $schema->string()
                ->description('The name of the product.')
                ->required(),

            'description' => $schema->string()
                    ->description('The description of the product.'),

            'price' => $schema->number()
                    ->description('The price of the product.')
                    ->required()
        ];
    }
}

The tool class structure as below, a skeleton tool class contains the handle() and schema() methods. You should provide meaningful descriptions and titles for the tools so that Ai Agents can infer the context of the title.

I added a name and title for the tool using Laravel\Mcp\Server\Attributes\Name and Laravel\Mcp\Server\Attributes\Title attributes. And also added a description with the #[Description] attribute. 

The schema() method, this is where you define input schemas from AI clients. For the above tool, we need the product name,  description and the price:

return [
            'name' => $schema->string()
                ->description('The name of the product.')
                ->required(),

            'description' => $schema->string()
                    ->description('The description of the product.'),

            'price' => $schema->number()
                    ->description('The price of the product.')
                    ->required()
        ];

The handle() method do the actual processing of the tool and must return a Laravel/Mcp/Response instance. An optional validation can be done for the input data arguments using laravel validators as in the above code and then creating the product and finally return the response.  

Registering the tool

It’s important to register the tool class in the MCP server class. Open the DashboardServer.php class and register the tool in the $tools property:

....

class DashboardServer extends Server
{
    protected array $tools = [
        App\Mcp\Tools\CreateProductTool::class
    ];

   ...
}

Testing the tool

There are many ways to test the MCP tools:

  • Using the MCP inspector as described above
  • Using Claude cli
  • Invoking the mcp server from a AI clients like OpenAi or Claude Api’s

How to use the MCP inspector:

  • First check the .env APP_URL points to the web app actual url. For local development this can be http://localhost:8000.
  • Launch the project with php artisan serve.
  • In another terminal run php artisan mcp:inspector mcp/dashboard. Once this command runs it will open a web interface and you can view the tools, resources and open any tool as in the figure:

Laravel MCP Inspector Demo

 

How to use the Claude cli to register mcp servers:

Using the command:

claude mcp add --transport http <server_name> https://<url>

Modify this command according to our example server:

claude mcp add --transport http laravel_app http://localhost:8000/mcp/dashboard

Now if you check the claude mcp servers:

claude mcp list

It should say:

Checking MCP server health...

laravel_app: http://localhost:8000/mcp/dashboard (HTTP) - ✓ Connected

After that open a claude session in terminal in the project root and type mcp, it should detect the mcp servers and tools and list them. You can select any tool and follow the steps like shown in this figure:

Claude-cli-mcp-demo

Tool Responses

Right now the tool response is a text:

Response::text("Product created with id: {$product->id}");

However there are other response methods you can use like

  • Response::image() to return image content
  • Response::audio() to return audio content
  • Response::structured() to return structured data
  • Response::error() to return error

Let’s replace the Response::text() in the example with Response::structured():

return Response::structured([
            "id" => $product->id,
            "name" => $product->name,
            "price" => $product->price,
            "description" => $product->description
        ]);

MCP-Inspector-structured-data

Update & Deleting Product Tool

Create another tool for updating and deleting product:

php artisan make:mcp-tool UpdateProductTool
php artisan make:mcp-tool DeleteProductTool

app/Mcp/Tools/UpdateProductTool.php

#[Name('update-product')]
#[Title('Update Product')]
#[Description('Update product by id and given data.')]
class UpdateProductTool extends Tool
{
    /**
     * Handle the tool request.
     */
    public function handle(Request $request): Response
    {
        $validated = $request->validate([
            'id' => 'required|numeric',
            'name' => 'required|string',
            'price' => 'required|numeric',
            'description' => 'nullable|string'
        ]);

        $product = Product::find($validated['id']);

        if(!$product) {
            return Response::error("Product not found.");
        }

        $product->update($validated);

        return Response::text("Product #{$product->id} updated successfully.");
    }

    /**
     * Get the tool's input schema.
     *
     * @return array<string, JsonSchema>
     */
    public function schema(JsonSchema $schema): array
    {
        return [
            'id' => $schema->number()
                    ->description('The id of the product.')
                    ->required(),
            'name' => $schema->string()
                ->description('The name of the product.')
                ->required(),

            'description' => $schema->string()
                ->description('The description of the product.'),

            'price' => $schema->number()
                ->description('The price of the product.')
                ->required()
        ];
    }
}

app/Mcp/Tools/DeleteProductTool.php

#[Name('delete-product')]
#[Title('Delete Product')]
#[Description('Delete product by id.')]
class DeleteProductTool extends Tool
{
    /**
     * Handle the tool request.
     */
    public function handle(Request $request): Response
    {
        $product = Product::find($request->get("id"));

        if(!$product) {
            return Response::error("Product not found.");
        }

        $product->delete();

        return Response::text("Product #{$product->id} deleted!.");
    }

    /**
     * Get the tool's input schema.
     *
     * @return array<string, JsonSchema>
     */
    public function schema(JsonSchema $schema): array
    {
        return [
            'id' => $schema->number()
                ->description('The id of the product to delete (required).')
                ->required()
        ];
    }
}

As shown in the DeleteProductTool code it’s similar to the CreateProductTool, except that it the handle() method finds a product by id, and if it’s not found it return an error response using Response::error(). Also the schemas() method add the “id” to the input schema.

Register the tool inside the DashboardServer.php:

<?php

use App\Mcp\Tools\CreateProductTool;
use App\Mcp\Tools\UpdateProductTool;
use App\Mcp\Tools\DeleteProductTool;

....

class DashboardServer extends Server
{
    protected array $tools = [
        CreateProductTool::class,
        UpdateProductTool::class,
        DeleteProductTool::class
    ];
  

?>

Now these tools will be discover-able and shown in the MCP inspector web interface.

Using the MCP Server With In Other Web Applications

You can use and expose the MCP Server we just created in other web applications that uses OpenAi or Claude Api’s. For example you can use it in OpenAi Responses Api like shown:

import OpenAI from "openai";

const client = new OpenAI();

const response = await client.responses.create({
    model: "gpt-5.4", // 2026 flagship model
    tools: [
        {
            type: "mcp",
            server_url: "https://xyz.ngrok-free.app/mcp/laravel",
            server_label: "dashboard_server",
            server_description: "Access to the Laravel database and product creation tools."
        }
    ],
    input: [
        {
            role: "user",
            content: "Create a product named 'OpenAI Mug' for $15."
        }
    ]
});

console.log(response.output_text);

 

Source Code

0 0 votes
Article Rating

What's your reaction?

Excited
0
Happy
0
Not Sure
0
Confused
0

You may also like

Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments