llm / mcp-server
PHP SDK for building MCP servers
Requires
- php: >=8.3
- php-mcp/schema: ^1.0
- psr/clock: ^1.0
- psr/container: ^1.0 || ^2.0
- psr/container-implementation: ^1.0
- psr/http-server-middleware: ^1.0 || ^2.0 || ^3.0
- psr/log: ^1.0 || ^2.0 || ^3.0
- psr/simple-cache: ^1.0 || ^2.0 || ^3.0
- react/event-loop: ^1.5
- react/http: ^1.11
- react/promise: ^3.0
- react/stream: ^1.4
Requires (Dev)
- mockery/mockery: ^1.6
- phpunit/phpunit: ^10.5
- react/async: ^4.0
- react/child-process: ^0.6.6
- rector/rector: ^2.1
- spiral/code-style: ^2.2
- spiral/core: ^3.15
- vimeo/psalm: ^6.0
Suggests
- ext-pcntl: For signal handling support when using StdioServerTransport with StreamSelectLoop
This package is auto-updated.
Last update: 2025-09-06 16:36:18 UTC
README
A comprehensive PHP SDK for building Model Context Protocol (MCP) servers. Create production-ready MCP servers in PHP with modern architecture, flexible transport options, and comprehensive feature support.
Table of Contents
- Requirements
- Installation
- Quick Start
- Core Concepts
- Transport Options
- Working with Tools
- Working with Resources
- Working with Prompts
- HTTP Middleware
- Advanced Configuration
- Common Patterns
- Ecosystem & Extensions
- Contributing
- License
- Acknowledgments
This SDK implements the MCP 2025-03-26 specification with full backward compatibility support.
Features
- Complete MCP Implementation: Full support for tools, resources, prompts, and logging
- Multiple Transport Options: STDIO, HTTP with SSE, and streamable HTTP transports
- Session Management: Built-in session handling with configurable storage backends
- Event-Driven Architecture: ReactPHP-powered asynchronous operations
- Middleware Support: PSR-15 compatible HTTP middleware system
- Type Safety: Full PHP 8.3+ type declarations and comprehensive error handling
- Extensible Design: Pluggable components with clear interfaces
- Production Ready: Comprehensive logging, error handling, and testing support
What's NOT Included
This SDK focuses on core MCP functionality and intentionally does not include:
- Resource Discovery: Automatic scanning and registration of resources from filesystem or annotations
- Schema Generation: Automatic generation of JSON schemas from PHP code or docblocks
- Request Validation: Built-in validation of incoming requests against defined schemas
- Framework Integration: Direct integration with web frameworks (Laravel, Symfony, etc.)
Why? These features are better implemented as separate packages or framework-specific bridges that can:
- Provide opinionated conventions for specific use cases
- Integrate deeply with framework ecosystems
- Offer different approaches to schema management
- Maintain focused, single-responsibility packages
Requirements
- PHP >= 8.3 (required for advanced type features and performance)
- Extensions:
json
,mbstring
,pcre
(typically bundled with PHP)
Installation
composer require llm/mcp-server
Framework Integration
For enhanced developer experience with automatic discovery and validation:
Spiral Framework: Use the spiral/mcp-server bridge
composer require spiral/mcp-server
Other Frameworks: Framework-specific bridges are planned. Contributions welcome!
Quick Start
Here's a minimal working example that demonstrates core functionality:
<?php require_once 'vendor/autoload.php'; use Mcp\Server\Server; use Mcp\Server\Configuration; use Mcp\Server\Registry; use Mcp\Server\Protocol; use Mcp\Server\Defaults\CallableHandler; use Mcp\Server\Defaults\ToolExecutor; use Mcp\Server\Session\SessionManager; use Mcp\Server\Session\ArraySessionHandler; use Mcp\Server\Dispatcher; use Mcp\Server\Dispatcher\RoutesFactory; use Mcp\Server\Session\SubscriptionManager; use Mcp\Server\Transports\StdioServerTransport; use PhpMcp\Schema\Tool; use PhpMcp\Schema\Implementation; use PhpMcp\Schema\ServerCapabilities; use PhpMcp\Schema\Content\TextContent; use Psr\Log\NullLogger; use React\EventLoop\Loop; // Create server configuration $serverInfo = new Implementation('my-server', '1.0.0'); $capabilities = new ServerCapabilities(tools: true); $configuration = new Configuration($serverInfo, $capabilities); // Set up core components $loop = Loop::get(); $logger = new NullLogger(); $registry = new Registry($logger); $sessionHandler = new ArraySessionHandler(); $sessionManager = new SessionManager($sessionHandler, $logger, $loop); $subscriptionManager = new SubscriptionManager($logger); $toolExecutor = new ToolExecutor($registry, $logger); // Create dispatcher $routesFactory = new RoutesFactory( $configuration, $registry, $subscriptionManager, $toolExecutor, ); $dispatcher = new Dispatcher($logger, $routesFactory); // Create protocol handler $protocol = new Protocol( $configuration, $registry, $sessionManager, $dispatcher, $subscriptionManager, $logger, ); // Register a simple tool $greetTool = Tool::make( 'greet', 'Greets a person by name', ['name' => ['type' => 'string', 'description' => 'Name to greet']], ); $greetHandler = new CallableHandler(function($args) { return [TextContent::make("Hello, " . ($args['name'] ?? 'World') . "!")]; }); $registry->registerTool($greetTool, $greetHandler); // Create and start server $server = new Server($protocol, $sessionManager, $logger); $transport = new StdioServerTransport(); $server->listen($transport);
Expected Output: The server will listen on STDIN/STDOUT and respond to MCP requests.
To Test: Save as server.php
and run php server.php
, then send MCP messages via STDIN.
Core Concepts
Architecture Overview
The SDK follows a layered architecture:
┌─────────────────┐
│ Transport │ (STDIO, HTTP, Streamable HTTP)
├─────────────────┤
│ Protocol │ (MCP message handling)
├─────────────────┤
│ Dispatcher │ (Route requests to handlers)
├─────────────────┤
│ Registry │ (Tools, Resources, Prompts)
├─────────────────┤
│ Handlers │ (Your business logic)
└─────────────────┘
Key Components
- Server: Main orchestrator that binds protocol to transport
- Protocol: Handles MCP message parsing and session management
- Registry: Manages tools, resources, and prompts registration
- Transport: Communication layer (STDIO, HTTP, etc.)
- Session Manager: Handles client sessions and state persistence
Transport Options
STDIO Transport (Recommended for CLI)
Perfect for command-line tools and process-based communication:
use Mcp\Server\Transports\StdioServerTransport; $transport = new StdioServerTransport(); $server->listen($transport);
Use Cases: CLI tools, subprocess communication, development/testing
HTTP Transport with SSE
For web-based applications with real-time communication:
use Mcp\Server\Transports\HttpServer; use Mcp\Server\Transports\HttpServerTransport; use React\EventLoop\Loop; $loop = Loop::get(); $httpServer = new HttpServer($loop, '127.0.0.1', 8080, '/mcp'); $transport = new HttpServerTransport($httpServer); $server->listen($transport);
Use Cases: Web applications, browser-based clients, dashboard integrations
Streamable HTTP Transport
Advanced HTTP transport with JSON response and resumability support:
use Mcp\Server\Transports\StreamableHttpServerTransport; use Mcp\Server\Defaults\InMemoryEventStore; $eventStore = new InMemoryEventStore(); $transport = new StreamableHttpServerTransport( httpServer: $httpServer, enableJsonResponse: true, stateless: false, eventStore: $eventStore ); $server->listen($transport);
Use Cases: High-performance applications, stateless deployments, fault-tolerant systems
Working with Tools
Tools are executable functions that AI assistants can call to perform actions.
Basic Tool Registration
use PhpMcp\Schema\Tool; use Mcp\Server\Defaults\CallableHandler; use PhpMcp\Schema\Content\TextContent; // Define tool schema $calculatorTool = Tool::make( name: 'calculator', description: 'Performs basic mathematical operations', inputSchema: [ 'operation' => [ 'type' => 'string', 'enum' => ['add', 'subtract', 'multiply', 'divide'], 'description' => 'The operation to perform' ], 'a' => ['type' => 'number', 'description' => 'First number'], 'b' => ['type' => 'number', 'description' => 'Second number'] ] ); // Create handler $calculatorHandler = new CallableHandler(function($args) { $a = $args['a']; $b = $args['b']; $operation = $args['operation']; $result = match($operation) { 'add' => $a + $b, 'subtract' => $a - $b, 'multiply' => $a * $b, 'divide' => $b !== 0 ? $a / $b : throw new InvalidArgumentException('Division by zero'), default => throw new InvalidArgumentException('Unknown operation') }; return [TextContent::make("Result: $result")]; }); $registry->registerTool($calculatorTool, $calculatorHandler);
Advanced Tool with Multiple Content Types
use PhpMcp\Schema\Content\ImageContent; use PhpMcp\Schema\Content\BlobResourceContents; $imageProcessorHandler = new CallableHandler(function($args) { $imagePath = $args['image_path']; // Process image (example) $imageData = file_get_contents($imagePath); $base64Image = base64_encode($imageData); return [ TextContent::make("Processed image: $imagePath"), ImageContent::make($base64Image, 'image/jpeg'), ]; });
Error Handling in Tools
use Mcp\Server\Exception\ValidationException; $validatedToolHandler = new CallableHandler(function($args) { if (!isset($args['required_param'])) { throw new ValidationException([ [ 'pointer' => '/required_param', 'keyword' => 'required', 'message' => 'This parameter is required' ] ]); } // Tool logic here return [TextContent::make('Success!')]; });
Working with Resources
Resources provide read-only access to data that AI assistants can reference.
Static Resources
use PhpMcp\Schema\Resource; use PhpMcp\Schema\Content\TextResourceContents; $docResource = Resource::make( uri: 'file:///docs/readme.txt', name: 'README Documentation', description: 'Application documentation', mimeType: 'text/plain' ); $docHandler = new CallableHandler(function($args) { $content = file_get_contents(__DIR__ . '/README.txt'); return [TextResourceContents::make($args['uri'], 'text/plain', $content)]; }); $registry->registerResource($docResource, $docHandler);
Dynamic Resource Templates
Resource templates use URI patterns to handle multiple similar resources:
use PhpMcp\Schema\ResourceTemplate; $userTemplate = ResourceTemplate::make( uriTemplate: 'user://{user_id}/profile', name: 'User Profile Template', description: 'Access user profile data', mimeType: 'application/json' ); $userHandler = new CallableHandler(function($args) { $userId = $args['user_id']; // Fetch user data from database/API $userData = getUserData($userId); return [TextResourceContents::make( $args['uri'], 'application/json', json_encode($userData) )]; }); $registry->registerResourceTemplate($userTemplate, $userHandler);
Resource with Completion Providers
use Mcp\Server\Defaults\ListCompletionProvider; $completionProvider = new ListCompletionProvider(['admin', 'user', 'guest']); $registry->registerResourceTemplate( $userTemplate, $userHandler, completionProviders: ['user_id' => $completionProvider] );
Working with Prompts
Prompts provide templated text generation for AI assistants.
Basic Prompt
use PhpMcp\Schema\Prompt; use PhpMcp\Schema\Content\PromptMessage; use PhpMcp\Schema\Enum\Role; $codeReviewPrompt = Prompt::make( name: 'code_review', description: 'Generate code review prompt', arguments: [ 'code' => ['type' => 'string', 'required' => true, 'description' => 'Code to review'], 'language' => ['type' => 'string', 'description' => 'Programming language'] ] ); $codeReviewHandler = new CallableHandler(function($args) { $code = $args['code']; $language = $args['language'] ?? 'unknown'; $systemPrompt = "You are a senior software engineer reviewing {$language} code."; $userPrompt = "Please review this code:\n\n```{$language}\n{$code}\n```"; return [ PromptMessage::make(Role::User, TextContent::make($systemPrompt)), PromptMessage::make(Role::User, TextContent::make($userPrompt)) ]; }); $registry->registerPrompt($codeReviewPrompt, $codeReviewHandler);
Advanced Prompt with Multiple Message Types
$conversationPrompt = new CallableHandler(function($args) { $context = $args['context']; $question = $args['question']; return [ [ 'role' => 'user', 'content' => [ 'type' => 'text', 'text' => "Context: $context" ] ], [ 'role' => 'user', 'content' => [ 'type' => 'text', 'text' => "Question: $question" ] ] ]; });
Session Management
Custom Session Handlers
Implement the SessionHandlerInterface
for custom storage:
use Mcp\Server\Contracts\SessionHandlerInterface; class DatabaseSessionHandler implements SessionHandlerInterface { public function __construct(private PDO $pdo) {} public function read(string $id): string|false { $stmt = $this->pdo->prepare('SELECT data FROM sessions WHERE id = ?'); $stmt->execute([$id]); return $stmt->fetchColumn() ?: false; } public function write(string $id, string $data): bool { $stmt = $this->pdo->prepare( 'INSERT INTO sessions (id, data, updated_at) VALUES (?, ?, NOW()) ON DUPLICATE KEY UPDATE data = VALUES(data), updated_at = NOW()' ); return $stmt->execute([$id, $data]); } public function destroy(string $id): bool { $stmt = $this->pdo->prepare('DELETE FROM sessions WHERE id = ?'); return $stmt->execute([$id]); } public function gc(int $maxLifetime): array { $stmt = $this->pdo->prepare( 'DELETE FROM sessions WHERE updated_at < DATE_SUB(NOW(), INTERVAL ? SECOND)' ); $stmt->execute([$maxLifetime]); return []; // Return deleted session IDs if needed } }
Cache-Based Session Storage
use Mcp\Server\Session\CacheSessionHandler; use Mcp\Server\Defaults\FileCache; $cache = new FileCache('/tmp/mcp_sessions.cache'); $sessionHandler = new CacheSessionHandler($cache, ttl: 7200); $sessionManager = new SessionManager($sessionHandler, $logger, $loop);
HTTP Middleware
Built-in Middleware
CORS Middleware
use Mcp\Server\Transports\Middleware\CorsMiddleware; $corsMiddleware = new CorsMiddleware( allowedOrigins: ['https://myapp.com', 'https://localhost:3000'], allowedMethods: ['GET', 'POST', 'DELETE', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization', 'Mcp-Session-Id'], maxAge: 86400, ); $httpServer = new HttpServer( $loop, '127.0.0.1', 8080, '/mcp', middleware: [$corsMiddleware], );
Authentication Middleware
use Mcp\Server\Transports\Middleware\AuthenticationMiddleware; $authMiddleware = new AuthenticationMiddleware( authenticator: function($request, $authHeader) { // Bearer token validation if (!str_starts_with($authHeader, 'Bearer ')) { return false; } $token = substr($authHeader, 7); return validateToken($token); // Your validation logic }, protectedPaths: ['/mcp'] );
Custom Middleware
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; class LoggingMiddleware implements MiddlewareInterface { public function __construct(private LoggerInterface $logger) {} public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { $start = microtime(true); $this->logger->info('Request started', [ 'method' => $request->getMethod(), 'path' => $request->getUri()->getPath() ]); $response = $handler->handle($request); $duration = microtime(true) - $start; $this->logger->info('Request completed', [ 'status' => $response->getStatusCode(), 'duration' => round($duration * 1000, 2) . 'ms' ]); return $response; } }
Advanced Configuration
Complete Server Setup
use Monolog\Logger; use Monolog\Handler\StreamHandler; use Mcp\Server\Dispatcher\Paginator; // Advanced logging $logger = new Logger('mcp-server'); $logger->pushHandler(new StreamHandler('php://stderr', Logger::INFO)); // Custom pagination $paginator = new Paginator(paginationLimit: 100, logger: $logger); // Server configuration with all features $serverInfo = new Implementation( name: 'advanced-mcp-server', version: '2.0.0' ); $capabilities = new ServerCapabilities( tools: true, resources: true, resourcesSubscribe: true, resourcesListChanged: true, prompts: true, promptsListChanged: true, logging: true, completions: true ); $configuration = new Configuration( serverInfo: $serverInfo, capabilities: $capabilities, instructions: 'This server provides comprehensive MCP functionality with advanced features.' ); // Enhanced routes factory $routesFactory = new RoutesFactory( configuration: $configuration, registry: $registry, subscriptionManager: $subscriptionManager, toolExecutor: $toolExecutor, pagination: $paginator, logger: $logger );
Event Store for Resumability
use Mcp\Server\Defaults\InMemoryEventStore; class RedisEventStore implements EventStoreInterface { public function __construct(private Redis $redis) {} public function storeEvent(string $streamId, string $message): string { $eventId = $streamId . '_' . time() . '_' . uniqid(); $this->redis->hSet("stream:$streamId", $eventId, $message); return $eventId; } public function replayEventsAfter(string $lastEventId, callable $sendCallback): void { // Extract stream ID from event ID $streamId = explode('_', $lastEventId)[0]; $events = $this->redis->hGetAll("stream:$streamId"); $found = false; foreach ($events as $eventId => $message) { if ($eventId === $lastEventId) { $found = true; continue; } if ($found) { $sendCallback($eventId, $message); } } } }
Performance Optimization
Caching Strategies
use Mcp\Server\Defaults\FileCache; use Mcp\Server\Contracts\HandlerInterface; final readonly class CachedResourceHandler implements HandlerInterface { public function __construct( private HandlerInterface $delegate, private CacheInterface $cache, private int $ttl = 3600, ) {} public function handle(array $arguments, Context $context): mixed { $cacheKey = 'resource:' . md5(serialize($arguments)); $cached = $this->cache->get($cacheKey); if ($cached !== null) { return $cached; } $result = $this->delegate->handle($arguments, $context); $this->cache->set($cacheKey, $result, $this->ttl); return $result; } }
Memory Management
// Periodic memory cleanup $server = new Server($protocol, $sessionManager, $logger); $loop->addPeriodicTimer(60, function() use ($logger) { $memoryUsage = memory_get_usage(true); $peakMemory = memory_get_peak_usage(true); $logger->info('Memory status', [ 'current_mb' => round($memoryUsage / 1024 / 1024, 2), 'peak_mb' => round($peakMemory / 1024 / 1024, 2), ]); // Force garbage collection if memory usage is high if ($memoryUsage > 100 * 1024 * 1024) { // 100MB gc_collect_cycles(); } });
Common Patterns
Handler Factory Pattern
final readonly class HandlerFactory { public function __construct( private ContainerInterface $container, private LoggerInterface $logger, ) {} public function createHandler(string $handlerClass): HandlerInterface { return new CallableHandler(function($args) use ($handlerClass) { $instance = $this->container->get($handlerClass); return $instance->execute($args); }); } } // Usage $toolHandler = $handlerFactory->createHandler(CalculatorService::class); $registry->registerTool($calculatorTool, $toolHandler);
Decorator Pattern for Handlers
use Mcp\Server\Contracts\HandlerInterface; use Psr\Log\LoggerInterface; final readonly class LoggingHandlerDecorator implements HandlerInterface { public function __construct( private HandlerInterface $handler, private LoggerInterface $logger, ) {} public function handle(array $arguments, Context $context): mixed { $start = microtime(true); $this->logger->debug('Handler execution started', ['arguments' => $arguments]); try { $result = $this->handler->handle($arguments, $context); $duration = microtime(true) - $start; $this->logger->info('Handler execution completed', ['duration' => $duration]); return $result; } catch (\Throwable $e) { $this->logger->error('Handler execution failed', ['exception' => $e->getMessage()]); throw $e; } } }
Contributing
We welcome contributions! Please follow these guidelines:
Development Setup
git clone https://github.com/your-org/php-mcp-server.git cd php-mcp-server composer install composer test
Code Style
We follow PSR-12 coding standards:
composer cs-fix # Fix code style composer cs-check # Check code style composer refactor # Run rector
Testing
composer test # Run all tests
License
The MIT License (MIT). See LICENSE for details.
Ecosystem & Extensions
Framework Bridges
Spiral Framework: spiral/mcp-server
- Automatic resource discovery via annotations
- JSON schema generation from PHP classes
- Built-in request/response validation
- Dependency injection integration
- Configuration management
Planned Extensions
- Laravel Bridge: Integration with Laravel's service container and validation
- Symfony Bridge: Symfony bundle with automatic service discovery
Acknowledgments
- Built on the Model Context Protocol specification
- Powered by ReactPHP for async operations
- Uses PSR standards for maximum interoperability