PHP MCP MCP server

PHP implementation that enables PHP applications to expose methods as tools, prompts, and resources through attribute-based annotations with support for multiple transport handlers.
Back to servers
Provider
Kyrian Obikwelu
Release date
Mar 02, 2025
Language
PHP
Stats
310 stars

The PHP MCP Server provides a server-side implementation of the Model Context Protocol (MCP) for PHP applications. It allows you to expose parts of your application as standardized MCP Tools, Resources, and Prompts that AI assistants like Claude or Cursor IDE can interact with using the MCP standard.

Installation

composer require php-mcp/server

Quick Start: Stdio Server with Discovery

1. Define Your MCP Element

Create src/MyMcpElements.php:

<?php
namespace App;

use PhpMcp\Server\Attributes\McpTool;

class MyMcpElements
{
    /**
     * Adds two numbers together.
     * @param int $a The first number.
     * @param int $b The second number.
     * @return int The sum.
     */
    #[McpTool(name: 'simple_adder')]
    public function add(int $a, int $b): int
    {
        fwrite(STDERR, "Executing simple_adder with a=$a, b=$b\n");
        return $a + $b;
    }
}

2. Create the Server Script

Create 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 {
    // 1. Build the Server configuration
    $server = Server::make()
        ->withServerInfo('My Discovery Server', '1.0.2')
        ->build();

    // 2. Explicitly run discovery
    $server->discover(
        basePath: __DIR__,
        scanDirs: ['src'],
    );

    // 3. Create the Stdio Transport
    $transport = new StdioServerTransport();

    // 4. Start Listening (BLOCKING call)
    $server->listen($transport);

    exit(0);

} catch (\Throwable $e) {
    fwrite(STDERR, "[MCP SERVER CRITICAL ERROR]\n" . $e . "\n");
    exit(1);
}

3. Configure Your MCP Client

Instruct your MCP client (e.g., Cursor, Claude Desktop) to use the stdio transport:

// Example: .cursor/mcp.json
{
    "mcpServers": {
        "my-php-stdio": {
            "command": "php",
            "args": ["/full/path/to/your/project/mcp-server.php"]
        }
    }
}

Defining MCP Elements

You can define MCP elements using either attributes or manual registration.

Using Attributes

Decorate public, non-static methods or invokable classes with #[Mcp*] attributes:

use PhpMcp\Server\Attributes\McpTool;
use PhpMcp\Server\Attributes\McpResource;
use PhpMcp\Server\Attributes\McpResourceTemplate;
use PhpMcp\Server\Attributes\McpPrompt;

// Tool example
#[McpTool(name: 'add_numbers')]
public function addNumbers(int $a, int $b): int
{
    return $a + $b;
}

// Resource example
#[McpResource(uri: 'status://system/load', mimeType: 'text/plain')]
public function getSystemLoad(): string
{
    return file_get_contents('/proc/loadavg');
}

// Resource template example
#[McpResourceTemplate(uriTemplate: 'user://{userId}/profile', mimeType: 'application/json')]
public function getUserProfile(string $userId): array
{
    return ['id' => $userId, 'name' => 'User ' . $userId];
}

// Prompt example
#[McpPrompt(name: 'summarize')]
public function generateSummaryPrompt(string $textToSummarize): array
{
    return [
        ['role' => 'user', 'content' => "Summarize the following text:\n\n{$textToSummarize}"],
    ];
}

Manual Registration

Use withTool, withResource, withResourceTemplate, withPrompt on the ServerBuilder:

use App\Handlers\MyToolHandler;
use App\Handlers\MyResourceHandler;

$server = Server::make()
    ->withServerInfo('My Manual Server', '1.0.0')
    ->withTool(
        [MyToolHandler::class, 'processData'], // Handler: [class, method]
        'data_processor'                       // MCP Name (Optional)
    )
    ->withResource(
        MyResourceHandler::class,              // Handler: Invokable class
        'config://app/name'                    // URI (Required)
    )
    ->build();

Server Configuration

Configure the server using the builder pattern:

$server = Server::make()
    // Required server identity
    ->withServerInfo('My MCP Server', '1.0.0')
    
    // Optional dependencies
    ->withLogger($psr3Logger)
    ->withCache($psr16Cache, $ttl = 3600)
    ->withContainer($psr11Container)
    ->withLoop($reactPhpLoop)
    
    // Optional settings
    ->withCapabilities(Capabilities::forServer(...))
    ->withPaginationLimit(50)
    
    // Manual element registration
    ->withTool(...)
    ->withResource(...)
    
    // Build the server instance
    ->build();

Running the Server

Stdio Transport

Ideal for servers launched directly by an MCP client (like Cursor):

use PhpMcp\Server\Transports\StdioServerTransport;

// ... build $server ...

$transport = new StdioServerTransport();
$server->listen($transport); // Blocks until the transport is closed

Warning: When using Stdio transport, avoid writing to STDOUT with echo, print, etc. Use STDERR for logging: fwrite(STDERR, "Debug message\n");

HTTP Transport (HTTP+SSE)

For web-based server implementation:

use PhpMcp\Server\Transports\HttpServerTransport;

// ... build $server ...

$transport = new HttpServerTransport(
    host: '127.0.0.1',   // Listen address
    port: 8080,          // Port to listen on
    mcpPathPrefix: 'mcp' // Base path for endpoints
);

$server->listen($transport); // Blocks, starting the HTTP server

The HTTP transport provides two endpoints:

  • SSE: GET /{mcpPathPrefix}/sse - Client connects here
  • Messages: POST /{mcpPathPrefix}/message?clientId={clientId} - Client sends requests here

Discovery and Caching

To find and register elements marked with attributes:

$server->discover(
    basePath: __DIR__,        // Project root
    scanDirs: ['src'],        // Directories to scan
    excludeDirs: ['tests'],   // Directories to skip
    force: false,             // Force re-scan
    saveToCache: true         // Save discovered elements to cache
);

Return Value Formatting

For Tools

// String, int, float, bool - wrapped in TextContent
#[McpTool]
public function returnString(): string {
    return "Hello world";
}

// Arrays/objects - JSON-encoded and wrapped in TextContent
#[McpTool]
public function returnArray(): array {
    return ['status' => 'success', 'data' => ['id' => 123]];
}

// Direct Content objects for full control
use PhpMcp\Server\JsonRpc\Contents\TextContent;

#[McpTool]
public function returnFormatted(): TextContent {
    return TextContent::code('echo "Hello";', 'php');
}

For Resources

// String content
#[McpResource(uri: 'text://example', mimeType: 'text/plain')]
public function getText(): string {
    return "This is plain text";
}

// JSON content
#[McpResource(uri: 'config://app', mimeType: 'application/json')]
public function getConfig(): array {
    return ['name' => 'MyApp', 'version' => '1.0.0'];
}

// File content
#[McpResource(uri: 'file://example')]
public function getFile(): SplFileInfo {
    return new SplFileInfo('/path/to/file.txt');
}

For Prompts

use PhpMcp\Server\JsonRpc\Contents\PromptMessage;

#[McpPrompt(name: 'translation')]
public function translationPrompt(string $text, string $targetLanguage): array {
    return [
        PromptMessage::user("Translate the following text to {$targetLanguage}:\n\n{$text}"),
        PromptMessage::assistant("Here's the translation:")
    ];
}

// Or using the simpler array format:
#[McpPrompt(name: 'translation_alt')]
public function translationPromptAlt(string $text, string $targetLanguage): array {
    return [
        ['role' => 'user', 'content' => "Translate to {$targetLanguage}:\n\n{$text}"],
        ['role' => 'assistant', 'content' => "Here's the translation:"]
    ];
}

Enhanced Schema Validation

Use the Schema attribute for enhanced input validation:

use PhpMcp\Server\Attributes\Schema;

#[McpTool]
public function validateUser(
    #[Schema(format: 'email')] 
    string $email,
    
    #[Schema(minItems: 2, uniqueItems: true)]
    array $tags,
    
    #[Schema(minimum: 1, maximum: 100)]
    int $age
): bool {
    // Implementation
}

How to add this MCP server to Cursor

There are two ways to add an MCP server to Cursor. The most common way is to add the server globally in the ~/.cursor/mcp.json file so that it is available in all of your projects.

If you only need the server in a single project, you can add it to the project instead by creating or adding it to the .cursor/mcp.json file.

Adding an MCP server to Cursor globally

To add a global MCP server go to Cursor Settings > MCP and click "Add new global MCP server".

When you click that button the ~/.cursor/mcp.json file will be opened and you can add your server like this:

{
    "mcpServers": {
        "cursor-rules-mcp": {
            "command": "npx",
            "args": [
                "-y",
                "cursor-rules-mcp"
            ]
        }
    }
}

Adding an MCP server to a project

To add an MCP server to a project you can create a new .cursor/mcp.json file or add it to the existing one. This will look exactly the same as the global MCP server example above.

How to use the MCP server

Once the server is installed, you might need to head back to Settings > MCP and click the refresh button.

The Cursor agent will then be able to see the available tools the added MCP server has available and will call them when it needs to.

You can also explictly ask the agent to use the tool by mentioning the tool name and describing what the function does.

Want to 10x your AI skills?

Get a free account and learn to code + market your apps using AI (with or without vibes!).

Nah, maybe later