organiseyou/mcp-server

Drop-in MCP (Model Context Protocol) server for Laravel applications

Maintainers

Package info

github.com/OrganiseYou/mcp-server

pkg:composer/organiseyou/mcp-server

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-04-24 11:20 UTC

This package is auto-updated.

Last update: 2026-04-27 09:26:38 UTC


README

A Model Context Protocol (MCP) server for Laravel built around runtime tool registration. Tools are registered programmatically at request time rather than declared upfront as static classes, making this package the right choice when your tool surface is dynamic — multi-tenant platforms, schema-driven APIs, feature-flagged environments, or anything else where you don't know your tools at boot time.

If your tools are static and known at boot time, laravel/mcp (the official first-party package) is likely the better fit.

Requirements

  • PHP 8.2+
  • Laravel 11+

Installation

composer require organiseyou/mcp-server

The service provider and Mcp facade are registered automatically via Laravel's package discovery.

Publish the config file:

php artisan vendor:publish --tag=mcp-config

Configuration

config/mcp.php:

return [
    'server' => [
        'name'    => env('APP_NAME', 'Laravel'),
        'version' => '1.0.0',
    ],

    'transport' => [
        'http'            => true,   // expose POST /mcp
        'http_path'       => '/mcp',
        'http_middleware' => [],     // e.g. ['auth:sanctum']
    ],

    'tools' => [
        // App\Mcp\Tools\StaticTool::class,
    ],
];

Registering Tools

Inline via the Mcp facade (dynamic)

Register tools anywhere that runs before the request is handled — a service provider, middleware, or a tenant boot hook:

use OrganiseYou\McpServer\Facades\Mcp;

// Single tool
Mcp::tool('add_numbers')
    ->description('Add two numbers together and return the sum.')
    ->inputSchema([
        'type'       => 'object',
        'properties' => [
            'a' => ['type' => 'number', 'description' => 'First operand.'],
            'b' => ['type' => 'number', 'description' => 'Second operand.'],
        ],
        'required' => ['a', 'b'],
    ])
    ->handle(fn (array $input) => $input['a'] + $input['b']);

A multi-tenant example where tools are generated from tenant configuration:

// In a tenant boot hook or middleware
foreach ($tenant->enabledDataSources() as $source) {
    Mcp::tool("query_{$source->slug}")
        ->description("Query the {$source->name} dataset. " . $source->description)
        ->inputSchema($source->buildInputSchema())
        ->handle(fn (array $input) => $source->query($input));
}

Class-based tools (static)

For tools that are the same across all tenants or contexts, implement OrganiseYou\McpServer\Contracts\McpTool and register the class in config/mcp.php:

<?php

namespace App\Mcp\Tools;

use App\Models\Order;
use OrganiseYou\McpServer\Contracts\McpTool;

class GetOrderTool implements McpTool
{
    public function __construct(private OrderRepository $orders) {}

    public function name(): string
    {
        return 'get_order';
    }

    public function description(): string
    {
        return 'Retrieve an order by its ID, including line items and shipping status.';
    }

    public function inputSchema(): array
    {
        return [
            'type'       => 'object',
            'properties' => [
                'order_id' => [
                    'type'        => 'integer',
                    'description' => 'The ID of the order to retrieve.',
                ],
            ],
            'required' => ['order_id'],
        ];
    }

    public function __invoke(array $input): mixed
    {
        $order = Order::with('items')->findOrFail($input['order_id']);

        return [
            'id'     => $order->id,
            'status' => $order->status,
            'total'  => $order->total,
            'items'  => $order->items->map(fn ($i) => [
                'sku'      => $i->sku,
                'quantity' => $i->quantity,
            ])->all(),
        ];
    }
}
// config/mcp.php
'tools' => [
    App\Mcp\Tools\GetOrderTool::class,
],

Tools are resolved from the Laravel container, so constructor dependencies are injected automatically.

Transports

HTTP (default)

When mcp.transport.http is true, the package registers a POST /mcp route. Any MCP client that supports HTTP can point to https://your-app.test/mcp.

To secure the endpoint, add middleware in config/mcp.php:

'http_middleware' => ['auth:sanctum'],

Stdio (Claude Desktop / local clients)

Run the artisan command to start an stdio MCP server:

php artisan mcp:serve

Add it to your claude_desktop_config.json:

{
    "mcpServers": {
        "my-laravel-app": {
            "command": "php",
            "args": ["/path/to/your/app/artisan", "mcp:serve"]
        }
    }
}

Protocol

The server speaks JSON-RPC 2.0 and implements the MCP 2024-11-05 protocol version. Supported methods:

Method Description
initialize Handshake, returns server info and capabilities
ping Health check
tools/list List all registered tools
tools/call Invoke a tool by name

Example HTTP request

curl -s -X POST https://your-app.test/mcp \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
{
    "jsonrpc": "2.0",
    "id": 1,
    "result": {
        "tools": [
            {
                "name": "get_order",
                "description": "Retrieve an order by its ID, including line items and shipping status.",
                "inputSchema": {
                    "type": "object",
                    "properties": {
                        "order_id": { "type": "integer", "description": "The ID of the order to retrieve." }
                    },
                    "required": ["order_id"]
                }
            }
        ]
    }
}

Calling a tool:

curl -s -X POST https://your-app.test/mcp \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_order","arguments":{"order_id":42}}}'
{
    "jsonrpc": "2.0",
    "id": 2,
    "result": {
        "content": [
            {
                "type": "text",
                "text": "{\"id\":42,\"status\":\"shipped\",\"total\":99.95,\"items\":[{\"sku\":\"WIDGET-1\",\"quantity\":2}]}"
            }
        ],
        "isError": false
    }
}

Batch requests are also supported — send an array of JSON-RPC objects and receive an array of responses.

Testing

composer install
./vendor/bin/phpunit

License

MIT