How to Use the PHP MCP Client for AI‑Powered Tool Integration
The PHP MCP Client library provides a PHP interface for communicating with Model Context Protocol (MCP) servers via stdio or HTTP‑SSE, offering synchronous and asynchronous APIs, configurable connection lifecycles, PSR‑compliant logging, caching, and detailed examples for installation, configuration, and usage.
Introduction
PHP MCP Client is a PHP library that enables interaction with servers implementing the Model Context Protocol (MCP). It supports both stdio and http+sse transports, manages connection lifecycles, discovers server capabilities, and executes requests such as tool calls or resource reads.
MCP Overview
Model Context Protocol (MCP) is an open standard that defines how AI assistants and applications connect to external data sources, APIs, and tools (e.g., code repositories, databases, browsers). It acts as a communication layer allowing AI models (Claude, OpenAI‑based models, etc.) to safely access contextual information from servers. The PHP client treats a PHP application as a "host" that can use one or more MCP servers.
Key Features
Client‑server one‑to‑one model : each Client instance manages a stateful connection to a single MCP server.
Fluent configuration : builder pattern via Client::make()->with...() for easy setup.
Dual API design :
Synchronous façade API (e.g., $client->listTools(), $client->callTool(...)) for simple blocking use.
Asynchronous Promise‑based API (e.g., $client->listToolsAsync(), $client->callToolAsync(...)) for concurrent operations.
Multiple transport support : stdio (standard input/output) and http (POST + Server‑Sent Events).
Explicit connection lifecycle : call ->initialize() or ->initializeAsync() before any request, and disconnect() / disconnectAsync() afterwards.
Tool/Resource/Prompt interaction : methods for listing and invoking tools, reading resources, and fetching prompts.
PSR compliance : implements PSR‑3 (logger), PSR‑16 (simple cache), and PSR‑14 (event dispatcher).
Robust error handling : specific exceptions for timeout, request, connection, and other failure modes.
Async core : built on ReactPHP for non‑blocking I/O.
Requirements
PHP >= 8.1
Composer
For stdio transport: ability to execute the server command.
For http transport: network access to the MCP server URL.
Installation
composer require php-mcp/clientRequired ReactPHP dependencies (event‑loop, promise, stream, child‑process, http) are installed automatically.
Quick Start – Simple Synchronous Usage (Stdio)
The example below connects to a local file‑system server started with npx and lists available tools.
<?php
require 'vendor/autoload.php';
use PhpMcp\Client\Client;
use PhpMcp\Client\Enum\TransportType;
use PhpMcp\Client\Model\Capabilities as ClientCapabilities;
use PhpMcp\Client\ServerConfig;
use PhpMcp\Client\Exception\McpClientException;
$clientCapabilities = ClientCapabilities::forClient();
$userHome = $_SERVER['HOME'] ?? $_SERVER['USERPROFILE'] ?? getcwd();
$fsServerConfig = new ServerConfig(
name: 'local_filesystem',
transport: TransportType::Stdio,
timeout: 15,
command: 'npx',
args: [
'-y',
'@modelcontextprotocol/server-filesystem',
$userHome . '/Documents',
],
workingDir: $userHome
);
$fsClient = Client::make()
->withClientInfo('MyFileSystemApp', '1.0')
->withCapabilities($clientCapabilities)
->withServerConfig($fsServerConfig)
->build();
try {
$fsClient->initialize();
$tools = $fsClient->listTools();
foreach ($tools as $tool) {
echo "- Tool: {$tool->name}
";
}
} catch (McpClientException $e) {
echo "[MCP ERROR] " . get_class($e) . ": " . $e->getMessage() . "
";
} catch (\Throwable $e) {
echo "[UNEXPECTED ERROR] " . $e->getMessage() . "
";
} finally {
if (isset($fsClient)) {
$fsClient->disconnect();
}
}Configuration
Configuration consists of four main parts:
Client identification : name and version passed to the builder.
Client capabilities : declared via ClientCapabilities.
Server connection : defined with a ServerConfig instance.
Optional dependencies : logger, cache, event dispatcher, event loop.
ClientCapabilities
Static factory methods create capability objects. Example:
use PhpMcp\Client\Model\Capabilities as ClientCapabilities;
$clientCapabilities = ClientCapabilities::forClient(supportsSampling: true);
// or
$clientCapabilities = ClientCapabilities::forClient(supportsSampling: false);ServerConfig
Defines how to connect to a single MCP server. Two transport examples are shown.
use PhpMcp\Client\Enum\TransportType;
use PhpMcp\Client\ServerConfig;
// Stdio server example
$stdioConfig = new ServerConfig(
name: 'local_file_server',
transport: TransportType::Stdio,
timeout: 15.0,
command: 'npx',
args: ['-y', '@modelcontextprotocol/server-filesystem', '/path/to/project'],
workingDir: '/path/to/project',
env: ['DEBUG' => 'mcp*']
);
// HTTP server example
$httpConfig = new ServerConfig(
name: 'remote_web_agent',
transport: TransportType::Http,
timeout: 45.0,
url: 'http://localhost:8080/sse',
headers: ['Authorization' => 'Bearer xyz789']
);Loading Configuration from Array/JSON
Configuration can be parsed from a JSON string or PHP array and converted into ServerConfig objects.
$jsonConfig = '{
"mcpServers": {
"stdio_files": {
"command": "php",
"args": ["/app/mcp/file_server.php"],
"timeout": 10
},
"http_api": {
"url": "https://api.example.com/mcp/sse",
"transport": "http",
"headers": {"X-API-Key": "secret"}
}
}
}';
$decodedConfig = json_decode($jsonConfig, true)['mcpServers'] ?? [];
$serverConfigs = [];
foreach ($decodedConfig as $name => $data) {
try {
$serverConfigs[$name] = ServerConfig::fromArray($name, $data);
} catch (ConfigurationException $e) {
echo "Error parsing config for '{$name}': {$e->getMessage()}
";
}
}ClientBuilder
Assemble a client instance with the fluent builder:
use PhpMcp\Client\Client;
$client = Client::make()
->withClientInfo($clientName, $clientVersion) // required
->withCapabilities($clientCapabilities) // optional (defaults provided)
->withServerConfig($stdioConfig) // required
->withLogger($myLogger) // optional
->withCache($myCache, 3600) // optional
->withEventDispatcher($myDispatcher) // optional
->withIdGenerator($myIdGenerator) // optional
->withLoop($myEventLoop) // optional (defaults to Loop::get())
->build();Usage
1. Initialize Connection
Before sending any request, call initialize() (blocking) or initializeAsync() (Promise).
// Synchronous
try {
$client->initialize();
echo "Connection Ready!";
} catch (\Throwable $e) {
echo "Initialization failed: " . $e->getMessage();
}
// Asynchronous
$client->initializeAsync()->then(
function (Client $readyClient) { /* ready */ },
function (\Throwable $error) { /* handle error */ }
);
// Remember to run the event loop when using async APIs.2. Send Requests
After initialization, use either the synchronous façade or the async Promise API.
Synchronous API (recommended for simple scripts) : listTools(), callTool(), readResource(), etc. These methods block until a response is received or a timeout occurs and may throw exceptions such as TimeoutException, RequestException, or ConnectionException.
Asynchronous API : listToolsAsync(), callToolAsync(), readResourceAsync(), etc. They return a React\Promise\PromiseInterface that can be handled with then, catch, or finally, and require the event loop to be running.
// Synchronous example
if ($client->isReady()) {
$tools = $client->listTools();
$result = $client->callTool('myTool', ['param' => 'value']);
}
// Asynchronous example
use function React\Promise\all;
$p1 = $client->listToolsAsync();
$p2 = $client->readResourceAsync('config://settings');
all([$p1, $p2])->then(
function (array $results) {
[$tools, $readResult] = $results;
// process results
},
function (\Throwable $error) {
// handle error
}
);
// $client->getLoop()->run(); // start loop if needed3. Disconnect
Always disconnect after work is finished to free resources, especially for stdio transports.
// Synchronous
$client->disconnect();
// Asynchronous
$client->disconnectAsync()->then(function () {
echo "Disconnected async";
});Available Client Methods
Connection & Lifecycle
initialize(): self– block until the handshake completes. initializeAsync(): PromiseInterface<Client> – async version. disconnect(): void – graceful blocking disconnect. disconnectAsync(): PromiseInterface<void> – async disconnect. getStatus(): ConnectionStatus – returns one of Disconnected, Connecting, Handshaking, Ready, Closing, Closed, Error. isReady(): bool – true when status is Ready. getServerName(): ?string – server name after successful init. getServerVersion(): ?string – server version after init. getNegotiatedCapabilities(): ?Capabilities – capabilities agreed with the server. getNegotiatedProtocolVersion(): ?string – protocol version after init.
MCP Operations (Synchronous)
ping(): void listTools(bool $useCache = true): array<ToolDefinition> listResources(bool $useCache = true): array<ResourceDefinition> listPrompts(bool $useCache = true): array<PromptDefinition> listResourceTemplates(bool $useCache = true): array<ResourceTemplateDefinition> callTool(string $toolName, array $arguments = []): CallToolResult readResource(string $uri): ReadResourceResult getPrompt(string $promptName, array $arguments = []): GetPromptResult subscribeResource(string $uri): void unsubscribeResource(string $uri): void setLogLevel(string $level): voidMCP Operations (Asynchronous)
pingAsync(): PromiseInterface<void> listToolsAsync(): PromiseInterface<array<ToolDefinition>> listResourcesAsync(): PromiseInterface<array<ResourceDefinition>> listPromptsAsync(): PromiseInterface<array<PromptDefinition>> listResourceTemplatesAsync(): PromiseInterface<array<ResourceTemplateDefinition>> callToolAsync(string $toolName, array $arguments = []): PromiseInterface<CallToolResult> readResourceAsync(string $uri): PromiseInterface<ReadResourceResult> getPromptAsync(string $promptName, array $arguments = []): PromiseInterface<GetPromptResult> subscribeResourceAsync(string $uri): PromiseInterface<void> unsubscribeResourceAsync(string $uri): PromiseInterface<void> setLogLevelAsync(string $level): PromiseInterface<void>Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Open Source Tech Hub
Sharing cutting-edge internet technologies and practical AI resources.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
