MCP Server implementation for PHP 5.6+

Maintainers

Package info

github.com/vluzrmos/mcp-php

pkg:composer/vluzrmos/mcp

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

dev-main 2026-03-14 13:30 UTC

This package is auto-updated.

Last update: 2026-03-14 13:30:52 UTC


README

PHP 5.6+ License: MIT

A Model Context Protocol (MCP) server implementation for PHP 5.6+, built on top of a JSON-RPC 2.0 foundation (vluzrmos/jsonrpc). It enables AI models to interact with PHP-based tools through the standardized MCP protocol.

Table of Contents

Requirements

  • PHP >= 5.6
  • json extension
  • curl extension

Installation

composer require vluzrmos/mcp

Quick Start

Minimal MCP Server

<?php

require 'vendor/autoload.php';

use Vluzrmos\MCP\Server;
use Vluzrmos\MCP\ServerInfo;
use Vluzrmos\MCP\Capability;
use Vluzrmos\MCP\Tools\ToolManager;
use Vluzrmos\MCP\Tools\AbstractTool;
use Vluzrmos\MCP\Tools\ToolResult;
use Vluzrmos\MCP\Utils\JsonSchema;
use Vluzrmos\MCP\Methods\ServerInitialize;
use Vluzrmos\MCP\Methods\ToolsList;
use Vluzrmos\MCP\Methods\ToolsCall;
use Vluzrmos\MCP\Methods\ResourcesList;
use Vluzrmos\MCP\Methods\ResourcesRead;
use Vluzrmos\MCP\Methods\PromptsList;
use Vluzrmos\MCP\Methods\PromptsGet;
use Vluzrmos\MCP\Methods\Ping;
use Vluzrmos\MCP\Methods\NotificationsInitialized;
use Vluzrmos\MCP\Resources\ResourceManager;
use Vluzrmos\MCP\Prompts\PromptManager;

// 1. Create managers and register tools, resources, prompts
$toolManager = new ToolManager();
$toolManager->registerTool(new MyTool());

$resourceManager = new ResourceManager();
$resourceManager->registerResource(new MyResource());

$promptManager = new PromptManager();
$promptManager->registerPrompt(new MyPrompt());

// 2. Create the server
$server = new Server(new ServerInfo('My MCP Server', '1.0.0'));
$server->addCapability(Capability::tools());
$server->addCapability(Capability::resources());
$server->addCapability(Capability::prompts());

// 3. Register MCP methods
$server->addMethod(new ServerInitialize($server));
$server->addMethod(new NotificationsInitialized());
$server->addMethod(new Ping());
$server->addMethod(new ToolsList($toolManager));
$server->addMethod(new ToolsCall($toolManager));
$server->addMethod(new ResourcesList($resourceManager));
$server->addMethod(new ResourcesRead($resourceManager));
$server->addMethod(new PromptsList($promptManager));
$server->addMethod(new PromptsGet($promptManager));

// 4. Process request
$input = file_get_contents('php://input');
$response = $server->handle($input);

header('Content-Type: application/json');
echo json_encode($response);

CLI Usage

$server = new Server(new ServerInfo('CLI Server', '1.0.0'));
// ... register methods ...

// Send a JSON-RPC request directly
$response = $server->handle('{"jsonrpc":"2.0","method":"initialize","id":1}');
echo json_encode($response, JSON_PRETTY_PRINT);

Architecture

Server
├── JsonRpcServer (vluzrmos/jsonrpc)
├── ServerInfo
├── SessionManagerInterface → SessionManager / LaravelSessionManager → Session
├── Capability → CapabilityOptions
└── Methods:
    ├── ServerInitialize ← Server
    ├── ToolsList ← ToolManager
    ├── ToolsCall ← ToolManager → Tool → ToolResult → Content
    ├── ResourcesList ← ResourceManager
    ├── ResourcesRead ← ResourceManager → Resource → Contents
    ├── PromptsList ← PromptManager
    ├── PromptsGet ← PromptManager → Prompt → PromptMessage → Content
    ├── Ping
    └── NotificationsInitialized

HTTP Layer:
├── Response ← HeaderBag
├── JsonResponse ← Response
├── BinaryFileResponse ← Response
└── Stream (SSE) ← Response ← Payload

Content Types:
├── Text ← Content
├── Image ← Base64Encoded ← Content
├── Audio ← Base64Encoded ← Content
├── ResourceLink ← Content
├── EmbeddedResource ← ResourceLink
└── Annotations

Resources:
├── Resource (interface) ← Concerns
├── AbstractResource ← Resource
└── ResourceManager (registry)

Prompts:
├── Prompt (interface) ← Concerns
├── AbstractPrompt ← Prompt
├── PromptArgument
├── PromptMessage
└── PromptManager (registry)

Core Components

Component Description
Server Main MCP server — wraps JSON-RPC
Tools/ Tool system with AbstractTool base class and ToolManager
Resources/ Resource system with AbstractResource base class and ResourceManager
Prompts/ Prompt system with AbstractPrompt, PromptArgument, PromptMessage and PromptManager
Methods/ JSON-RPC method implementations for the MCP protocol
Content/ Content types (Text, Image, Audio, ResourceLink, etc.)
Http/ HTTP layer with JSON responses and Server-Sent Events
Session / SessionManagerInterface MCP session management (swappable implementations)

Creating Tools

Tools are the main extension point of the MCP server. Extend AbstractTool to create one:

use Vluzrmos\MCP\Tools\AbstractTool;
use Vluzrmos\MCP\Tools\ToolResult;
use Vluzrmos\MCP\Utils\JsonSchema;

class SearchTool extends AbstractTool
{
    public function getName()
    {
        return 'search';
    }

    public function getTitle()
    {
        return 'Search Tool';
    }

    public function getDescription()
    {
        return 'Performs a search with the given query.';
    }

    public function getInputSchema()
    {
        return new JsonSchema('object', [
            'query' => ['type' => 'string', 'description' => 'Search term'],
            'limit' => ['type' => 'integer', 'description' => 'Max number of results']
        ], ['query']); // 'query' is required
    }

    public function getOutputSchema()
    {
        return new JsonSchema('object', [
            'results' => ['type' => 'array'],
            'total' => ['type' => 'integer']
        ]);
    }

    public function execute(array $arguments)
    {
        $query = $arguments['query'];
        $limit = isset($arguments['limit']) ? $arguments['limit'] : 10;

        // Your logic here...
        $results = performSearch($query, $limit);

        return ToolResult::structured([
            'results' => $results,
            'total' => count($results)
        ]);
    }
}

Registering Tools

$toolManager = new ToolManager();
$toolManager->registerTool(new SearchTool());
$toolManager->registerTool(new AnotherTool());

// Check registered tools
$toolManager->hasTool('search');       // true
$toolManager->listTools();             // ['search', 'another']
$toolManager->executeTool('search', ['query' => 'php']); // execute directly

Tool Return Types

ToolsCall automatically converts the tool's return value:

Return Behavior
string, int, float Wrapped in ToolResult::content(new Text(...))
null ToolResult::content(new Text(''))
ToolResult Returned as-is
array / object with outputSchema ToolResult::structured(...)
array / object without outputSchema ToolResult::content(new Text(json_encode(...)))

ToolResult Factory Methods

use Vluzrmos\MCP\Tools\ToolResult;
use Vluzrmos\MCP\Content\Text;

// Simple content
ToolResult::content(new Text('Operation result'));

// Structured content (JSON)
ToolResult::structured(['key' => 'value', 'data' => [1, 2, 3]]);

// Error
ToolResult::error('Failed to process the request');

Error Handling in Tools

use Vluzrmos\MCP\Exceptions\ToolExecutionException;

class ErrorProneToool extends AbstractTool
{
    public function execute(array $arguments)
    {
        // ToolExecutionException: message is sent to the client
        throw new ToolExecutionException('Invalid parameter X');

        // Any other exception: generic message "Tool execution failed."
        throw new \RuntimeException('Internal error');
    }
}

Creating Resources

Resources expose data that AI models can read. Extend AbstractResource to create one:

use Vluzrmos\MCP\Resources\AbstractResource;

class ServerInfoResource extends AbstractResource
{
    public function getUri()
    {
        return 'info://server';
    }

    public function getName()
    {
        return 'Server Info';
    }

    public function getDescription()
    {
        return 'Returns information about the current server environment.';
    }

    public function getMimeType()
    {
        return 'application/json';
    }

    public function read()
    {
        return [
            [
                'uri' => $this->getUri(),
                'mimeType' => $this->getMimeType(),
                'text' => json_encode([
                    'php_version' => PHP_VERSION,
                    'server_time' => date('Y-m-d\TH:i:sP'),
                    'timezone' => date_default_timezone_get(),
                ])
            ]
        ];
    }
}

Registering Resources

use Vluzrmos\MCP\Resources\ResourceManager;

$resourceManager = new ResourceManager();
$resourceManager->registerResource(new ServerInfoResource());

// Check registered resources
$resourceManager->hasResource('info://server');    // true
$resourceManager->listResources();                 // ['info://server']
$resourceManager->readResource('info://server');   // reads content directly

Resource Interface

The Resource interface requires:

Method Returns Description
getUri() string Unique URI identifying the resource
getName() string Human-readable name
getDescription() string|null Optional description
getMimeType() string|null Optional MIME type
read() array Array of content objects with uri, mimeType, and text or blob

Wiring Resources to the Server

use Vluzrmos\MCP\Capability;
use Vluzrmos\MCP\Methods\ResourcesList;
use Vluzrmos\MCP\Methods\ResourcesRead;

$server->addCapability(Capability::resources());
$server->addMethod(new ResourcesList($resourceManager));
$server->addMethod(new ResourcesRead($resourceManager));

Creating Prompts

Prompts define reusable prompt templates that AI models can discover and use. Extend AbstractPrompt to create one:

use Vluzrmos\MCP\Prompts\AbstractPrompt;
use Vluzrmos\MCP\Prompts\PromptArgument;
use Vluzrmos\MCP\Prompts\PromptMessage;
use Vluzrmos\MCP\Content\Text;

class CodeReviewPrompt extends AbstractPrompt
{
    public function getName()
    {
        return 'code_review';
    }

    public function getDescription()
    {
        return 'Generates a code review prompt for the given code snippet.';
    }

    public function getArguments()
    {
        return [
            new PromptArgument('code', 'The code snippet to review.', true),
            new PromptArgument('language', 'The programming language.', false)
        ];
    }

    public function render(array $arguments)
    {
        $code = isset($arguments['code']) ? $arguments['code'] : '';
        $language = isset($arguments['language']) ? $arguments['language'] : 'unknown';

        return [
            PromptMessage::user(new Text(
                "Please review the following {$language} code:\n\n```{$language}\n{$code}\n```"
            )),
            PromptMessage::assistant(new Text(
                "I'll analyze the {$language} code you provided."
            ))
        ];
    }
}

Registering Prompts

use Vluzrmos\MCP\Prompts\PromptManager;

$promptManager = new PromptManager();
$promptManager->registerPrompt(new CodeReviewPrompt());

// Check registered prompts
$promptManager->hasPrompt('code_review');           // true
$promptManager->listPrompts();                      // ['code_review']
$promptManager->renderPrompt('code_review', [       // render directly
    'code' => '$x = 1;',
    'language' => 'php'
]);

PromptArgument

use Vluzrmos\MCP\Prompts\PromptArgument;

$arg = new PromptArgument(
    'code',                    // name (required)
    'The code to review',      // description (optional)
    true                       // required (default: false)
);

$arg->getName();        // 'code'
$arg->getDescription(); // 'The code to review'
$arg->isRequired();     // true

PromptMessage

PromptMessage wraps a role (user or assistant) and a Content object:

use Vluzrmos\MCP\Prompts\PromptMessage;
use Vluzrmos\MCP\Content\Text;

$userMsg = PromptMessage::user(new Text('Hello'));
$assistantMsg = PromptMessage::assistant(new Text('Hi there!'));

$userMsg->getRole();    // 'user'
$userMsg->getContent(); // Text instance

Wiring Prompts to the Server

use Vluzrmos\MCP\Capability;
use Vluzrmos\MCP\Methods\PromptsList;
use Vluzrmos\MCP\Methods\PromptsGet;

$server->addCapability(Capability::prompts());
$server->addMethod(new PromptsList($promptManager));
$server->addMethod(new PromptsGet($promptManager));

Content Types

All types extend Vluzrmos\MCP\Content\Content and implement JsonSerializable.

Text

use Vluzrmos\MCP\Content\Text;

$text = new Text('Hello, world!');
$text->getText(); // 'Hello, world!'

Image

use Vluzrmos\MCP\Content\Image;

$image = new Image(base64_encode($imageData), 'image/png');
$image = new Image($base64, 'image/jpeg');

Audio

use Vluzrmos\MCP\Content\Audio;

$audio = new Audio(base64_encode($audioData), 'audio/wav');
$audio = new Audio($base64, 'audio/mp3');

ResourceLink

use Vluzrmos\MCP\Content\ResourceLink;

$link = new ResourceLink(
    'file:///path/to/document.pdf',
    'Document',
    'A PDF document',
    'application/pdf'
);

EmbeddedResource

use Vluzrmos\MCP\Content\EmbeddedResource;

$resource = new EmbeddedResource(
    'file:///data/config.json',
    'Configuration',
    'Configuration file',
    'application/json'
);

Annotations

Annotations can be attached to any content:

use Vluzrmos\MCP\Content\Text;
use Vluzrmos\MCP\Content\Annotations;

$annotations = new Annotations();
$annotations->setAudience(['user', 'assistant']);
$annotations->setPriority(0.8);
$annotations->setLastModified('2025-01-01T00:00:00Z');

$text = new Text('Annotated content', $annotations);

Sessions & Login

The MCP protocol uses sessions to maintain state across multiple requests. A session is created during the initialize handshake and tracked via the Mcp-Session-Id HTTP header.

How It Works

  1. Client sends initialize — the ServerInitialize method creates a new Session with a UUID v4 identifier and stores it in SessionManager.
  2. Server responds — the response includes the Mcp-Session-Id header with the session ID.
  3. Client sends subsequent requests — includes the Mcp-Session-Id header so the server can resume the session.
  4. Server resolves the session — looks up the session by ID, updates its last activity timestamp, and restores context.

Session Lifecycle Diagram

Client                                    Server
  │                                          │
  ├─── initialize ──────────────────────────►│  → Creates Session (UUID v4)
  │                                          │  → Stores in SessionManager
  │◄────── response ────────────────────────┤  ← Mcp-Session-Id: <uuid>
  │       + Mcp-Session-Id header            │
  │                                          │
  ├─── tools/list ──────────────────────────►│  → Resolves Session from header
  │    Mcp-Session-Id: <uuid>                │  → session.touch()
  │◄────── response ────────────────────────┤
  │                                          │
  ├─── tools/call ──────────────────────────►│  → Same session context
  │    Mcp-Session-Id: <uuid>                │
  │◄────── response ────────────────────────┤
  │                                          │

Automatic Session Creation

When a client sends the initialize method, a session is created automatically:

$server = new Server();
$server->addMethod(new ServerInitialize($server));

// Client sends initialize with optional clientInfo
$server->handle(json_encode([
    'jsonrpc' => '2.0',
    'method' => 'initialize',
    'id' => 1,
    'params' => [
        'clientInfo' => ['name' => 'MyClient', 'version' => '1.0']
    ]
]));

// Session is now available
$session = $server->getCurrentSession();
$session->getId();         // UUID v4, e.g. "550e8400-e29b-41d4-a716-446655440000"
$session->getClientInfo(); // ['name' => 'MyClient', 'version' => '1.0']

Full HTTP Server with Session Handling

This is the recommended pattern for an HTTP MCP server with session persistence:

use Vluzrmos\MCP\Server;
use Vluzrmos\MCP\Http\Headers\HeaderBag;
use Vluzrmos\MCP\Http\JsonResponse;

$server = new Server();
// ... register methods ...

$headers = HeaderBag::fromServer();
$input = file_get_contents('php://input');

// 1. Resolve existing session from incoming header
$sessionId = $headers->get('Mcp-Session-Id');
if ($sessionId) {
    $session = $server->getSessionManager()->get($sessionId);
    if ($session) {
        $session->touch();                    // Update last activity
        $server->setCurrentSession($session); // Restore session context
    }
}

// 2. Handle the JSON-RPC request (may create a new session on "initialize")
$mcpResponse = $server->handle($input);

// 3. Include session ID in the response headers
$currentSession = $server->getCurrentSession();
$currentSessionId = $currentSession ? $currentSession->getId() : $sessionId;

$responseHeaders = new HeaderBag([
    'MCP-Protocol-Version' => $server->getProtocolVersion(),
]);

if ($currentSessionId) {
    $responseHeaders->set('Mcp-Session-Id', $currentSessionId);
}

// 4. Send the response
$response = new JsonResponse($mcpResponse, 200, $responseHeaders);
$response->send();

Manual Session Management

You can interact with the SessionManager directly:

use Vluzrmos\MCP\SessionManager\SessionManager;

$manager = new SessionManager();

// Create a session
$session = $manager->create(['name' => 'MyClient']);

// Look up and verify
$manager->has($session->getId());  // true
$manager->get($session->getId());  // Session instance
$manager->count();                 // 1
$manager->all();                   // ['id' => Session, ...]

// Update activity timestamp
$session->touch();

// Destroy a session
$manager->destroy($session->getId());

Session Object API

$session->getId();             // string — UUID v4
$session->getClientInfo();     // array — client info from initialize
$session->setClientInfo([...]); // self — update client info
$session->getCreatedAt();      // float — microtime of creation
$session->getLastActivityAt(); // float — microtime of last touch()
$session->touch();             // self — update lastActivityAt to now
$session->toArray();           // array — full session data
Session::restore($array);      // static — restore a Session from an array

SessionManagerInterface

The session management is driven by the SessionManagerInterface, allowing you to swap the default in-memory implementation for any persistence backend:

interface SessionManagerInterface
{
    public function create(array $clientInfo = []);  // Session
    public function get($id);                        // Session|null
    public function has($id);                        // bool
    public function destroy($id);                    // bool
    public function all();                           // array<string, Session>
    public function count();                         // int
}

The default SessionManager implements this interface and stores sessions in memory — ideal for single-process servers or testing.

Custom Session Manager

You can create your own implementation for any persistence backend:

use Vluzrmos\MCP\SessionManager\SessionManagerInterface;
use Vluzrmos\MCP\Session;

class RedisSessionManager implements SessionManagerInterface
{
    public function create(array $clientInfo = [])
    {
        $session = new Session($this->generateId(), $clientInfo);
        $this->redis->set('mcp:' . $session->getId(), json_encode($session->toArray()));
        return $session;
    }

    public function get($id)
    {
        $data = $this->redis->get('mcp:' . $id);
        return $data ? Session::restore(json_decode($data, true)) : null;
    }

    // ... implement remaining methods
}

$server = new Server(null, null, new RedisSessionManager($redis));

Laravel Integration

LaravelSessionManager works with any Laravel store that provides get(), put(), forget(), and has() — such as the Cache Repository or Session Store available in Laravel 5.1+.

use Vluzrmos\MCP\SessionManager\LaravelSessionManager;
use Vluzrmos\MCP\Server;

// Using Laravel Cache (Redis, file, database, memcached, etc.)
$store = app('cache')->store('redis');
$sessionManager = new LaravelSessionManager($store);

$server = new Server(null, null, $sessionManager);
// Using Laravel Session Store
$store = app('session')->driver();
$sessionManager = new LaravelSessionManager($store);

$server = new Server(null, null, $sessionManager);
// Custom key prefix
$sessionManager = new LaravelSessionManager($store, 'my_app_mcp:');

The LaravelSessionManager also provides a save() method to persist session changes (e.g. after calling touch()):

$session = $sessionManager->get($sessionId);
$session->touch();
$sessionManager->save($session);

Note: The default SessionManager stores sessions in memory. In a production environment with multiple PHP processes (e.g. php-fpm), use LaravelSessionManager with a shared store, or implement your own SessionManagerInterface with persistence (database, Redis, file-based, etc.) so sessions survive across requests.

HTTP & Server-Sent Events

JsonResponse

use Vluzrmos\MCP\Http\JsonResponse;

$response = new JsonResponse(['result' => 'ok']);
$response->send();

BinaryFileResponse

use Vluzrmos\MCP\Http\BinaryFileResponse;

$response = new BinaryFileResponse('/path/to/file.pdf');
$response->send();

Server-Sent Events (Streaming)

use Vluzrmos\MCP\ServerSentEvents\Stream;
use Vluzrmos\MCP\ServerSentEvents\Payload;

$stream = new Stream();
$stream->stream(function ($stream) use ($response) {
    // Send an SSE event
    $stream->payload(new Payload(
        $response,   // data
        'message',   // event type
        '1',         // id
        5000         // retry (ms)
    ));

    // Send notifications
    $stream->payload(new Payload(null, 'ping'));
});

Content Negotiation

use Vluzrmos\MCP\Http\Headers\Accept;
use Vluzrmos\MCP\Http\Headers\HeaderBag;

$headers = HeaderBag::fromServer();
$accept = Accept::make($headers->get('Accept', 'application/json'));

$preferred = $accept->getPreferredType([
    'application/json',
    'text/event-stream'
]);

if ($preferred === 'text/event-stream') {
    // Streaming SSE
} else {
    // JSON response
}

Capabilities

Capabilities tell the client which features the server supports.

use Vluzrmos\MCP\Capability;
use Vluzrmos\MCP\CapabilityOptions;

// Simple capabilities
$server->addCapability(Capability::tools());
$server->addCapability(Capability::resources());
$server->addCapability(Capability::prompts());
$server->addCapability(Capability::logging());
$server->addCapability(Capability::completions());

// With options
$options = new CapabilityOptions();
$options->listChanged(true);   // supports list change notifications
$options->subscribe(true);     // supports change subscriptions

$server->addCapability(Capability::tools($options));

Pagination

Use Cursor to implement pagination in responses:

use Vluzrmos\MCP\Pagination\Cursor;

// Create cursor
$cursor = Cursor::pagination(1, 20); // page 1, 20 per page

// Custom cursor
$cursor = new Cursor(['offset' => 0, 'limit' => 50]);

// Opaque serialization (base64)
$encoded = json_encode($cursor); // base64 string
$restored = Cursor::fromBase64String($encoded);

// Add to ToolResult
$result = ToolResult::structured($data);
$result->setNextCursor($cursor);

API Reference

Server

$server = new Server(
    ServerInfo $serverInfo = null,                   // default: new ServerInfo('MCP Server', '1.0.0')
    JsonRpcServer $jsonRpcServer = null,             // default: new JsonRpcServer()
    SessionManagerInterface $sessionManager = null   // default: new SessionManager()
);

$server->setProtocolVersion($version);   // default: '2025-06-18'
$server->setInstructions($text);
$server->addCapability(Capability $cap);
$server->addMethod(Method $method);
$server->addMethodHandler($name, $callable);
$server->handle($jsonRpcInput);          // JSON string or array (batch)
$server->getSessionManager();
$server->getCurrentSession();
$server->setCurrentSession(Session $s);

ServerInfo

$info = new ServerInfo($name, $version, array $extraInfos = []);

$info->setExtraInfo('homepage', 'https://example.com');
// Serializes as: {"name": "...", "version": "...", "x-homepage": "https://..."}

JsonSchema

$schema = new JsonSchema($type, $properties = null, array $required = null);

$schema = new JsonSchema('object', [
    'name' => ['type' => 'string', 'description' => 'User name'],
    'age'  => ['type' => 'integer']
], ['name']);

$schema->setTitle('Title');
$schema->setDescription('Description');
$schema->property('email', ['type' => 'string', 'format' => 'email']);

JSON-RPC Methods

Class MCP Method Description
ServerInitialize initialize Initializes MCP connection and creates session
ToolsList tools/list Lists available tools
ToolsCall tools/call Executes a tool
ResourcesList resources/list Lists available resources
ResourcesRead resources/read Reads a resource by URI
PromptsList prompts/list Lists available prompts
PromptsGet prompts/get Renders a prompt with arguments
Ping ping Health check
NotificationsInitialized notifications/initialized Post-initialization acknowledgement
NotificationsCancelled notifications/cancelled Handles request cancellation

Registering Custom Methods

// Via Method interface
use Vluzrmos\JsonRPC\Concerns\Method;
use Vluzrmos\JsonRPC\Request;

class MyMethod implements Method
{
    public function getName()
    {
        return 'my/method';
    }

    public function execute(Request $request)
    {
        $params = $request->getParams();
        return ['result' => 'ok'];
    }
}

$server->addMethod(new MyMethod());

// Via closure
$server->addMethodHandler('my/handler', function (Request $request) {
    return ['status' => 'ok'];
});

Testing

The project uses PHPUnit 5.7.

# Install dependencies
composer install

# Install dependencies: Via Docker
docker run -it --rm -v .:/app -w /app vluzrmos/mcp-php composer install

# Run all tests
vendor/bin/phpunit

# Via Docker
docker run -it --rm -v .:/app -w /app vluzrmos/mcp-php vendor/bin/phpunit

Test Environment Variables

Variable Default Description
RUN_INTEGRATION_TESTS 0 Enable integration tests
TEST_DEBUG 0 Enable debug output in tests

Docker

Build image

docker build -t vluzrmos/mcp-php .

Run tests

docker run -it --rm -v .:/app -w /app vluzrmos/mcp-php vendor/bin/phpunit

Development server

# Via Docker
docker run -it --rm -p 8000:8000 -v .:/app -w /app vluzrmos/mcp-php \
    php -S 0.0.0.0:8000 -t examples/server

# Or locally
php -S 0.0.0.0:8000 -t examples/server

Basic example (CLI)

docker run -it --rm -v .:/app -w /app vluzrmos/mcp-php php examples/basic.php

License

MIT