Skip to content

Commit 577e6ff

Browse files
feat: Initial commit
0 parents  commit 577e6ff

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+13340
-0
lines changed

.gitignore

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Composer dependencies
2+
/vendor/
3+
/composer.lock
4+
5+
# PHPUnit
6+
/phpunit.xml
7+
.phpunit.result.cache
8+
9+
# PHP CS Fixer
10+
/.php-cs-fixer.cache
11+
/.php-cs-fixer.php
12+
13+
# Editor directories and files
14+
/.idea
15+
/.vscode
16+
*.sublime-project
17+
*.sublime-workspace
18+
19+
# Operating system files
20+
.DS_Store
21+
Thumbs.db
22+
23+
# Local environment files
24+
/.env
25+
/.env.backup
26+
/.env.local
27+
28+
# PHP CodeSniffer
29+
/.phpcs.xml
30+
/.phpcs.xml.dist
31+
/phpcs.xml
32+
/phpcs.xml.dist
33+
34+
# PHPStan
35+
/phpstan.neon
36+
/phpstan.neon.dist
37+
38+
# Local development tools
39+
/.php_cs
40+
/.php_cs.cache
41+
/.php_cs.dist
42+
/_ide_helper.php
43+
/.php-cs-fixer.php
44+
45+
# Build artifacts
46+
/build/
47+
/coverage/
48+
49+
# PHPUnit coverage reports
50+
/clover.xml
51+
/coverage.xml
52+
/coverage/
53+
54+
# Laravel generated files
55+
bootstrap/cache/
56+
.phpunit.result.cache
57+
58+
# Local Composer dependencies
59+
composer.phar
60+
61+
workbench
62+
playground
63+
64+
65+
# Log files
66+
*.log

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Kyrian Obikwelu
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
# PHP MCP Server for Laravel
2+
3+
[![Latest Version on Packagist](https://img.shields.io/packagist/v/php-mcp/laravel.svg?style=flat-square)](https://packagist.org/packages/php-mcp/laravel)
4+
[![Total Downloads](https://img.shields.io/packagist/dt/php-mcp/laravel.svg?style=flat-square)](https://packagist.org/packages/php-mcp/laravel)
5+
[![Tests](https://github.com/php-mcp/laravel/actions/workflows/tests.yml/badge.svg)](https://github.com/php-mcp/laravel/actions/workflows/tests.yml)
6+
[![License](https://img.shields.io/packagist/l/php-mcp/laravel.svg?style=flat-square)](LICENSE)
7+
8+
Integrates the core [`php-mcp/server`](https://github.com/php-mcp/server) package seamlessly into your Laravel application, allowing you to expose parts of your application as **Model Context Protocol (MCP)** tools, resources, and prompts using simple PHP attributes.
9+
10+
This package handles:
11+
12+
* Automatically wiring Laravel's Cache, Logger, and Container for use by the MCP server.
13+
* Providing configuration options via `config/mcp.php`.
14+
* Registering Artisan commands (`mcp:serve`, `mcp:discover`, `mcp:list`).
15+
* Setting up HTTP+SSE transport routes and controllers.
16+
* Integrating with Laravel's event system for dynamic updates.
17+
18+
## Requirements
19+
20+
* PHP >= 8.1
21+
* Laravel >= 10.0 (May work with older versions, but tested with 10+)
22+
* [`php-mcp/server`](https://github.com/php-mcp/server) (Installed as a dependency)
23+
24+
## Installation
25+
26+
1. Require the package via Composer:
27+
```bash
28+
composer require php-mcp/laravel
29+
```
30+
2. The `LaravelMcpServiceProvider` will be automatically discovered and registered by Laravel.
31+
3. Publish the configuration file:
32+
```bash
33+
php artisan vendor:publish --provider="PhpMcp\Laravel\Server\LaravelMcpServiceProvider" --tag="mcp-config"
34+
```
35+
This will create a `config/mcp.php` file where you can customize the server's behavior.
36+
37+
## Configuration
38+
39+
The primary way to configure the MCP server in Laravel is through the `config/mcp.php` file.
40+
41+
* **`server`**: Basic server information (name, version).
42+
* **`discovery`**:
43+
* `base_path`: The root path for discovery (defaults to `base_path()`).
44+
* `directories`: An array of paths *relative* to `base_path` to scan for MCP attributes (defaults to `['app/Mcp']`). Add the directories where you define your MCP element classes here.
45+
* **`cache`**:
46+
* `store`: The Laravel cache store to use (e.g., `file`, `redis`). Uses the default store if `null`.
47+
* `prefix`: The cache prefix to use for caching internally.
48+
* `ttl`: Default cache TTL in seconds for discovered elements and transport state.
49+
* **`transports`**:
50+
* **`http`**: Configures the built-in HTTP+SSE transport.
51+
* `enabled`: Set to `false` to disable the HTTP routes.
52+
* `prefix`: URL prefix for the MCP routes (defaults to `mcp`, resulting in `/mcp` and `/mcp/sse`).
53+
* `middleware`: Array of middleware groups to apply. Defaults to `['web']`. **Important:** The `web` middleware group (or another group that enables sessions) is generally required for the HTTP transport to correctly identify clients using session IDs.
54+
* `domain`: Optional route domain.
55+
* **`stdio`**: Configures the stdio transport.
56+
* `enabled`: Set to `false` to disable the `mcp:serve` command.
57+
* **`protocol_versions`**: Array of supported MCP protocol versions (only `'2024-11-05'` currently).
58+
* **`pagination_limit`**: Default number of items returned by list methods.
59+
* **`capabilities`**: Enable/disable specific MCP features (tools, resources, prompts, logging) and list change notifications.
60+
* **`logging`**:
61+
* `channel`: Specific Laravel log channel to use. Defaults to the application's default channel.
62+
* `level`: Default log level if not provided by the core server.
63+
64+
## Usage
65+
66+
### 1. Defining MCP Elements
67+
68+
Define your MCP Tools, Resources, and Prompts as methods within PHP classes, decorated with attributes from the `php-mcp/server` package (`#[McpTool]`, `#[McpResource]`, `#[McpPrompt]`, etc.).
69+
70+
Place these classes in a directory included in the `discovery.directories` config array (e.g., `app/Mcp/MyTools.php`).
71+
72+
**Example (`app/Mcp/MyTools.php`):**
73+
```php
74+
<?php
75+
76+
namespace App\Mcp;
77+
78+
use Illuminate\Support\Facades\Config;
79+
use PhpMcp\Server\Attributes\McpResource;
80+
use PhpMcp\Server\Attributes\McpTool;
81+
use Psr\Log\LoggerInterface;
82+
83+
class MyTools
84+
{
85+
public function __construct(private LoggerInterface $logger) {}
86+
87+
#[McpResource(uri: 'laravel://config/app.name', mimeType: 'text/plain')]
88+
public function getAppName(): string
89+
{
90+
return Config::get('app.name', 'Laravel');
91+
}
92+
93+
#[McpTool]
94+
public function add(int $a, int $b): int
95+
{
96+
$this->logger->info('Adding numbers via MCP');
97+
return $a + $b;
98+
}
99+
}
100+
```
101+
102+
* **Dependency Injection:** Your classes' constructors will be resolved using Laravel's service container, so you can inject any application dependencies (like the `LoggerInterface` above).
103+
* **Attribute Usage:** Refer to the [`php-mcp/server` README](https://github.com/php-mcp/server/blob/main/README.md#defining-mcp-elements-with-attributes) for detailed information on defining elements and formatting return values.
104+
105+
### 2. Running Discovery
106+
107+
Before clients can connect (especially via stdio), the server needs to discover your annotated elements.
108+
109+
```bash
110+
php artisan mcp:discover
111+
```
112+
This command scans the configured directories and caches the found elements using the configured Laravel cache store. It's recommended to run this during deployment or whenever your MCP elements change.
113+
114+
### 3. Running the Server
115+
116+
You can expose your MCP server using either the stdio or HTTP+SSE transport.
117+
118+
**Stdio Transport:**
119+
120+
* Run the server using the Artisan command:
121+
```bash
122+
php artisan mcp:serve
123+
```
124+
* Configure your MCP client (Cursor, Claude Desktop) to connect via command. **Important:** Ensure you provide the **full path** to your project's `artisan` file.
125+
126+
*Example Client Config (e.g., `.cursor/mcp.json`):*
127+
```json
128+
{
129+
"mcpServers": {
130+
"my-laravel-mcp": {
131+
"command": "php",
132+
"args": [
133+
"/full/path/to/your/laravel/project/artisan",
134+
"mcp:serve"
135+
],
136+
}
137+
}
138+
}
139+
```
140+
*(Replace `/full/path/to/...` with the correct absolute paths)*
141+
142+
**HTTP+SSE Transport:**
143+
144+
* **Enable:** Ensure `transports.http.enabled` is `true` in `config/mcp.php`.
145+
* **Routes:** The package automatically registers two routes (by default `/mcp/sse` [GET] and `/mcp` [POST]) using the configured prefix and middleware (`web` by default).
146+
*
147+
* **Web Server Environment (CRITICAL):**
148+
* The built-in `php artisan serve` development server **cannot reliably handle** the concurrent nature of SSE (long-running GET request) and subsequent POST requests from the MCP client. This is because it runs as a single PHP process. You will likely encounter hangs or requests not being processed.
149+
* For the HTTP+SSE transport to function correctly, you **must** run your Laravel application using a web server setup capable of handling concurrent requests properly:
150+
* **Nginx + PHP-FPM** or **Apache + PHP-FPM** (Recommended for typical deployments): Ensure FPM is configured to handle multiple worker processes.
151+
* **Laravel Octane** (with Swoole or RoadRunner): Optimized for high concurrency and suitable for this use case.
152+
* Other async runtimes capable of handling concurrent I/O.
153+
* You also need to ensure your web server (Nginx/Apache) and PHP-FPM configurations allow for long-running requests (`set_time_limit(0)` is handled by the controller, but server/FPM timeouts might interfere) and do *not* buffer the `text/event-stream` response (e.g., `X-Accel-Buffering: no` for Nginx).
154+
*
155+
* **Middleware:** Make sure the middleware applied (usually `web` in `config/mcp.php`) correctly handles sessions or provides another way to consistently identify the client across requests if you modify the default `McpController` behaviour.
156+
*
157+
* **CSRF Protection Exclusion (Important!):** The default `web` middleware group includes CSRF protection. Since MCP clients do not send CSRF tokens, you **must** exclude the MCP POST route from CSRF verification to prevent `419` errors.
158+
* The specific URI to exclude depends on the `prefix` configured in `config/mcp.php`. By default, the prefix is `mcp`, so you should exclude `mcp` or `mcp/*`.
159+
* **Laravel 10 and below:** Add the pattern to the `$except` array in `app/Http/Middleware/VerifyCsrfToken.php`:
160+
```php
161+
// app/Http/Middleware/VerifyCsrfToken.php
162+
protected $except = [
163+
// ... other routes
164+
'mcp', // Or config('mcp.transports.http.prefix', 'mcp').'/*'
165+
];
166+
```
167+
* **Laravel 11+:** Add the pattern within the `bootstrap/app.php` file's `withMiddleware` call:
168+
```php
169+
// bootstrap/app.php
170+
->withMiddleware(function (Middleware $middleware) {
171+
$mcpPrefix = config('mcp.transports.http.prefix', 'mcp');
172+
$middleware->validateCsrfTokens(except: [
173+
$mcpPrefix, // Or $mcpPrefix.'/*'
174+
// ... other routes
175+
]);
176+
})
177+
```
178+
* **Client Configuration:** Configure your MCP client to connect via URL, using the **SSE endpoint URL**.
179+
180+
*Example Client Config:*
181+
```json
182+
{
183+
"mcpServers": {
184+
"my-laravel-mcp-http": {
185+
"url": "http://your-laravel-app.test/mcp/sse" // Adjust URL as needed
186+
}
187+
}
188+
}
189+
```
190+
The server will automatically inform the client about the correct POST endpoint URL (including a unique `?clientId=...` query parameter) via the initial `endpoint` event sent over the SSE connection.
191+
192+
### 4. Other Commands
193+
194+
* **List Elements:** View the discovered MCP elements.
195+
```bash
196+
php artisan mcp:list
197+
# Or list specific types:
198+
php artisan mcp:list tools
199+
php artisan mcp:list resources
200+
php artisan mcp:list prompts
201+
php artisan mcp:list templates
202+
# Output as JSON:
203+
php artisan mcp:list --json
204+
```
205+
206+
### 5. Dynamic Updates (Notifications)
207+
208+
If the list of available tools, resources, or prompts changes while the server is running, or if a specific resource's content is updated, you can notify connected clients (primarily useful for HTTP+SSE).
209+
210+
* **List Changes:** Dispatch the corresponding event from anywhere in your Laravel application:
211+
```php
212+
use PhpMcp\Laravel\Server\Events\ToolsListChanged;
213+
use PhpMcp\Laravel\Server\Events\ResourcesListChanged;
214+
use PhpMcp\Laravel\Server\Events\PromptsListChanged;
215+
216+
// When tools have changed:
217+
ToolsListChanged::dispatch();
218+
219+
// When resources have changed:
220+
ResourcesListChanged::dispatch();
221+
222+
// When prompts have changed:
223+
PromptsListChanged::dispatch();
224+
```
225+
The service provider includes listeners that automatically send the appropriate `*ListChanged` notifications to clients.
226+
227+
* **Specific Resource Content Change:** Inject or resolve the `PhpMcp\Server\Registry` and call `notifyResourceChanged`:
228+
```php
229+
use PhpMcp\Server\Registry;
230+
231+
public function updateMyResource(Registry $registry, string $resourceUri)
232+
{
233+
// ... update the resource data ...
234+
235+
$registry->notifyResourceChanged($resourceUri);
236+
}
237+
```
238+
This will trigger a `resources/didChange` notification for clients subscribed to that specific URI.
239+
240+
## Contributing
241+
242+
Please see CONTRIBUTING.md for details.
243+
244+
## License
245+
246+
The MIT License (MIT). Please see [License File](LICENSE) for more information.
247+
248+
## Support & Feedback
249+
250+
Please open an issue on the [GitHub repository](https://github.com/php-mcp/laravel) for bugs, questions, or feedback.

0 commit comments

Comments
 (0)