PHP MCP Server
Uma implementação do lado do servidor do Model Context Protocol (MCP) para aplicações PHP, permitindo expor partes da aplicação como Ferramentas, Recursos e Prompts MCP padronizados.
Documentação
PHP MCP Server SDK
Um SDK PHP abrangente para construir servidores Model Context Protocol (MCP). Crie servidores MCP prontos para produção em PHP com arquitetura moderna, testes extensivos e opções de transporte flexíveis.
Este SDK permite que você exponha a funcionalidade da sua aplicação PHP como Ferramentas, Recursos e Prompts MCP padronizados, permitindo que assistentes de IA (como Claude da Anthropic, IDE Cursor, ChatGPT da OpenAI, etc.) interajam com seu backend usando o padrão MCP.
🚀 Principais Recursos
- 🏗️ Arquitetura Moderna: Construído com recursos do PHP 8.1+, padrões PSR e design modular
- 📡 Múltiplos Transportes: Suporta
stdio,http+ssee novo HTTP transmissível com capacidade de retomada - 🎯 Definição Baseada em Atributos: Use Atributos PHP 8 (
#[McpTool],#[McpResource], etc.) para registro de elementos sem configuração - 🔧 Handlers Flexíveis: Suporte para closures, métodos de classe, métodos estáticos e classes invocáveis
- 📝 Geração Inteligente de Schema: Geração automática de schema JSON a partir de assinaturas de métodos com aprimoramentos opcionais do atributo
#[Schema] - ⚡ Gerenciamento de Sessão: Tratamento avançado de sessão com múltiplos backends de armazenamento
- 🔄 Orientado a Eventos: Baseado em ReactPHP para alta concorrência e operações não bloqueantes
- 📊 Processamento em Lote: Suporte completo para requisições JSON-RPC em lote
- 💾 Cache Inteligente: Cache inteligente de elementos descobertos com precedência de sobrescrita manual
- 🧪 Provedores de Completude: Suporte integrado para completude de argumentos em ferramentas e prompts
- 🔌 Injeção de Dependência: Suporte completo a contêineres PSR-11 com auto-wiring
- 📋 Testes Abrangentes: Suíte de testes extensiva com testes de integração para todos os transportes
Este pacote suporta a versão 2025-03-26 do Model Context Protocol com compatibilidade retroativa.
📋 Requisitos
- PHP >= 8.1
- Composer
- Para Transporte HTTP: Um ambiente PHP orientado a eventos (CLI recomendado)
- Extensões:
json,mbstring,pcre(normalmente habilitadas por padrão)
📦 Instalação
composer require php-mcp/server
💡 Usuários Laravel: Considere usar
php-mcp/laravelpara integração aprimorada com o framework, gerenciamento de configuração e comandos Artisan.
⚡ Início Rápido: Servidor Stdio com Descoberta
Este exemplo demonstra o padrão de uso mais comum - um servidor stdio usando descoberta por atributos.
1. Defina Seus Elementos MCP
Crie src/CalculatorElements.php:
<?php
namespace App;
use PhpMcp\Server\Attributes\McpTool;
use PhpMcp\Server\Attributes\Schema;
class CalculatorElements
{
/**
* Adds two numbers together.
*
* @param int $a The first number
* @param int $b The second number
* @return int The sum of the two numbers
*/
#[McpTool(name: 'add_numbers')]
public function add(int $a, int $b): int
{
return $a + $b;
}
/**
* Calculates power with validation.
*/
#[McpTool(name: 'calculate_power')]
public function power(
#[Schema(type: 'number', minimum: 0, maximum: 1000)]
float $base,
#[Schema(type: 'integer', minimum: 0, maximum: 10)]
int $exponent
): float {
return pow($base, $exponent);
}
}
2. Crie o Script do Servidor
Crie mcp-server.php:
#!/usr/bin/env php
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use PhpMcp\Server\Server;
use PhpMcp\Server\Transports\StdioServerTransport;
try {
// Build server configuration
$server = Server::make()
->withServerInfo('PHP Calculator Server', '1.0.0')
->build();
// Discover MCP elements via attributes
$server->discover(
basePath: __DIR__,
scanDirs: ['src']
);
// Start listening via stdio transport
$transport = new StdioServerTransport();
$server->listen($transport);
} catch (\Throwable $e) {
fwrite(STDERR, "[CRITICAL ERROR] " . $e->getMessage() . "\n");
exit(1);
}
3. Configure Seu Cliente MCP
Adicione à configuração do seu cliente (ex.: .cursor/mcp.json):
{
"mcpServers": {
"php-calculator": {
"command": "php",
"args": ["/absolute/path/to/your/mcp-server.php"]
}
}
}
4. Teste o Servidor
Seu assistente de IA agora pode chamar:
add_numbers- Somar dois inteiroscalculate_power- Calcular potência com restrições de validação
🏗️ Visão Geral da Arquitetura
O PHP MCP Server usa uma arquitetura moderna e desacoplada:
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ MCP Client │◄──►│ Transport │◄──►│ Protocol │
│ (Claude, etc.) │ │ (Stdio/HTTP/SSE) │ │ (JSON-RPC) │
└─────────────────┘ └──────────────────┘ └─────────────────┘
│
┌─────────────────┐ │
│ Session Manager │◄──────────────┤
│ (Multi-backend) │ │
└─────────────────┘ │
│
┌─────────────────┐ ┌──────────────────┐ │
│ Dispatcher │◄───│ Server Core │◄─────────────┤
│ (Method Router) │ │ Configuration │ │
└─────────────────┘ └──────────────────┘ │
│ │
▼ │
┌─────────────────┐ ┌──────────────────┐ │
│ Registry │ │ Elements │◄─────────────┘
│ (Element Store)│◄──►│ (Tools/Resources │
└─────────────────┘ │ Prompts/etc.) │
└──────────────────┘
Componentes Principais
ServerBuilder: Interface de configuração fluente (Server::make()->...->build())Server: Coordenador central contendo todos os componentes configuradosProtocol: Handler JSON-RPC 2.0 que faz a ponte entre transportes e lógica centralSessionManager: Armazenamento de sessão multi-backend (array, cache, personalizado)Dispatcher: Roteamento de métodos e processamento de requisiçõesRegistry: Armazenamento de elementos com cache inteligente e regras de precedênciaElements: Componentes MCP registrados (Ferramentas, Recursos, Prompts, Modelos)
Opções de Transporte
StdioServerTransport: I/O padrão para execução direta pelo clienteHttpServerTransport: HTTP + Server-Sent Events para integração webStreamableHttpServerTransport: HTTP aprimorado com capacidade de retomada e event sourcing
⚙️ Configuração do Servidor
Configuração Básica
use PhpMcp\Server\Server;
use PhpMcp\Schema\ServerCapabilities;
$server = Server::make()
->withServerInfo('My App Server', '2.1.0')
->withCapabilities(ServerCapabilities::make(
resources: true,
resourcesSubscribe: true,
prompts: true,
tools: true
))
->withPaginationLimit(100)
->build();
Configuração Avançada com Dependências
use Psr\Log\Logger;
use Psr\SimpleCache\CacheInterface;
use Psr\Container\ContainerInterface;
$server = Server::make()
->withServerInfo('Production Server', '1.0.0')
->withLogger($myPsrLogger) // PSR-3 Logger
->withCache($myPsrCache) // PSR-16 Cache
->withContainer($myPsrContainer) // PSR-11 Container
->withSession('cache', 7200) // Cache-backed sessions, 2hr TTL
->withPaginationLimit(50) // Limit list responses
->build();
Opções de Gerenciamento de Sessão
// In-memory sessions (default, not persistent)
->withSession('array', 3600)
// Cache-backed sessions (persistent across restarts)
->withSession('cache', 7200)
// Custom session handler (implement SessionHandlerInterface)
->withSessionHandler(new MyCustomSessionHandler(), 1800)
🎯 Definindo Elementos MCP
O servidor fornece duas formas poderosas de definir elementos MCP: Descoberta Baseada em Atributos (recomendada) e Registro Manual. Ambas podem ser combinadas, com registros manuais tendo precedência.
Tipos de Elementos
- 🔧 Ferramentas: Funções/ações executáveis (ex.:
calculate,send_email,query_database) - 📄 Recursos: Conteúdo/dados estáticos (ex.:
config://settings,file://readme.txt) - 📋 Modelos de Recursos: Recursos dinâmicos com padrões de URI (ex.:
user://{id}/profile) - 💬 Prompts: Iniciadores/modelos de conversa (ex.:
summarize,translate)
1. 🏷️ Descoberta Baseada em Atributos (Recomendada)
Use atributos PHP 8 para marcar métodos ou classes invocáveis como elementos MCP. O servidor os descobrirá via varredura do sistema de arquivos.
use PhpMcp\Server\Attributes\{McpTool, McpResource, McpResourceTemplate, McpPrompt};
class UserManager
{
/**
* Creates a new user account.
*/
#[McpTool(name: 'create_user')]
public function createUser(string $email, string $password, string $role = 'user'): array
{
// Create user logic
return ['id' => 123, 'email' => $email, 'role' => $role];
}
/**
* Get user configuration.
*/
#[McpResource(
uri: 'config://user/settings',
mimeType: 'application/json'
)]
public function getUserConfig(): array
{
return ['theme' => 'dark', 'notifications' => true];
}
/**
* Get user profile by ID.
*/
#[McpResourceTemplate(
uriTemplate: 'user://{userId}/profile',
mimeType: 'application/json'
)]
public function getUserProfile(string $userId): array
{
return ['id' => $userId, 'name' => 'John Doe'];
}
/**
* Generate welcome message prompt.
*/
#[McpPrompt(name: 'welcome_user')]
public function welcomeUserPrompt(string $username, string $role): array
{
return [
['role' => 'user', 'content' => "Create a welcome message for {$username} with role {$role}"]
];
}
}
Processo de Descoberta:
// Build server first
$server = Server::make()
->withServerInfo('My App Server', '1.0.0')
->build();
// Then discover elements
$server->discover(
basePath: __DIR__,
scanDirs: ['src/Handlers', 'src/Services'], // Directories to scan
excludeDirs: ['src/Tests'], // Directories to skip
saveToCache: true // Cache results (default: true)
);
Atributos Disponíveis:
#[McpTool]: Ações executáveis#[McpResource]: Conteúdo estático acessível via URI#[McpResourceTemplate]: Recursos dinâmicos com modelos de URI#[McpPrompt]: Modelos de conversa e geradores de prompts
2. 🔧 Registro Manual
Registre elementos programaticamente usando o ServerBuilder antes de chamar build(). Útil para registro dinâmico, closures ou quando você prefere controle explícito.
use App\Handlers\{EmailHandler, ConfigHandler, UserHandler, PromptHandler};
use PhpMcp\Schema\{ToolAnnotations, Annotations};
$server = Server::make()
->withServerInfo('Manual Registration Server', '1.0.0')
// Register a tool with handler method
->withTool(
[EmailHandler::class, 'sendEmail'], // Handler: [class, method]
name: 'send_email', // Tool name (optional)
description: 'Send email to user', // Description (optional)
annotations: ToolAnnotations::make( // Annotations (optional)
title: 'Send Email Tool'
)
)
// Register invokable class as tool
->withTool(UserHandler::class) // Handler: Invokable class
// Register a closure as tool
->withTool(
function(int $a, int $b): int { // Handler: Closure
return $a + $b;
},
name: 'add_numbers',
description: 'Add two numbers together'
)
// Register a resource with closure
->withResource(
function(): array { // Handler: Closure
return ['timestamp' => time(), 'server' => 'php-mcp'];
},
uri: 'config://runtime/status', // URI (required)
mimeType: 'application/json' // MIME type (optional)
)
// Register a resource template
->withResourceTemplate(
[UserHandler::class, 'getUserProfile'],
uriTemplate: 'user://{userId}/profile' // URI template (required)
)
// Register a prompt with closure
->withPrompt(
function(string $topic, string $tone = 'professional'): array {
return [
['role' => 'user', 'content' => "Write about {$topic} in a {$tone} tone"]
];
},
name: 'writing_prompt' // Prompt name (optional)
)
->build();
O servidor suporta três formatos flexíveis de handlers: [ClassName::class, 'methodName'] para handlers de métodos de classe, InvokableClass::class para handlers de classes invocáveis (classes com método __invoke) e qualquer callable PHP incluindo closures, métodos estáticos como [SomeClass::class, 'staticMethod'] ou nomes de funções. Handlers baseados em classe são resolvidos via contêiner PSR-11 configurado para injeção de dependência. Registros manuais nunca são armazenados em cache e têm precedência sobre elementos descobertos com o mesmo identificador.
[!IMPORTANT] Ao usar closures como handlers, o servidor gera schemas JSON mínimos baseados apenas em type hints PHP, pois não há docblocks ou contexto de classe disponível. Para schemas mais detalhados com restrições de validação, descrições e formatos, você tem duas opções:
- Use o atributo
#[Schema]para geração aprimorada de schema- Forneça um parâmetro
$inputSchemapersonalizado ao registrar ferramentas com->withTool()
🏆 Precedência de Elementos e Descoberta
Regras de Precedência:
- Registros manuais sempre sobrescrevem elementos descobertos/armazenados em cache com o mesmo identificador
- Elementos descobertos são armazenados em cache para desempenho (configurável)
- O cache é invalidado automaticamente em novas execuções de descoberta
Processo de Descoberta:
$server->discover(
basePath: __DIR__,
scanDirs: ['src/Handlers', 'src/Services'], // Scan these directories
excludeDirs: ['tests', 'vendor'], // Skip these directories
force: false, // Force re-scan (default: false)
saveToCache: true // Save to cache (default: true)
);
Comportamento de Cache:
- Apenas elementos descobertos são armazenados em cache (nunca registros manuais)
- O cache é carregado automaticamente durante
build()se disponível - Chamadas
discover()novas limpam e reconstroem o cache - Use
force: truepara ignorar a verificação de descoberta já executada
🚀 Executando o Servidor (Transportes)
O núcleo do servidor é agnóstico em relação ao transporte. Escolha um transporte com base nas suas necessidades de implantação:
1. 📟 Transporte Stdio
Melhor para: Execução direta pelo cliente, ferramentas de linha de comando, implantações simples
use PhpMcp\Server\Transports\StdioServerTransport;
$server = Server::make()
->withServerInfo('Stdio Server', '1.0.0')
->build();
$server->discover(__DIR__, ['src']);
// Create stdio transport (uses STDIN/STDOUT by default)
$transport = new StdioServerTransport();
// Start listening (blocking call)
$server->listen($transport);
Configuração do Cliente:
{
"mcpServers": {
"my-php-server": {
"command": "php",
"args": ["/absolute/path/to/server.php"]
}
}
}
⚠️ Importante: Ao usar transporte stdio, nunca escreva em
STDOUTem seus handlers (useSTDERRpara depuração).STDOUTé reservado para comunicação JSON-RPC.
2. 🌐 Transporte HTTP + Server-Sent Events (Obsoleto)
⚠️ Nota: Este transporte está obsoleto na versão mais recente do protocolo MCP, mas permanece disponível para compatibilidade retroativa. Para novos projetos, use o StreamableHttpServerTransport que fornece recursos aprimorados e melhor conformidade com o protocolo.
Melhor para: Aplicações legadas que exigem compatibilidade retroativa
use PhpMcp\Server\Transports\HttpServerTransport;
$server = Server::make()
->withServerInfo('HTTP Server', '1.0.0')
->withLogger($logger) // Recommended for HTTP
->build();
$server->discover(__DIR__, ['src']);
// Create HTTP transport
$transport = new HttpServerTransport(
host: '127.0.0.1', // MCP protocol prohibits 0.0.0.0
port: 8080, // Port number
mcpPathPrefix: 'mcp' // URL prefix (/mcp/sse, /mcp/message)
);
$server->listen($transport);
Configuração do Cliente:
{
"mcpServers": {
"my-http-server": {
"url": "http://localhost:8080/mcp/sse"
}
}
}
Endpoints:
- Conexão SSE:
GET /mcp/sse - Envio de Mensagens:
POST /mcp/message?clientId={clientId}
3. 🔄 Transporte HTTP Transmissível (Recomendado)
Melhor para: Implantações de produção, servidores MCP remotos, múltiplos clientes, conexões retomáveis
use PhpMcp\Server\Transports\StreamableHttpServerTransport;
$server = Server::make()
->withServerInfo('Streamable Server', '1.0.0')
->withLogger($logger)
->withCache($cache) // Required for resumability
->build();
$server->discover(__DIR__, ['src']);
// Create streamable transport with resumability
$transport = new StreamableHttpServerTransport(
host: '127.0.0.1', // MCP protocol prohibits 0.0.0.0
port: 8080,
mcpPathPrefix: 'mcp',
enableJsonResponse: false, // Use SSE streaming (default)
stateless: false // Enable stateless mode for session-less clients
);
$server->listen($transport);
Modo de Resposta JSON:
A opção enableJsonResponse controla como as respostas são entregues:
false(padrão): Usa streams Server-Sent Events (SSE) para respostas. Melhor para ferramentas que podem levar tempo para processar.true: Retorna respostas JSON imediatas sem abrir streams SSE. Use quando suas ferramentas executam rapidamente e não precisam de streaming.
// For fast-executing tools, enable JSON mode
$transport = new StreamableHttpServerTransport(
host: '127.0.0.1',
port: 8080,
enableJsonResponse: true // Immediate JSON responses
);
Modo Sem Estado:
Para clientes que têm problemas com gerenciamento de sessão, habilite o modo sem estado:
$transport = new StreamableHttpServerTransport(
host: '127.0.0.1',
port: 8080,
stateless: true // Each request is independent
);
No modo sem estado, IDs de sessão são gerados internamente, mas não expostos aos clientes, e cada requisição é tratada como independente, sem estado de sessão persistente.
Recursos:
- Conexões retomáveis - clientes podem reconectar e reproduzir eventos perdidos
- Event sourcing - todos os eventos são armazenados para reprodução
- Modo JSON - respostas opcionais apenas em JSON para ferramentas rápidas
- Gerenciamento aprimorado de sessão - estado de sessão persistente
- Suporte a múltiplos clientes - projetado para clientes concorrentes
- Modo sem estado - operação sem sessão para clientes simples
📋 Geração de Schema e Validação
O servidor gera automaticamente schemas JSON para parâmetros de ferramentas usando um sistema de prioridade sofisticado que combina type hints PHP, informações de docblock e o atributo opcional #[Schema]. Esses schemas gerados são usados tanto para validação de entrada quanto para fornecer informações de schema aos clientes MCP.
Prioridade de Geração de Schema
O servidor segue esta ordem de precedência ao gerar schemas:
- Atributo
#[Schema]comdefinition- Sobrescrita completa do schema (maior precedência) - Atributo
#[Schema]em nível de parâmetro - Aprimoramentos de schema específicos do parâmetro - Atributo
#[Schema]em nível de método - Configuração de schema para todo o método - Type hints PHP + docblocks - Inferência automática do código (menor precedência)
Quando um definition é fornecido no atributo Schema, toda inferência automática é ignorada e a definição completa é usada como está.
Atributos de Schema em Nível de Parâmetro
use PhpMcp\Server\Attributes\{McpTool, Schema};
#[McpTool(name: 'validate_user')]
public function validateUser(
#[Schema(format: 'email')] // PHP already knows it's string
string $email,
#[Schema(
pattern: '^[A-Z][a-z]+$',
description: 'Capitalized name'
)]
string $name,
#[Schema(minimum: 18, maximum: 120)] // PHP already knows it's integer
int $age
): bool {
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}
Schema em Nível de Método
/**
* Process user data with nested validation.
*/
#[McpTool(name: 'create_user')]
#[Schema(
properties: [
'profile' => [
'type' => 'object',
'properties' => [
'name' => ['type' => 'string', 'minLength' => 2],
'age' => ['type' => 'integer', 'minimum' => 18],
'email' => ['type' => 'string', 'format' => 'email']
],
'required' => ['name', 'email']
]
],
required: ['profile']
)]
public function createUser(array $userData): array
{
// PHP type hint provides base 'array' type
// Method-level Schema adds object structure validation
return ['id' => 123, 'status' => 'created'];
}
Sobrescrita Completa de Schema (Apenas em Nível de Método)
#[McpTool(name: 'process_api_request')]
#[Schema(definition: [
'type' => 'object',
'properties' => [
'endpoint' => ['type' => 'string', 'format' => 'uri'],
'method' => ['type' => 'string', 'enum' => ['GET', 'POST', 'PUT', 'DELETE']],
'headers' => [
'type' => 'object',
'patternProperties' => [
'^[A-Za-z0-9-]+$' => ['type' => 'string']
]
]
],
'required' => ['endpoint', 'method']
])]
public function processApiRequest(string $endpoint, string $method, array $headers): array
{
// PHP type hints are completely ignored when definition is provided
// The schema definition above takes full precedence
return ['status' => 'processed', 'endpoint' => $endpoint];
}
⚠️ Importante: A sobrescrita completa da definição de schema raramente deve ser usada. Ela ignora toda inferência automática de schema e exige que você defina todo o schema JSON manualmente. Use apenas se você for bem versado na especificação JSON Schema e tiver requisitos complexos de validação que não podem ser alcançados pelo sistema de prioridade. Na maioria dos casos, os atributos
#[Schema]em nível de parâmetro e método fornecem flexibilidade suficiente.
🎨 Formatação de Valores de Retorno
O servidor formata automaticamente os valores de retorno dos seus handlers em tipos de conteúdo MCP apropriados:
Formatação Automática
// Simple values are auto-wrapped in TextContent
public function getString(): string { return "Hello World"; } // → TextContent
public function getNumber(): int { return 42; } // → TextContent
public function getBool(): bool { return true; } // → TextContent
public function getArray(): array { return ['key' => 'value']; } // → TextContent (JSON)
// Null handling
public function getNull(): ?string { return null; } // → TextContent("(null)")
public function returnVoid(): void { /* no return */ } // → Empty content
Tipos de Conteúdo Avançados
use PhpMcp\Schema\Content\{TextContent, ImageContent, AudioContent, ResourceContent};
public function getFormattedCode(): TextContent
{
return TextContent::code('<?php echo "Hello";', 'php');
}
public function getMarkdown(): TextContent
{
return TextContent::make('# Title\n\nContent here');
}
public function getImage(): ImageContent
{
return ImageContent::make(
data: base64_encode(file_get_contents('image.png')),
mimeType: 'image/png'
);
}
public function getAudio(): AudioContent
{
return AudioContent::make(
data: base64_encode(file_get_contents('audio.mp3')),
mimeType: 'audio/mpeg'
);
}
Tratamento de Arquivos e Streams
// File objects are automatically read and formatted
public function getFileContent(): \SplFileInfo
{
return new \SplFileInfo('/path/to/file.txt'); // Auto-detects MIME type
}
// Stream resources are read completely
public function getStreamContent()
{
$stream = fopen('/path/to/data.json', 'r');
return $stream; // Will be read and closed automatically
}
// Structured resource responses
public function getStructuredResource(): array
{
return [
'text' => 'File content here',
'mimeType' => 'text/plain'
];
// Or for binary data:
// return [
// 'blob' => base64_encode($binaryData),
// 'mimeType' => 'application/octet-stream'
// ];
}
🔄 Processamento em Lote
O servidor lida automaticamente com requisições JSON-RPC em lote:
// Client can send multiple requests in a single HTTP call:
[
{"jsonrpc": "2.0", "id": "1", "method": "tools/call", "params": {...}},
{"jsonrpc": "2.0", "method": "notifications/ping"}, // notification
{"jsonrpc": "2.0", "id": "2", "method": "tools/call", "params": {...}}
]
// Server returns batch response (excluding notifications):
[
{"jsonrpc": "2.0", "id": "1", "result": {...}},
{"jsonrpc": "2.0", "id": "2", "result": {...}}
]
🔧 Recursos Avançados
Provedores de Completude
Provedores de completude permitem que clientes MCP ofereçam sugestões de autocompletar em suas interfaces de usuário. Eles são projetados especificamente para Modelos de Recursos e Prompts para ajudar os usuários a descobrir opções disponíveis para partes dinâmicas, como variáveis de modelo ou argumentos de prompt.
Nota: Ferramentas e recursos podem ser descobertos via comandos MCP padrão (
tools/list,resources/list), portanto provedores de completude não são necessários para eles. Provedores de completude são usados apenas para modelos de recursos (variáveis de URI) e argumentos de prompt.
O atributo #[CompletionProvider] suporta três tipos de fontes de completude:
1. Classes de Provedores Personalizadas
Para lógica de conclusão complexa, implemente a CompletionProviderInterface:
use PhpMcp\Server\Contracts\CompletionProviderInterface;
use PhpMcp\Server\Contracts\SessionInterface;
use PhpMcp\Server\Attributes\{McpResourceTemplate, CompletionProvider};
class UserIdCompletionProvider implements CompletionProviderInterface
{
public function __construct(private DatabaseService $db) {}
public function getCompletions(string $currentValue, SessionInterface $session): array
{
// Dynamic completion from database
return $this->db->searchUsers($currentValue);
}
}
class UserService
{
#[McpResourceTemplate(uriTemplate: 'user://{userId}/profile')]
public function getUserProfile(
#[CompletionProvider(provider: UserIdCompletionProvider::class)] // Class string - resolved from container
string $userId
): array {
return ['id' => $userId, 'name' => 'John Doe'];
}
}
Você também pode passar instâncias de provedores pré-configuradas:
class DocumentService
{
#[McpPrompt(name: 'document_prompt')]
public function generatePrompt(
#[CompletionProvider(provider: new UserIdCompletionProvider($database))] // Pre-configured instance
string $userId,
#[CompletionProvider(provider: $this->categoryProvider)] // Instance from property
string $category
): array {
return [['role' => 'user', 'content' => "Generate document for user {$userId} in {$category}"]];
}
}
2. Conclusões de Lista Simples
Para listas de conclusão estáticas, use o parâmetro values:
use PhpMcp\Server\Attributes\{McpPrompt, CompletionProvider};
class ContentService
{
#[McpPrompt(name: 'content_generator')]
public function generateContent(
#[CompletionProvider(values: ['blog', 'article', 'tutorial', 'guide', 'documentation'])]
string $contentType,
#[CompletionProvider(values: ['beginner', 'intermediate', 'advanced', 'expert'])]
string $difficulty
): array {
return [['role' => 'user', 'content' => "Create a {$difficulty} level {$contentType}"]];
}
}
3. Conclusões Baseadas em Enum
Para classes enum, use o parâmetro enum:
enum Priority: string
{
case LOW = 'low';
case MEDIUM = 'medium';
case HIGH = 'high';
case CRITICAL = 'critical';
}
enum Status // Unit enum (no backing values)
{
case DRAFT;
case PUBLISHED;
case ARCHIVED;
}
class TaskService
{
#[McpTool(name: 'create_task')]
public function createTask(
string $title,
#[CompletionProvider(enum: Priority::class)] // String-backed enum uses values
string $priority,
#[CompletionProvider(enum: Status::class)] // Unit enum uses case names
string $status
): array {
return ['id' => 123, 'title' => $title, 'priority' => $priority, 'status' => $status];
}
}
Registro Manual com Provedores de Conclusão
$server = Server::make()
->withServerInfo('Completion Demo', '1.0.0')
// Using provider class (resolved from container)
->withPrompt(
[DocumentHandler::class, 'generateReport'],
name: 'document_report'
// Completion providers are auto-discovered from method attributes
)
// Using closure with inline completion providers
->withPrompt(
function(
#[CompletionProvider(values: ['json', 'xml', 'csv', 'yaml'])]
string $format,
#[CompletionProvider(enum: Priority::class)]
string $priority
): array {
return [['role' => 'user', 'content' => "Export data in {$format} format with {$priority} priority"]];
},
name: 'export_data'
)
->build();
Resolução de Provedores de Conclusão
O servidor lida automaticamente com a resolução de provedores:
- Strings de classe (
MyProvider::class) → Resolvidas do contêiner PSR-11 com injeção de dependência - Instâncias (
new MyProvider()) → Usadas diretamente como estão - Arrays de valores (
['a', 'b', 'c']) → Automaticamente envolvidos emListCompletionProvider - Classes enum (
MyEnum::class) → Automaticamente envolvidas emEnumCompletionProvider
Importante: Os provedores de conclusão apenas oferecem sugestões aos usuários na interface do cliente MCP. Os usuários ainda podem inserir qualquer valor, então sempre valide os parâmetros em seus manipuladores, independentemente das restrições do provedor de conclusão.
Injeção de Dependência Personalizada
Seus manipuladores de elementos MCP podem usar injeção de dependência via construtor para acessar serviços como bancos de dados, APIs ou outra lógica de negócio. Quando os manipuladores têm dependências no construtor, você deve fornecer um contêiner PSR-11 pré-configurado que contenha essas dependências.
Por padrão, o servidor usa um BasicContainer - uma implementação simples que tenta conectar dependências automaticamente, instanciando classes com construtores sem parâmetros. Para dependências que exigem configuração (como conexões de banco de dados), você pode adicioná-las manualmente ao BasicContainer ou usar um contêiner PSR-11 mais avançado, como PHP-DI ou o contêiner do Laravel.
use Psr\Container\ContainerInterface;
class DatabaseService
{
public function __construct(private \PDO $pdo) {}
#[McpTool(name: 'query_users')]
public function queryUsers(): array
{
$stmt = $this->pdo->query('SELECT * FROM users');
return $stmt->fetchAll();
}
}
// Option 1: Use the basic container and manually add dependencies
$basicContainer = new \PhpMcp\Server\Defaults\BasicContainer();
$basicContainer->set(\PDO::class, new \PDO('sqlite::memory:'));
// Option 2: Use any PSR-11 compatible container (PHP-DI, Laravel, etc.)
$container = new \DI\Container();
$container->set(\PDO::class, new \PDO('mysql:host=localhost;dbname=app', $user, $pass));
$server = Server::make()
->withContainer($basicContainer) // Handlers get dependencies auto-injected
->build();
Assinaturas de Recursos
use PhpMcp\Schema\ServerCapabilities;
$server = Server::make()
->withCapabilities(ServerCapabilities::make(
resourcesSubscribe: true, // Enable resource subscriptions
prompts: true,
tools: true
))
->build();
// In your resource handler, you can notify clients of changes:
#[McpResource(uri: 'file://config.json')]
public function getConfig(): array
{
// When config changes, notify subscribers
$this->notifyResourceChange('file://config.json');
return ['setting' => 'value'];
}
Retomabilidade e Armazenamento de Eventos
Para implantações de produção usando StreamableHttpServerTransport, você pode implementar retomabilidade com event sourcing fornecendo um armazenamento de eventos personalizado:
use PhpMcp\Server\Contracts\EventStoreInterface;
use PhpMcp\Server\Defaults\InMemoryEventStore;
use PhpMcp\Server\Transports\StreamableHttpServerTransport;
// Use the built-in in-memory event store (for development/testing)
$eventStore = new InMemoryEventStore();
// Or implement your own persistent event store
class DatabaseEventStore implements EventStoreInterface
{
public function storeEvent(string $streamId, string $message): string
{
// Store event in database and return unique event ID
return $this->database->insert('events', [
'stream_id' => $streamId,
'message' => $message,
'created_at' => now()
]);
}
public function replayEventsAfter(string $lastEventId, callable $sendCallback): void
{
// Replay events for resumability
$events = $this->database->getEventsAfter($lastEventId);
foreach ($events as $event) {
$sendCallback($event['id'], $event['message']);
}
}
}
// Configure transport with event store
$transport = new StreamableHttpServerTransport(
host: '127.0.0.1',
port: 8080,
eventStore: new DatabaseEventStore() // Enable resumability
);
Manipuladores de Sessão Personalizados
Implemente armazenamento de sessão personalizado criando uma classe que implemente SessionHandlerInterface:
use PhpMcp\Server\Contracts\SessionHandlerInterface;
class DatabaseSessionHandler implements SessionHandlerInterface
{
public function __construct(private \PDO $db) {}
public function read(string $id): string|false
{
$stmt = $this->db->prepare('SELECT data FROM sessions WHERE id = ?');
$stmt->execute([$id]);
$session = $stmt->fetch(\PDO::FETCH_ASSOC);
return $session ? $session['data'] : false;
}
public function write(string $id, string $data): bool
{
$stmt = $this->db->prepare(
'INSERT OR REPLACE INTO sessions (id, data, updated_at) VALUES (?, ?, ?)'
);
return $stmt->execute([$id, $data, time()]);
}
public function destroy(string $id): bool
{
$stmt = $this->db->prepare('DELETE FROM sessions WHERE id = ?');
return $stmt->execute([$id]);
}
public function gc(int $maxLifetime): array
{
$cutoff = time() - $maxLifetime;
$stmt = $this->db->prepare('DELETE FROM sessions WHERE updated_at < ?');
$stmt->execute([$cutoff]);
return []; // Return array of cleaned session IDs if needed
}
}
// Use custom session handler
$server = Server::make()
->withSessionHandler(new DatabaseSessionHandler(), 3600)
->build();
Suporte a Middleware
Tanto HttpServerTransport quanto StreamableHttpServerTransport suportam middleware compatível com PSR-7 para interceptar e modificar requisições e respostas HTTP. O middleware permite extrair funcionalidades comuns como autenticação, registro de logs, tratamento de CORS e validação de requisições em componentes reutilizáveis.
O middleware deve ser um callable PHP válido que aceita um ServerRequestInterface PSR-7 como primeiro argumento e um callable como segundo argumento.
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
use React\Promise\PromiseInterface;
class AuthMiddleware
{
public function __invoke(ServerRequestInterface $request, callable $next)
{
$apiKey = $request->getHeaderLine('Authorization');
if (empty($apiKey)) {
return new Response(401, [], 'Authorization required');
}
$request = $request->withAttribute('user_id', $this->validateApiKey($apiKey));
$result = $next($request);
return match (true) {
$result instanceof PromiseInterface => $result->then(fn($response) => $this->handle($response)),
$result instanceof ResponseInterface => $this->handle($result),
default => $result
};
}
private function handle($response)
{
return $response instanceof ResponseInterface
? $response->withHeader('X-Auth-Provider', 'mcp-server')
: $response;
}
}
$middlewares = [
new AuthMiddleware(),
new LoggingMiddleware(),
function(ServerRequestInterface $request, callable $next) {
$result = $next($request);
return match (true) {
$result instanceof PromiseInterface => $result->then(function($response) {
return $response instanceof ResponseInterface
? $response->withHeader('Access-Control-Allow-Origin', '*')
: $response;
}),
$result instanceof ResponseInterface => $result->withHeader('Access-Control-Allow-Origin', '*'),
default => $result
};
}
];
$transport = new StreamableHttpServerTransport(
host: '127.0.0.1',
port: 8080,
middlewares: $middlewares
);
Considerações Importantes:
- Tratamento de Respostas: O middleware deve lidar tanto com retornos síncronos
ResponseInterfacequanto assíncronosPromiseInterfacede$next($request), já que o ReactPHP opera de forma assíncrona - Padrão Invokable: O padrão recomendado é usar classes invokable com um método
handle()separado para processar respostas, tornando a lógica assíncrona reutilizável - Ordem de Execução: O middleware executa na ordem fornecida, com o último middleware sendo o mais próximo dos seus manipuladores MCP
Configuração de Contexto SSL
Para implantações HTTPS de StreamableHttpServerTransport, configure as opções de contexto SSL:
$sslContext = [
'ssl' => [
'local_cert' => '/path/to/certificate.pem',
'local_pk' => '/path/to/private-key.pem',
'verify_peer' => false,
'allow_self_signed' => true,
]
];
$transport = new StreamableHttpServerTransport(
host: '0.0.0.0',
port: 8443,
sslContext: $sslContext
);
Referência de Contexto SSL: Para opções completas de contexto SSL, consulte a documentação de Opções de Contexto SSL do PHP.
🔍 Tratamento de Erros e Depuração
O servidor fornece capacidades abrangentes de tratamento de erros e depuração:
Tratamento de Exceções
Manipuladores de ferramentas podem lançar qualquer exceção PHP quando ocorrem erros. O servidor converte automaticamente essas exceções em respostas de erro JSON-RPC adequadas para clientes MCP.
#[McpTool(name: 'divide_numbers')]
public function divideNumbers(float $dividend, float $divisor): float
{
if ($divisor === 0.0) {
// Any exception with descriptive message will be sent to client
throw new \InvalidArgumentException('Division by zero is not allowed');
}
return $dividend / $divisor;
}
#[McpTool(name: 'calculate_factorial')]
public function calculateFactorial(int $number): int
{
if ($number < 0) {
throw new \InvalidArgumentException('Factorial is not defined for negative numbers');
}
if ($number > 20) {
throw new \OverflowException('Number too large, factorial would cause overflow');
}
// Implementation continues...
return $this->factorial($number);
}
O servidor converterá essas exceções em respostas de erro JSON-RPC apropriadas que os clientes MCP podem entender e exibir aos usuários.
Registro de Logs e Depuração
use Psr\Log\LoggerInterface;
class DebugAwareHandler
{
public function __construct(private LoggerInterface $logger) {}
#[McpTool(name: 'debug_tool')]
public function debugTool(string $data): array
{
$this->logger->info('Processing debug tool', ['input' => $data]);
// For stdio transport, use STDERR for debug output
fwrite(STDERR, "Debug: Processing data length: " . strlen($data) . "\n");
return ['processed' => true];
}
}
🚀 Implantação em Produção
Como o $server->listen() executa um processo persistente, você pode implantá-lo usando qualquer estratégia que atenda às necessidades da sua infraestrutura. O servidor pode ser implantado em VPS, instâncias em nuvem, contêineres ou qualquer ambiente que suporte processos de longa duração.
Aqui estão duas abordagens populares de implantação a considerar:
Opção 1: VPS com Supervisor + Nginx (Recomendado)
Melhor para: A maioria das implantações em produção, custo-benefício, controle total
# 1. Install your application on VPS
git clone https://github.com/yourorg/your-mcp-server.git /var/www/mcp-server
cd /var/www/mcp-server
composer install --no-dev --optimize-autoloader
# 2. Install Supervisor
sudo apt-get install supervisor
# 3. Create Supervisor configuration
sudo nano /etc/supervisor/conf.d/mcp-server.conf
Configuração do Supervisor:
[program:mcp-server]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/mcp-server/server.php --transport=http --host=127.0.0.1 --port=8080
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=1
redirect_stderr=true
stdout_logfile=/var/log/mcp-server.log
stdout_logfile_maxbytes=10MB
stdout_logfile_backups=3
Configuração do Nginx com SSL:
# /etc/nginx/sites-available/mcp-server
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name mcp.yourdomain.com;
# SSL configuration
ssl_certificate /etc/letsencrypt/live/mcp.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mcp.yourdomain.com/privkey.pem;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# MCP Server proxy
location / {
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Important for SSE connections
proxy_buffering off;
proxy_cache off;
proxy_pass http://127.0.0.1:8080/;
}
}
# Redirect HTTP to HTTPS
server {
listen 80;
listen [::]:80;
server_name mcp.yourdomain.com;
return 301 https://$server_name$request_uri;
}
Iniciar Serviços:
# Enable and start supervisor
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start mcp-server:*
# Enable and start nginx
sudo systemctl enable nginx
sudo systemctl restart nginx
# Check status
sudo supervisorctl status
Configuração do Cliente:
{
"mcpServers": {
"my-server": {
"url": "https://mcp.yourdomain.com/mcp"
}
}
}
Opção 2: Implantação com Docker
Melhor para: Ambientes containerizados, Kubernetes, plataformas em nuvem
Dockerfile de Produção:
FROM php:8.3-fpm-alpine
# Install system dependencies
RUN apk --no-cache add \
nginx \
supervisor \
&& docker-php-ext-enable opcache
# Install PHP extensions for MCP
RUN docker-php-ext-install pdo_mysql pdo_sqlite opcache
# Create application directory
WORKDIR /var/www/mcp
# Copy application code
COPY . /var/www/mcp
COPY docker/nginx.conf /etc/nginx/nginx.conf
COPY docker/supervisord.conf /etc/supervisord.conf
COPY docker/php.ini /usr/local/etc/php/conf.d/production.ini
# Install Composer dependencies
RUN composer install --no-dev --optimize-autoloader --no-interaction
# Set permissions
RUN chown -R www-data:www-data /var/www/mcp
# Expose port
EXPOSE 80
# Start supervisor
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"]
docker-compose.yml:
services:
mcp-server:
build: .
ports:
- "8080:80"
environment:
- MCP_ENV=production
- MCP_LOG_LEVEL=info
volumes:
- ./storage:/var/www/mcp/storage
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
# Optional: Add database if needed
database:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: secure_password
MYSQL_DATABASE: mcp_server
volumes:
- mysql_data:/var/lib/mysql
restart: unless-stopped
volumes:
mysql_data:
Boas Práticas de Segurança
- Configuração de Firewall:
# Only allow necessary ports
sudo ufw allow ssh
sudo ufw allow 80
sudo ufw allow 443
sudo ufw deny 8080 # MCP port should not be publicly accessible
sudo ufw enable
- Configuração SSL/TLS:
# Install Certbot for Let's Encrypt
sudo apt install certbot python3-certbot-nginx
# Generate SSL certificate
sudo certbot --nginx -d mcp.yourdomain.com
📚 Exemplos e Casos de Uso
Explore exemplos abrangentes no diretório examples/:
Exemplos Disponíveis
01-discovery-stdio-calculator/- Calculadora básica stdio com descoberta de atributos02-discovery-http-userprofile/- Servidor HTTP com gerenciamento de perfil de usuário03-manual-registration-stdio/- Padrões de registro manual de elementos04-combined-registration-http/- Combinando elementos manuais e descobertos05-stdio-env-variables/- Tratamento de variáveis de ambiente06-custom-dependencies-stdio/- Injeção de dependência com gerenciamento de tarefas07-complex-tool-schema-http/- Exemplos avançados de validação de esquema08-schema-showcase-streamable/- Demonstração abrangente de recursos de esquema
Executando Exemplos
# Navigate to an example directory
cd examples/01-discovery-stdio-calculator/
# Make the server executable
chmod +x server.php
# Run the server (or configure it in your MCP client)
./server.php
🚧 Migração da v2.x
Se estiver migrando da versão 2.x, observe estas mudanças principais:
Atualizações de Esquema
- Usa o pacote
php-mcp/schemapara DTOs em vez de classes internas - Tipos de conteúdo movidos para o namespace
PhpMcp\Schema\Content\* - Assinaturas de métodos atualizadas para melhor segurança de tipos
Gerenciamento de Sessão
- Novo gerenciamento de sessão com múltiplos backends
- Use
->withSession()ou->withSessionHandler()para configuração - Sessões agora são persistentes entre reconexões (com backend de cache)
Mudanças de Transporte
- Novo
StreamableHttpServerTransportcom retomabilidade - Tratamento de erros aprimorado e event sourcing
- Melhor processamento de requisições em lote
🧪 Testes
# Install development dependencies
composer install --dev
# Run the test suite
composer test
# Run tests with coverage (requires Xdebug)
composer test:coverage
# Run code style checks
composer lint
🤝 Contribuindo
Aceitamos contribuições! Consulte CONTRIBUTING.md para diretrizes.
📄 Licença
A Licença MIT (MIT). Consulte LICENSE para detalhes.
🙏 Agradecimentos
- Construído sobre a especificação Model Context Protocol
- Alimentado por ReactPHP para operações assíncronas
- Usa padrões PSR para máxima interoperabilidade