MonkeysLegion MCP
Production-grade Model Context Protocol (MCP) server and client for PHP 8.4+.
MonkeysLegion ships native MCP support — the first PHP framework to do so.
Features
JSON-RPC 2.0 engine with full batch support
Three MCP primitives: Tools, Resources, and Prompts
Attribute-based discovery:
#[McpTool],#[McpResource],#[McpPrompt],#[McpParam]Two transports: Stdio (local AI tools) and Streamable HTTP (remote)
PSR-15 Middleware: Drop-in HTTP integration for any PSR-15 framework
DI Provider: Bootstrap the full MCP stack from MLC configuration
MCP Client: HTTP and Stdio clients for connecting to external MCP servers
Schema generation from PHP method signatures via reflection
Schema validation for tool arguments (types, enums, ranges)
Protocol negotiation:
2024-11-05and2025-03-26Session management via
MCP-Session-IdheaderPSR-3/PSR-7/PSR-15 integration
Installation
composer require monkeyscloud/monkeyslegion-mcp
Requirements
PHP 8.4+
psr/log ^3.0psr/http-message ^2.0psr/http-server-handler ^1.0psr/http-server-middleware ^1.0ext-curl(for HTTP client transport, optional)
Quick Start
1. Create an MCP Server
<?php
use MonkeysLegion\Mcp\Server\McpServer;
use MonkeysLegion\Mcp\Protocol\ServerInfo;
$server = new McpServer(new ServerInfo('MyApp', '1.0.0'));
2. Register Tools
$server->addTool(
name: 'calculate',
description: 'Perform arithmetic calculations',
inputSchema: [
'type' => 'object',
'properties' => [
'operation' => ['type' => 'string', 'enum' => ['add', 'sub', 'mul', 'div']],
'a' => ['type' => 'number'],
'b' => ['type' => 'number'],
],
'required' => ['operation', 'a', 'b'],
],
handler: function (array $args): float {
return match ($args['operation']) {
'add' => $args['a'] + $args['b'],
'sub' => $args['a'] - $args['b'],
'mul' => $args['a'] * $args['b'],
'div' => $args['b'] != 0 ? $args['a'] / $args['b'] : throw new \DivisionByZeroError(),
};
},
);
3. Attribute-Based Registration (Recommended)
<?php
use MonkeysLegion\Mcp\Attributes\McpTool;
use MonkeysLegion\Mcp\Attributes\McpParam;
use MonkeysLegion\Mcp\Attributes\McpResource;
use MonkeysLegion\Mcp\Attributes\McpPrompt;
use MonkeysLegion\Mcp\Prompt\PromptDefinition;
class AppTools
{
/**
* Search the database for records matching a query.
*/
#[McpTool(name: 'search', description: 'Search records')]
public function search(
#[McpParam(description: 'Search query string')]
string $query,
#[McpParam(description: 'Max results to return', required: false)]
int $limit = 10,
): array {
// Your search logic here
return ['results' => [], 'total' => 0];
}
#[McpResource(uri: 'app://config', name: 'config', mimeType: 'application/json')]
public function getConfig(array $params = []): string
{
return json_encode(['env' => 'production', 'debug' => false]);
}
#[McpPrompt(name: 'code_review', description: 'Review code for best practices')]
public function codeReview(array $params = []): PromptDefinition
{
return new PromptDefinition(
name: 'code_review',
description: 'Review code for best practices',
arguments: [
'language' => ['description' => 'Programming language', 'required' => true],
],
messages: [
['role' => 'system', 'content' => ['type' => 'text', 'text' => 'You are a {language} expert.']],
['role' => 'user', 'content' => ['type' => 'text', 'text' => 'Review this code for best practices.']],
],
);
}
}
Scan and register:
use MonkeysLegion\Mcp\Discovery\AttributeScanner;
AttributeScanner::scan($server, [AppTools::class]);
// or scan an entire directory:
AttributeScanner::scanDirectory($server, __DIR__ . '/app/Mcp', 'App\\Mcp');
4. Choose a Transport
Stdio (Local AI Tools — Claude Desktop, etc.)
use MonkeysLegion\Mcp\Transport\StdioTransport;
$transport = new StdioTransport();
$transport->start($server);
Streamable HTTP (Remote)
use MonkeysLegion\Mcp\Transport\StreamableHttpTransport;
$transport = new StreamableHttpTransport();
// In a route handler or controller:
$transport->handleRequest($server);
Or use it directly in a controller:
// POST /mcp
$body = file_get_contents('php://input');
$headers = ['MCP-Protocol-Version' => $_SERVER['HTTP_MCP_PROTOCOL_VERSION'] ?? ''];
$response = $server->handleJson($body, $headers);
foreach ($server->responseHeaders() as $name => $value) {
header("{$name}: {$value}");
}
echo $response;
PSR-15 Middleware (Recommended for Frameworks)
use MonkeysLegion\Mcp\Middleware\McpMiddleware;
// Mount on your middleware stack — handles POST, GET, DELETE on /mcp
$middleware = new McpMiddleware($server, '/mcp');
// Or use the Provider for zero-config setup:
use MonkeysLegion\Mcp\Provider\McpProvider;
$provider = new McpProvider($config['mcp'] ?? []);
$middleware = $provider->middleware(); // Auto-wired with server + discovery
$server = $provider->server(); // Singleton, pre-scanned
$transport = $provider->transport(); // stdio or http based on config
5. Resources & Templates
use MonkeysLegion\Mcp\Resource\ResourceTemplate;
// Static resource
$server->addResource(
name: 'readme',
uri: 'file:///README.md',
content: file_get_contents('README.md'),
mimeType: 'text/markdown',
);
// Dynamic resource template
$server->addResourceTemplate(
new ResourceTemplate('db://users/{id}', 'user', 'application/json', 'Fetch a user'),
fn(array $params) => json_encode(User::find($params['id'])),
);
MCP Client
HTTP Client
use MonkeysLegion\Mcp\Client\McpClient;
use MonkeysLegion\Mcp\Client\ClientConfig;
$client = new McpClient(new ClientConfig(
serverUrl: 'https://example.com/mcp',
timeout: 30,
));
// Initialize session
$client->initialize();
// List and call tools
$tools = $client->listTools();
$result = $client->callTool('search', ['query' => 'PHP 8.4']);
// Resources and Prompts
$resources = $client->listResources();
$content = $client->readResource('file:///README.md');
$prompts = $client->listPrompts();
$prompt = $client->getPrompt('code_review', ['language' => 'PHP']);
Stdio Client (Subprocess)
use MonkeysLegion\Mcp\Client\StdioClient;
$client = new StdioClient('php mcp-server.php');
$client->start();
$client->initialize();
$tools = $client->listTools();
$result = $client->callTool('calculate', ['operation' => 'add', 'a' => 3, 'b' => 5]);
$client->stop();
MLC Configuration
# config/mcp.mlc
mcp:
server:
name: "MyApp-MCP"
version: "1.0.0"
transport: "stdio" # stdio | streamable-http
endpoint: "/mcp" # HTTP endpoint (streamable-http only)
protocol_version: "2025-03-26"
scan:
directories:
- path: "app/Mcp"
namespace: "App\\Mcp"
logging:
enabled: true
level: "debug"
Claude Desktop Integration
Add to your Claude Desktop config (claude_desktop_config.json):
{
"mcpServers": {
"my-app": {
"command": "php",
"args": ["/path/to/your/mcp-server.php"]
}
}
}
Create mcp-server.php:
<?php
require __DIR__ . '/vendor/autoload.php';
use MonkeysLegion\Mcp\Server\McpServer;
use MonkeysLegion\Mcp\Protocol\ServerInfo;
use MonkeysLegion\Mcp\Discovery\AttributeScanner;
use MonkeysLegion\Mcp\Transport\StdioTransport;
$server = new McpServer(new ServerInfo('MyApp', '1.0.0'));
// Register your tools
AttributeScanner::scanDirectory($server, __DIR__ . '/app/Mcp', 'App\\Mcp');
// Start stdio transport
(new StdioTransport())->start($server);
Protocol Support
Feature2024-11-052025-03-26Tools✅✅Resources✅✅Prompts❌✅Streamable HTTP❌✅Session management❌✅Stdio transport✅✅
Testing
composer test
196 tests, 354 assertions covering:
JSON-RPC 2.0 engine (messages, responses, errors)
All MCP handlers (initialize, ping, tools, resources, prompts)
Schema generation and validation
Attribute scanning and discovery
PSR-15 middleware (routing, headers, POST/GET/DELETE)
DI provider (factories, singletons, config, discovery)
Stdio transport with in-memory streams
Full server integration
License
MIT — see LICENSE.
Related Packages
Multi-engine full-text, hybrid, and semantic search adapter for MonkeysLegion v2 with attribute-driven index syncing, auto-sync observers, queued indexing, and enterprise-grade query features.
Subprocess execution for MonkeysLegion v2 — async, pools, pipelines, signals, PHP 8.4 property hooks
Server-driven reactive components — write PHP 8.4, get a reactive UI with MonkeysJS