laravel-mcp-server
A Laravel package for implementing secure Model Context Protocol servers using Streamable HTTP and SSE transport, providing real-time communication and a sca...
Documentation
Laravel MCP Server by OP.GG
Build a route-first MCP server in Laravel and Lumen
|
|
|
|
|
|
|
Breaking Changes 2.0.0
- Endpoint setup moved from config-driven registration to route-driven registration.
- Streamable HTTP is the only supported transport.
- Server metadata mutators are consolidated into `setServerInfo(...)`.
- Legacy tool transport methods were removed from runtime (`messageType()`, `ProcessMessageType::SSE`).
Full migration guide: docs/migrations/v2.0.0-migration.md
Overview
Laravel MCP Server provides route-based MCP endpoint registration for Laravel and Lumen.
Key points:
- Streamable HTTP transport
- Route-first configuration (`Route::mcp(...)` / `McpRoute::register(...)`)
- Tool, resource, resource template, and prompt registration per endpoint
- Route cache compatible endpoint metadata
Requirements
- PHP >= 8.2
- Laravel (Illuminate) >= 9.x
- Lumen >= 9.x (optional)
Quick Start
1) Install
composer require opgginc/laravel-mcp-server2) Register an endpoint (Laravel)
use Illuminate\Support\Facades\Route;
use OPGG\LaravelMcpServer\Enums\ProtocolVersion;
use OPGG\LaravelMcpServer\Services\ToolService\Examples\HelloWorldTool;
use OPGG\LaravelMcpServer\Services\ToolService\Examples\VersionCheckTool;
Route::mcp('/mcp')
->setServerInfo(
name: 'OP.GG MCP Server',
version: '2.0.0',
)
->setConfig(
compactEnumExampleCount: 3,
)
->setProtocolVersion(ProtocolVersion::V2025_11_25)
->enabledApi()
->tools([
HelloWorldTool::class,
VersionCheckTool::class,
]);If you need compatibility with clients that do not support `2025-11-25`, set:
->setProtocolVersion(ProtocolVersion::V2025_06_18)3) Verify
php artisan route:list | grep mcp
php artisan mcp:test-tool --list --endpoint=/mcpQuick JSON-RPC check:
curl -X POST http://localhost:8000/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'Dynamic Tool Filtering by Query String
If one endpoint needs to expose different tool sets based on the incoming URL, attach a dynamic tools resolver to the route.
The resolver owns both the declared tool catalog for the endpoint and the per-request visible subset.
use OPGG\LaravelMcpServer\Data\ToolResolutionContext;
use OPGG\LaravelMcpServer\Routing\McpEndpointDefinition;
use OPGG\LaravelMcpServer\Services\ToolService\DynamicToolResolverInterface;
final class LolPhaseToolResolver implements DynamicToolResolverInterface
{
public function declaredTools(McpEndpointDefinition $endpoint): array
{
return [
\App\MCP\Tools\LolSearchChampionMetaTool::class,
\App\MCP\Tools\LolGetChampionAnalysisTool::class,
\App\MCP\Tools\LolGetLiveItemRecommendationsTool::class,
];
}
public function resolve(
McpEndpointDefinition $endpoint,
ToolResolutionContext $context,
): array {
return match ($context->queryParameters['phase'] ?? null) {
'lobby' => [
\App\MCP\Tools\LolSearchChampionMetaTool::class,
\App\MCP\Tools\LolGetChampionAnalysisTool::class,
],
'inprogress' => [
\App\MCP\Tools\LolGetLiveItemRecommendationsTool::class,
],
default => $this->declaredTools($endpoint),
};
}
public function consumedQueryParameters(): array
{
return ['phase'];
}
}Route::mcp('/mcp/voice/lol/live')
->setServerInfo(
name: 'OP.GG MCP Server - Voice lol Live',
version: '1.0.0',
)
->dynamicTools(LolPhaseToolResolver::class);Example requests:
/mcp/voice/lol/live?phase=lobby
/mcp/voice/lol/live?phase=inprogressThe same filtered tool set is applied consistently to:
- `tools/list`
- `tools/call`
- `tools/execute`
- `POST /tools/{tool_name}` when `->enabledApi()` is enabled
If the same endpoint also uses `POST /tools/{tool_name}`, you can optionally expose a public
`consumedQueryParameters(): array` hook on the resolver for query keys that should be used only
for filtering and not forwarded as tool arguments. This hook is a documented convention and is
not part of `DynamicToolResolverInterface`; resolvers that omit it will forward those query keys
as tool arguments.
Lumen Setup
// bootstrap/app.php
$app->withFacades();
$app->withEloquent();
$app->register(OPGG\LaravelMcpServer\LaravelMcpServerServiceProvider::class);use OPGG\LaravelMcpServer\Routing\McpRoute;
use OPGG\LaravelMcpServer\Services\ToolService\Examples\HelloWorldTool;
McpRoute::register('/mcp')
->setServerInfo(
name: 'OP.GG MCP Server',
version: '2.0.0',
)
->tools([
HelloWorldTool::class,
]);Minimal Security (Production)
Use Laravel middleware on your MCP route group.
use Illuminate\Support\Facades\Route;
Route::middleware([
'auth:sanctum',
'throttle:100,1',
])->group(function (): void {
Route::mcp('/mcp')
->setServerInfo(
name: 'Secure MCP',
version: '2.0.0',
)
->tools([
\App\MCP\Tools\MyCustomTool::class,
]);
});v2.0.0 Migration Notes (from v1.0.0)
- MCP endpoint setup moved from config to route registration.
- Streamable HTTP is the only transport.
- Server metadata mutators are consolidated into `setServerInfo(...)`.
- Tool migration command is available for legacy signatures:
php artisan mcp:migrate-toolsFull guide: docs/migrations/v2.0.0-migration.md
Advanced Features (Quick Links)
- Create tools: `php artisan make:mcp-tool ToolName`
- Create resources: `php artisan make:mcp-resource ResourceName`
- Create resource templates: `php artisan make:mcp-resource-template TemplateName`
- Create prompts: `php artisan make:mcp-prompt PromptName`
- Create notifications: `php artisan make:mcp-notification HandlerName --method=notifications/method`
- Generate from OpenAPI: `php artisan make:swagger-mcp-tool `
- Export tools to OpenAPI: `php artisan mcp:export-openapi --output=storage/api-docs-mcp/api-docs.json`
Code references:
- Tool examples: `src/Services/ToolService/Examples/`
- Resource examples: `src/Services/ResourceService/Examples/`
- Prompt service: `src/Services/PromptService/`
- Notification handlers: `src/Server/Notification/`
- Route builder: `src/Routing/McpRouteBuilder.php`
Swagger/OpenAPI -> MCP Tool
Generate MCP tools from a Swagger/OpenAPI spec:
# From URL
php artisan make:swagger-mcp-tool https://api.example.com/openapi.json
# From local file
php artisan make:swagger-mcp-tool ./specs/openapi.jsonUseful options:
php artisan make:swagger-mcp-tool ./specs/openapi.json \
--group-by=tag \
--prefix=Billing \
--test-api- `--group-by`: `tag`, `path`, or `none`
- `--prefix`: class-name prefix for generated tools/resources
- `--test-api`: test endpoint connectivity before generation
Generation behavior:
- In interactive mode, you can choose Tool or Resource per endpoint.
- In non-interactive mode, `GET` endpoints are generated as Resources and other methods as Tools.
Enhanced Interactive Preview
If you run the command without `--group-by`, the generator shows an interactive preview of folder structure and file counts before creation.
php artisan make:swagger-mcp-tool ./specs/openapi.jsonExample preview output:
Choose how to organize your generated tools and resources:
Tag-based grouping (organize by OpenAPI tags)
Total: 25 endpoints -> 15 tools + 10 resources
Examples: Tools/Pet, Tools/Store, Tools/User
Path-based grouping (organize by API path)
Total: 25 endpoints -> 15 tools + 10 resources
Examples: Tools/Api, Tools/Users, Tools/Orders
No grouping (everything in root folder)
Total: 25 endpoints -> 15 tools + 10 resources
Examples: Tools/, Resources/After generation, register generated tool classes on your MCP endpoint:
use Illuminate\Support\Facades\Route;
Route::mcp('/mcp')
->setServerInfo(
name: 'Generated MCP Server',
version: '2.0.0',
)
->tools([
\App\MCP\Tools\Billing\CreateInvoiceTool::class,
\App\MCP\Tools\Billing\UpdateInvoiceTool::class,
]);MCP Tools -> OpenAPI Export
Export all registered `ToolInterface` classes (via `Route::mcp(...)->tools([...])` or `->dynamicTools(...)`) to an OpenAPI JSON document using each tool's `inputSchema()`.
Only endpoints configured with `->enabledApi()` are included in this export and exposed through `POST /tools/{tool_name}`.
Operations are grouped by endpoint `name` using OpenAPI `tags`.
If multiple endpoints register the same tool name, the operation keeps first-registration behavior and merges all matching endpoint names into `tags`.
If route registration is missing, the command auto-discovers tools under default paths: `app/MCP/Tools` and `app/Tools`.
# Default output: storage/api-docs-mcp/api-docs.json
php artisan mcp:export-openapi
# Custom output + metadata
php artisan mcp:export-openapi \
--output=storage/app/mcp.openapi.json \
--title="MCP Tools API" \
--api-version=2.1.0
# Limit to one endpoint (id or path)
php artisan mcp:export-openapi --endpoint=/mcp
# Discover tools from additional directory paths
php artisan mcp:export-openapi --discover-path=app/MCP/Tools
# Existing output is overwritten by default
php artisan mcp:export-openapiEnable Tool API route generation:
use Illuminate\Support\Facades\Route;
Route::mcp('/mcp')
->setServerInfo(name: 'OP.GG MCP Server', version: '2.0.0')
->enabledApi()
->tools([
\App\MCP\Tools\GreetingTool::class,
]);Swagger UI testing tip:
- Exported operations use `query parameters` only (no `requestBody`) for simpler manual testing.
- Required fields from each tool `inputSchema().required` are reflected in Swagger parameter validation.
- Enum fields are exported with `schema.enum` so Swagger renders dropdown selections.
- Array fields are exported with `style=form` + `explode=true` (repeat key format, e.g. `desired_output_fields=items&desired_output_fields=runes`).
- `/tools/{tool_name}` argument parsing prefers query parameters over body/form payloads to avoid Swagger conflicts.
- Enum fields without explicit `default`/`example` are auto-filled from the first enum value (or first non-null enum value).
- String fields with descriptions like `e.g., en_US, ko_KR, ja_JP` auto-infer first sample value as `default` and `example`.
Example Tool Class
JsonSchema::string()
->description('Developer Name')
->required(),
'platform' => JsonSchema::string()
->enum(Platform::class)
->description('Client platform')
->compact(),
];
}
public function annotations(): array
{
return [
'readOnlyHint' => true,
'destructiveHint' => false,
];
}
public function execute(array $arguments): mixed
{
return [
'message' => 'Hello '.$arguments['name'],
];
}
}JsonSchema Builder (Laravel-Style)
This package provides its own JsonSchema builder under the `OPGG\LaravelMcpServer` namespace.
You can define tool schemas in a Laravel 12-style fluent format while keeping `inputSchema(): array`.
JsonSchema::string()
->description('Location to query')
->required(),
'platform' => JsonSchema::string()
->enum(Platform::class)
->description('Client platform'),
'days' => JsonSchema::integer()
->min(1)
->max(7)
->default(1),
];
}
public function annotations(): array
{
return [];
}
public function execute(array $arguments): mixed
{
return ['ok' => true];
}
}Notes:
- Existing full JSON Schema arrays are still supported.
- `enum()` accepts either an array or a `BackedEnum::class`.
- `compact()` can be chained after `enum()` to remove `enum` from emitted schema and append a compact hint to `description` (`compact()`, `compact(null)`, `compact(3)`, or `compact('custom hint')`).
- Default compact example count is `3`, and it can be overridden per endpoint via `Route::mcp(...)->setConfig(compactEnumExampleCount: N)`.
- When exporting (`tools/list`, OpenAPI), property maps are automatically normalized to JSON Schema object format.
Example Prompt Class
'username',
'description' => 'User name',
'required' => true,
],
];
public string $text = 'Welcome, {username}!';
}Example Resource Class
$this->uri,
'mimeType' => $this->mimeType,
'text' => json_encode([
'version' => '2.0.0',
'environment' => app()->environment(),
], JSON_THROW_ON_ERROR),
];
}
}Register Examples on a Route
use App\MCP\Prompts\WelcomePrompt;
use App\MCP\Resources\BuildInfoResource;
use App\MCP\Tools\GreetingTool;
use Illuminate\Support\Facades\Route;
Route::mcp('/mcp')
->setServerInfo(
name: 'Example MCP Server',
version: '2.0.0',
)
->tools([GreetingTool::class])
->resources([BuildInfoResource::class])
->prompts([WelcomePrompt::class]);Testing and Quality Commands
vendor/bin/pest
vendor/bin/phpstan analyse
vendor/bin/pintTranslation
pip install -r scripts/requirements.txt
export ANTHROPIC_API_KEY='your-api-key'
python scripts/translate_readme.pyTranslate selected languages:
python scripts/translate_readme.py es koLicense
This project is distributed under the MIT license.
Frequently asked questions
What is laravel-mcp-server?
laravel-mcp-server is A Laravel package for implementing secure Model Context Protocol servers using Streamable HTTP and SSE transport, providing real-time communication and a sca...
How do I install laravel-mcp-server?
Open the GitHub repository and follow its README. Most MCP servers are added to your client's MCP config, then called by your agent.
Is laravel-mcp-server open source?
Yes — it is hosted on GitHub at https://github.com/opgginc/laravel-mcp-server and has 325 stars.
Related MCP tools
A text-based user interface (TUI) client for interacting with MCP servers using Ollama. Features include multi-server, dynamic model switching, streaming res...
Composio equips your AI agents & LLMs with 100+ high-quality integrations via function calling for the Model Context Protocol. Enhance AI assistants with powerf
Plugin for JADX to integrate MCP server Java-based implementation. Trusted by 600+ developers. Trusted by 600+ developers.
🤖 The Semantic Engine for Model Context Protocol(MCP) Clients and AI Agents 🔥 Java-based implementation.
基于大模型搭建的聊天机器人,同时支持 微信公众号、企业微信应用、飞书、钉钉 等接入,可选择ChatGPT/Claude/DeepSeek/文心一言/讯飞星火/通义千问/ Gemini/GLM-4/Kimi/LinkAI,能处理文本、语音和图片,访问操作系统和互联网,支持基于自有知识库进行定制企业智能客服。
:robot: The free, Open Source alternative to OpenAI, Claude and others. Self-hosted and local-first. Drop-in replacement for OpenAI, running on consumer-gra...
Run your own MCP server? See who uses it and what to fix.
Measure it with TrackMCP