eskono / mcp-client
Framework-agnostic PHP 8.3+ SDK implementing the client side of the Model Context Protocol (MCP).
Requires
- php: ^8.3
- psr/http-client: ^1.0
- psr/http-factory: ^1.0
- psr/log: ^3.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.64
- nyholm/psr7: ^1.8
- phpstan/phpstan: ^2.0
- phpstan/phpstan-strict-rules: ^2.0
- phpunit/phpunit: ^10.5
- symfony/process: ^7.0
Suggests
- psr/http-client-implementation: A PSR-18 HTTP client is required by the Streamable HTTP transport (e.g. guzzlehttp/guzzle).
- psr/http-factory-implementation: A PSR-17 request/stream factory is required by the Streamable HTTP transport (e.g. nyholm/psr7).
Provides
None
Conflicts
None
Replaces
None
README
Framework-agnostic PHP 8.3+ SDK implementing the client side of the Model Context Protocol (MCP). It lets any PHP application connect to MCP servers โ both local (spawned processes over stdio) and, soon, remote (Streamable HTTP) โ to discover and invoke tools, read resources and render prompts over JSON-RPC 2.0.
No framework required. HTTP support is built on PSR interfaces (PSR-18/PSR-17), logging on PSR-3 โ you bring the implementations via dependency injection.
Features
- ๐งฉ Strictly typed โ every request, response and MCP entity is a readonly DTO. No raw arrays leaking out of the public API.
- ๐ Pluggable transport โ one
TransportInterface; ship with a dependency-freeStdioTransport(nativeproc_open+ non-blocking streams). - ๐ Full JSON-RPC 2.0 โ requests, responses, notifications, errors, id correlation, and handling of server-initiated requests.
- ๐ Discovery โ
listTools(),listResources(),listPrompts()with transparent cursor pagination. - โก Execution โ
callTool(),readResource(),getPrompt(). - ๐ชต Observable โ pass any PSR-3 logger; JSON-RPC traffic is logged at
debug, serverstderris forwarded automatically. - โ Tested โ PHPUnit 10 suite covering the protocol layer and the client.
Requirements
- PHP 8.3+
psr/log(PSR-3)psr/http-client+psr/http-factory(PSR-18/PSR-17, used by the upcoming HTTP transport)- For the stdio transport: the ability to run the target server binary
(
node,python,npx, โฆ). No PHP extension required.
Installation
composer require eskono/mcp-client
Quick start
Connect to a local MCP server started as a child process, run the handshake, list its tools and call one:
<?php require __DIR__ . '/vendor/autoload.php'; use Mcp\McpClient; use Mcp\Transport\StdioTransport; use Mcp\Transport\StdioServerParameters; // 1. Describe how to launch the server (argv form โ no shell, no injection). $transport = new StdioTransport( new StdioServerParameters( command: 'node', args: ['weather-server.js'], ), ); // 2. Create the client and perform the MCP handshake. $client = new McpClient($transport); $init = $client->initialize(); echo "Connected to {$init->serverInfo->name} {$init->serverInfo->version}\n"; // 3. Discover tools. foreach ($client->listTools() as $tool) { echo "- {$tool->name}: {$tool->description}\n"; } // 4. Call a tool. $result = $client->callTool('get_weather', ['city' => 'Paris']); echo $result->text() . "\n"; // 5. Clean up (also terminates the child process). $client->close();
Shortcut: McpClient::stdio('node', ['weather-server.js']) builds the
transport for you (and McpClient::http(...) for remote servers).
More runnable scripts live in examples/.
Core concepts
The SDK is layered so each concern is testable in isolation:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ McpClient โ facade: initialize / list* / call* / read* / getPrompt
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ typed DTOs (Tool, Resource, โฆ) โฒ
โผ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ JSON-RPC 2.0 protocol โ JsonRpcRequest/Response/Notification/Error, MessageParser
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ JsonRpcMessageInterface โฒ
โผ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ TransportInterface โ StdioTransport (proc_open) โ StreamableHttpTransport (soon)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ stdin/stdout โฒ stderr โ PSR-3
โผ โ
MCP server process
- Transport moves framed JSON-RPC messages to/from a server.
- JSON-RPC layer models the wire protocol as immutable DTOs.
- DTO layer models MCP entities (tools, resources, prompts, content blocks).
McpClientties it together: handshake, request/response correlation, pagination, and error mapping.
See docs/architecture.md for details.
Transports
| Transport | Status | Use for |
|---|---|---|
StdioTransport |
โ available | Local servers launched as a child process |
StreamableHttpTransport |
โ available | Remote servers over HTTP (PSR-18/PSR-17) |
StdioTransport reads protocol messages from the server's stdout
(newline-delimited JSON), writes to its stdin, and forwards stderr to
your logger. StreamableHttpTransport speaks the MCP Streamable HTTP protocol
(POST + application/json / text/event-stream, Mcp-Session-Id) over any
PSR-18 client you inject. Details in docs/transports.md.
Working with results
$result = $client->callTool('get_weather', ['city' => 'Paris']); $result->isError; // tool-level error flag (bool) $result->text(); // concatenated text of all TextContent blocks $result->structuredContent; // structured output (array|null) foreach ($result->content as $block) { // TextContent | ImageContent | AudioContent | EmbeddedResource match (true) { $block instanceof \Mcp\Model\Content\TextContent => print($block->text), $block instanceof \Mcp\Model\Content\ImageContent => file_put_contents('img', $block->decoded()), default => null, }; } $read = $client->readResource('file:///readme.md'); $first = $read->first(); // ?ResourceContentsInterface echo $first->uri();
Full DTO map: docs/dtos.md.
Error handling
All SDK exceptions extend Mcp\Exception\McpException:
| Exception | Thrown when |
|---|---|
JsonRpcException |
The server answered a request with a JSON-RPC error. Exposes getRpcError() / getRpcData() / getMethod(). |
TimeoutException |
No response arrived within requestTimeout. |
TransportException |
The transport failed (process died, pipe closed, โฆ). |
ProtocolException |
A malformed / unrecognizable JSON-RPC payload. |
RequestCancelledException |
The request was cancelled via a CancellationToken. |
use Mcp\Exception\JsonRpcException; try { $client->callTool('unknown_tool'); } catch (JsonRpcException $e) { echo "Server error {$e->getRpcError()->code}: {$e->getMessage()}\n"; }
More in docs/error-handling.md.
Logging
Pass any PSR-3 logger; it receives JSON-RPC traffic at debug and forwarded
server stderr:
$client = new McpClient($transport, $logger);
See docs/logging.md.
Testing
composer install vendor/bin/phpunit
Note: this project targets PHP 8.3+. If your default
phpis older, run the suite with an explicit 8.3 binary, e.g./opt/homebrew/opt/php@8.3/bin/php vendor/bin/phpunit. SeeCONTRIBUTING.md.
Documentation
- Usage guide โ task-oriented cookbook covering every feature.
- Getting started โ install to first tool call.
- Client API reference โ every method.
- Architecture ยท Transports ยท DTO reference ยท Error handling ยท Logging
Versioning & compatibility
This package follows Semantic Versioning. While it is pre-1.0, minor releases may include breaking changes (noted in the changelog).
| eskono/mcp-client | PHP | MCP protocol (preferred) |
|---|---|---|
^0.1 |
8.3, 8.4 | 2025-06-18 |
The client sends its preferred protocol version on initialize and records what
the server negotiates ($init->protocolVersion), so it also interoperates with
servers speaking earlier revisions (2025-03-26, 2024-11-05) for the features
they support.
Releases
See the changelog for release notes and
RELEASING.md for the maintainer release process.
Contributing
Contributions welcome โ see CONTRIBUTING.md.
License
MIT.