1. Go to this page and download the library: Download php-mcp/server library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
php-mcp / server example snippets
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);
}
}
#!/usr/bin/env php
declare(strict_types=1);
orts\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);
}
use PhpMcp\Server\Contracts\CompletionProviderInterface;
use PhpMcp\Server\Contracts\SessionInterface;
use PhpMcp\Server\Attributes\{McpResourceTemplate, CompletionProvider};
class UserIdCompletionProvider implements CompletionProviderInterface
{
public function getCompletions(string $currentValue, SessionInterface $session): array
{
// Return completion suggestions based on current input
$allUsers = ['user_1', 'user_2', 'user_3', 'admin_user'];
// Filter based on what user has typed so far
return array_filter($allUsers, fn($user) => str_starts_with($user, $currentValue));
}
}
class UserService
{
#[McpResourceTemplate(uriTemplate: 'user://{userId}/profile')]
public function getUserProfile(
#[CompletionProvider(UserIdCompletionProvider::class)]
string $userId
): array {
// Always validate input even with completion providers
// Users can still pass any value regardless of completion suggestions
if (!$this->isValidUserId($userId)) {
throw new \InvalidArgumentException('Invalid user ID provided');
}
return ['id' => $userId, 'name' => 'John Doe'];
}
}
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();
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'];
}
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
);
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();
#[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);
}
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];
}
}
dockerfile
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"]
bash
# 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
Loading please wait ...
Before you can download the PHP files, the dependencies should be resolved. This can take some minutes. Please be patient.