[Client] docs: introduce client SDK documentation and improve README structure (#259)

* docs: add comprehensive client SDK documentation

- Create new client documentation with examples and API reference
- Restructure README for better clarity and flow
- Add client examples to documentation index
- Update roadmap to mark client component as complete

* Update README.md

Co-authored-by: Christopher Hertel <mail@christopher-hertel.de>

* Apply suggestions from code review

Co-authored-by: Christopher Hertel <mail@christopher-hertel.de>

---------

Co-authored-by: Christopher Hertel <mail@christopher-hertel.de>
This commit is contained in:
Kyrian Obikwelu 2026-03-31 20:37:32 +01:00 • committed by GitHub
commit 85fddf5f0f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 1019 additions and 158 deletions

334
README.md
View file

@ -10,202 +10,143 @@ development practices and standards from the Symfony project, including [Coding
Until the first major release, this SDK is considered [experimental](https://symfony.com/doc/current/contributing/code/experimental.html), please see the [roadmap](./ROADMAP.md) for
planned next steps and features.
## Table of Contents
- [Installation](#installation)
- [Overview](#overview)
- [Server SDK](#server-sdk)
- [Client SDK](#client-sdk)
- [Documentation](#documentation)
- [External Resources](#external-resources)
- [PHP Libraries Using the MCP SDK](#php-libraries-using-the-mcp-sdk)
- [Contributing](#contributing)
- [Credits](#credits)
- [License](#license)
## Installation
```bash
composer require mcp/sdk
```
## Quick Start
## Overview
This example demonstrates the most common usage pattern - a STDIO server using attribute discovery.
The MCP PHP SDK provides both **server** and **client** implementations for the Model Context Protocol, enabling you to:
### 1. Define Your MCP Elements
- **Build MCP Servers**: Expose your PHP application's functionality (tools, resources, prompts) to AI agents
- **Build MCP Clients**: Connect to and interact with MCP servers from your PHP applications
Create a class with MCP capabilities using attributes:
## Server SDK
Build MCP servers to expose your PHP application's capabilities to AI agents like Claude, Codex, and others.
### Quick Start
```php
<?php
namespace App;
use Mcp\Server;
use Mcp\Server\Transport\StdioTransport;
use Mcp\Capability\Attribute\McpTool;
use Mcp\Capability\Attribute\McpResource;
class CalculatorElements
// Define capabilities using PHP attributes
class CalculatorCapabilities
{
/**
* 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]
public function add(int $a, int $b): int
{
return $a + $b;
}
/**
* Performs basic arithmetic operations.
*/
#[McpTool(name: 'calculate')]
public function calculate(float $a, float $b, string $operation): float|string
{
return match($operation) {
'add' => $a + $b,
'subtract' => $a - $b,
'multiply' => $a * $b,
'divide' => $b != 0 ? $a / $b : 'Error: Division by zero',
default => 'Error: Unknown operation'
};
}
#[McpResource(
uri: 'config://calculator/settings',
name: 'calculator_config',
mimeType: 'application/json'
)]
#[McpResource(uri: 'config://calculator/settings')]
public function getSettings(): array
{
return ['precision' => 2, 'allow_negative' => true];
return ['precision' => 2];
}
}
```
### 2. Create the Server Script
Create your MCP server:
```php
#!/usr/bin/env php
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use Mcp\Server;
use Mcp\Server\Transport\StdioTransport;
// Build and run the server
$server = Server::builder()
->setServerInfo('Calculator Server', '1.0.0')
->setDiscovery(__DIR__, ['.'])
->setDiscovery(__DIR__, ['.']) // Auto-discover attributes
->build();
$transport = new StdioTransport();
$server->run($transport);
```
### 3. Configure Your MCP Client
### Server Capabilities
Add to your client configuration (e.g., Claude Desktop's `mcp.json`):
- **Tools**: Executable functions that AI agents can call
- **Resources**: Data sources that can be read (files, configs, databases)
- **Resource Templates**: Dynamic resources with URI parameters
- **Prompts**: Pre-defined templates for AI interactions
- **Server-Initiated Communication**: Elicitations, sampling, logging, progress notifications
```json
{
"mcpServers": {
"php-calculator": {
"command": "php",
"args": ["/absolute/path/to/your/server.php"]
}
}
}
```
### Registration Methods
### 4. Test Your Server
```bash
# Test with MCP Inspector
npx @modelcontextprotocol/inspector php /path/to/server.php
# Your AI assistant can now call:
# - add: Add two integers
# - calculate: Perform arithmetic operations
# - Read config://calculator/settings resource
```
## Key Features
### Attribute-Based Discovery
Define MCP elements using PHP attributes with automatic discovery:
There are multiple ways to register your MCP capabilities—choose the approach that best fits your application's architecture:
**1. Attribute-Based Discovery** — Define capabilities using PHP attributes for automatic discovery:
```php
// Tool with automatic name and description from method
#[McpTool]
public function generateReport(): string { /* ... */ }
// Tool with custom name
#[McpTool(name: 'custom_name')]
public function myMethod(): string { /* ... */ }
// Resource with URI and metadata
#[McpResource(uri: 'config://app/settings', mimeType: 'application/json')]
#[McpResource(uri: 'config://app/settings')]
public function getConfig(): array { /* ... */ }
```
### Manual Registration
Register capabilities programmatically:
**2. Manual Registration** — Register capabilities programmatically without attributes:
```php
$server = Server::builder()
->addTool([MyClass::class, 'myMethod'], 'tool_name')
->addResource([MyClass::class, 'getData'], 'data://config')
->addTool([Calculator::class, 'add'], 'add_numbers')
->addResource([Config::class, 'get'], 'config://app')
->build();
```
### Multiple Transport Options
**3. Hybrid Approach** — Combine both methods for maximum flexibility:
```php
$server = Server::builder()
->setDiscovery(__DIR__, ['.'])
->addTool([ExternalService::class, 'process'], 'external')
->build();
```
**STDIO Transport** (Command-line integration):
### Transports
Choose the transport that matches your deployment environment:
**1. STDIO Transport** — For command-line integration and local processes:
```php
$transport = new StdioTransport();
$server->run($transport);
```
**HTTP Transport** (Web-based communication):
**2. HTTP Transport** — For web-based servers and distributed systems:
```php
$transport = new StreamableHttpTransport($request, $responseFactory, $streamFactory);
$response = $server->run($transport);
// Handle $response in your web application
```
### Session Management
By default, the SDK uses in-memory sessions. You can configure different session stores:
Configure session storage to maintain state between requests. Choose the backend that fits your infrastructure:
**In-Memory** (default, suitable for STDIO):
```php
use Mcp\Server\Session\FileSessionStore;
use Mcp\Server\Session\InMemorySessionStore;
use Mcp\Server\Session\Psr16SessionStore;
use Symfony\Component\Cache\Psr16Cache;
use Symfony\Component\Cache\Adapter\RedisAdapter;
// Use default in-memory sessions with custom TTL
$server = Server::builder()
->setSession(ttl: 7200) // 2 hours
->build();
```
// Override with file-based storage
**File-Based** (suitable for single-server HTTP deployments):
```php
$server = Server::builder()
->setSession(new FileSessionStore(__DIR__ . '/sessions'))
->build();
```
// Override with in-memory storage and custom TTL
$server = Server::builder()
->setSession(new InMemorySessionStore(3600))
->build();
// Override with PSR-16 cache-based storage
// Requires psr/simple-cache and symfony/cache (or any other PSR-16 implementation)
// composer require psr/simple-cache symfony/cache
$redisAdapter = new RedisAdapter(
RedisAdapter::createConnection('redis://localhost:6379'),
'mcp_sessions'
);
**PSR-16 Cache** (for example with Redis for scaled deployments):
```php
$server = Server::builder()
->setSession(new Psr16SessionStore(
cache: new Psr16Cache($redisAdapter),
@ -215,60 +156,141 @@ $server = Server::builder()
->build();
```
### Discovery Caching
[→ Server Documentation](docs/server-builder.md)
Use any PSR-16 cache implementation to cache discovery results and avoid running discovery on every server start:
## Client SDK
Connect to MCP servers from your PHP applications to access their tools, resources, and prompts.
### Quick Start
```php
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\Cache\Psr16Cache;
use Mcp\Client;
use Mcp\Client\Transport\StdioTransport;
$cache = new Psr16Cache(new FilesystemAdapter('mcp-discovery'));
// Build the client
$client = Client::builder()
->setClientInfo('My Application', '1.0.0')
->setInitTimeout(30)
->setRequestTimeout(120)
->build();
$server = Server::builder()
->setDiscovery(
basePath: __DIR__,
scanDirs: ['.', 'src'], // Default: ['.', 'src']
excludeDirs: ['vendor'], // Default: ['vendor', 'node_modules']
cache: $cache
)
// Connect to a server
$transport = new StdioTransport(
command: 'php',
args: ['/path/to/server.php'],
);
$client->connect($transport);
// Discover and use capabilities
$tools = $client->listTools();
$result = $client->callTool('add', ['a' => 5, 'b' => 3]);
$resources = $client->listResources();
$content = $client->readResource('config://calculator/settings');
$client->disconnect();
```
### Client Capabilities
- **Tool Calling**: List and execute tools from any MCP server
- **Resource Access**: Read static and dynamic resources
- **Prompt Management**: List and retrieve prompt templates
- **Completion Support**: Request argument completion suggestions
### Advanced Features
- **Progress Tracking**: Real-time progress during long operations
```php
$result = $client->callTool(
name: 'process_data',
arguments: ['dataset' => 'large_file.csv'],
onProgress: function (float $progress, ?float $total, ?string $message) {
echo "Progress: {$progress}/{$total} - {$message}\n";
}
);
```
- **Sampling Support**: Handle server LLM sampling requests
```php
$samplingHandler = new SamplingRequestHandler($myCallback);
$client = Client::builder()
->setCapabilities(new ClientCapabilities(sampling: true))
->addRequestHandler($samplingHandler)
->build();
```
- **Logging Notifications**: Receive server log messages
```php
$loggingHandler = new LoggingNotificationHandler($myCallback);
$client = Client::builder()
->addNotificationHandler($loggingHandler)
->build();
```
### Transports
Connect to MCP servers using the transport that matches your setup:
**1. STDIO Transport** — Connect to local server processes:
```php
$transport = new StdioTransport(
command: 'php',
args: ['/path/to/server.php'],
);
$client->connect($transport);
```
**2. HTTP Transport** — Connect to remote or web-based servers:
```php
$transport = new HttpTransport('http://localhost:8000');
$client->connect($transport);
```
[→ Client Documentation](docs/client.md)
## Documentation
**Core Concepts:**
- [Server Builder](docs/server-builder.md) - Complete ServerBuilder reference and configuration
- [Transports](docs/transports.md) - STDIO and HTTP transport setup and usage
- [MCP Elements](docs/mcp-elements.md) - Creating tools, resources, and prompts
- [Client Communication](docs/client-communication.md) - Communicating back to the client from server-side
- [Events](docs/events.md) - Hooking into server lifecycle with events
### Core Concepts
**Learning:**
- [Examples](docs/examples.md) - Comprehensive example walkthroughs
- **[Server Builder](docs/server-builder.md)** — Complete ServerBuilder reference and configuration
- **[Client](docs/client.md)** — Client SDK for connecting to and communicating with MCP servers
- **[Transports](docs/transports.md)** — STDIO and HTTP transport setup and usage
- **[MCP Elements](docs/mcp-elements.md)** — Creating tools, resources, prompts, and templates
- **[Server-Client Communication](docs/server-client-communication.md)** — Sampling, logging, progress, and notifications
- **[Events](docs/events.md)** — Hooking into server lifecycle with events
**External Resources:**
- [Model Context Protocol documentation](https://modelcontextprotocol.io)
- [Model Context Protocol specification](https://spec.modelcontextprotocol.io)
- [Officially supported servers](https://github.com/modelcontextprotocol/servers)
### Learning & Examples
- **[Examples](docs/examples.md)** — Comprehensive example walkthroughs for servers and clients
- **[ROADMAP.md](ROADMAP.md)** — Planned features and development roadmap
## External Resources
- **[Model Context Protocol Documentation](https://modelcontextprotocol.io)** — Official MCP documentation
- **[Model Context Protocol Specification](https://spec.modelcontextprotocol.io)** — Protocol specification
- **[Officially Supported Servers](https://github.com/modelcontextprotocol/servers)** — Reference server implementations
## PHP Libraries Using the MCP SDK
* [pronskiy/mcp](https://github.com/pronskiy/mcp) - Additional DX layer
* [symfony/mcp-bundle](https://github.com/symfony/mcp-bundle) - Symfony integration bundle
* [josbeir/cakephp-synapse](https://github.com/josbeir/cakephp-synapse) - CakePHP integration plugin
- [pronskiy/mcp](https://github.com/pronskiy/mcp) — Additional developer experience layer
- [symfony/mcp-bundle](https://github.com/symfony/mcp-bundle) — Symfony integration bundle
- [josbeir/cakephp-synapse](https://github.com/josbeir/cakephp-synapse) — CakePHP integration plugin
## Contributing
We are passionate about supporting contributors of all levels of experience and would love to see you get involved in
the project. See the [contributing guide](CONTRIBUTING.md) to get started before you [report issues](https://github.com/modelcontextprotocol/php-sdk/issues) and [send pull requests](https://github.com/modelcontextprotocol/php-sdk/pulls).
We are passionate about supporting contributors of all levels of experience and would love to see you get involved in the project.
See the [Contributing Guide](CONTRIBUTING.md) to get started before you [report issues](https://github.com/modelcontextprotocol/php-sdk/issues) and [send pull requests](https://github.com/modelcontextprotocol/php-sdk/pulls).
## Credits
The starting point for this SDK was the [PHP-MCP](https://github.com/php-mcp/server) project, initiated by
[Kyrian Obikwelu](https://github.com/CodeWithKyrian), and the [Symfony AI initiative](https://github.com/symfony/ai). We are grateful for the work
done by both projects and their contributors, which created a solid foundation for this SDK.
The starting point for this SDK was the [PHP-MCP](https://github.com/php-mcp/server) project, initiated by [Kyrian Obikwelu](https://github.com/CodeWithKyrian), and the [Symfony AI initiative](https://github.com/symfony/ai). We are grateful for the work done by both projects and their contributors, which created a solid foundation for this SDK.
## License
This project is licensed under the Apache License, Version 2.0 for new contributions, with existing code under the MIT License - see the [LICENSE](LICENSE) file for details.
This project is licensed under the Apache License, Version 2.0 for new contributions, with existing code under the MIT License — see the [LICENSE](LICENSE) file for details.

View file

@ -8,8 +8,8 @@ This roadmap is a living document that outlines the planned features and improve
- [ ] Implement full support for elicitations
- [ ] Implement OAuth2 authentication for server
- **Client**
- [ ] Implement client-side support
- [ ] Implement client examples and documentation
- [x] Implement client-side support
- [x] Implement client examples and documentation
- **Schema**
- [ ] Implement schema generation based on TS or JSON Schema

691
docs/client.md Normal file
View file

@ -0,0 +1,691 @@
# Client
The MCP Client SDK provides a synchronous, framework-agnostic API for communicating with MCP servers from PHP applications.
It handles connection management, request/response correlation, server-initiated requests (sampling), and real-time notifications.
## Table of Contents
- [Overview](#overview)
- [Client Builder](#client-builder)
- [Transports](#transports)
- [Connecting to Servers](#connecting-to-servers)
- [Server Information](#server-information)
- [Working with Tools](#working-with-tools)
- [Working with Resources](#working-with-resources)
- [Working with Prompts](#working-with-prompts)
- [Server-Initiated Communication](#server-initiated-communication)
- [Error Handling](#error-handling)
- [Complete Example](#complete-example)
## Overview
The client follows a builder pattern for configuration and provides a synchronous API for all operations:
```php
use Mcp\Client;
use Mcp\Client\Transport\StdioTransport;
// Build and configure the client
$client = Client::builder()
->setClientInfo('My Client', '1.0.0')
->setInitTimeout(30)
->setRequestTimeout(120)
->build();
// Create a transport
$transport = new StdioTransport(
command: 'php',
args: ['/path/to/server.php'],
);
// Connect and use the server
$client->connect($transport);
$tools = $client->listTools();
$client->disconnect();
```
## Client Builder
The `Client\Builder` provides fluent configuration of client instances.
### Basic Configuration
```php
use Mcp\Client;
$client = Client::builder()
->setClientInfo('My Application', '1.0.0', 'Description of my client')
->setInitTimeout(30) // Seconds to wait for initialization
->setRequestTimeout(120) // Seconds to wait for request responses
->setMaxRetries(3) // Retry attempts for failed connections
->build();
```
### Client Information
Set the client's identity reported to servers during initialization:
```php
$client = Client::builder()
->setClientInfo(
name: 'AI Assistant Client',
version: '2.1.0',
description: 'Client for automated AI workflows'
)
->build();
```
### Protocol Version
Specify the MCP protocol version (defaults to latest):
```php
use Mcp\Schema\Enum\ProtocolVersion;
$client = Client::builder()
->setProtocolVersion(ProtocolVersion::V2025_06_18)
->build();
```
### Capabilities
Declare client capabilities to enable server features:
```php
use Mcp\Schema\ClientCapabilities;
$client = Client::builder()
->setCapabilities(new ClientCapabilities(
sampling: true, // Enable LLM sampling requests from server
roots: true, // Enable filesystem root listing
))
->build();
```
### Notification Handlers
Register handlers for server-initiated notifications:
```php
use Mcp\Client\Handler\Notification\LoggingNotificationHandler;
use Mcp\Schema\Notification\LoggingMessageNotification;
$loggingHandler = new LoggingNotificationHandler(
static function (LoggingMessageNotification $notification) {
echo "[{$notification->level->value}] {$notification->data}\n";
}
);
$client = Client::builder()
->addNotificationHandler($loggingHandler)
->build();
```
### Request Handlers
Register handlers for server-initiated requests (e.g., sampling):
```php
use Mcp\Client\Handler\Request\SamplingRequestHandler;
use Mcp\Client\Handler\Request\SamplingCallbackInterface;
use Mcp\Schema\Request\CreateSamplingMessageRequest;
use Mcp\Schema\Result\CreateSamplingMessageResult;
$samplingCallback = new class implements SamplingCallbackInterface {
public function __invoke(CreateSamplingMessageRequest $request): CreateSamplingMessageResult
{
// Perform LLM sampling and return result
}
};
$client = Client::builder()
->addRequestHandler(new SamplingRequestHandler($samplingCallback))
->build();
```
### Logger
Configure PSR-3 logging for debugging:
```php
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
$logger = new Logger('mcp-client');
$logger->pushHandler(new StreamHandler('client.log', Logger::DEBUG));
$client = Client::builder()
->setLogger($logger)
->build();
```
## Transports
Transports handle the communication layer between client and server.
### STDIO Transport
Spawns a server process and communicates via standard input/output:
```php
use Mcp\Client\Transport\StdioTransport;
$transport = new StdioTransport(
command: 'php',
args: ['/path/to/server.php'],
cwd: '/working/directory', // Optional working directory
env: ['KEY' => 'value'], // Optional environment variables
);
```
**Parameters:**
- `command` (string): The command to execute
- `args` (array): Command arguments
- `cwd` (string|null): Working directory for the process
- `env` (array|null): Environment variables
- `logger` (LoggerInterface|null): Optional PSR-3 logger
### HTTP Transport
Communicates with remote MCP servers over HTTP:
```php
use Mcp\Client\Transport\HttpTransport;
$transport = new HttpTransport(
endpoint: 'http://localhost:8000',
headers: ['Authorization' => 'Bearer token'],
);
```
**Parameters:**
- `endpoint` (string): The MCP server URL
- `headers` (array): Additional HTTP headers
- `httpClient` (ClientInterface|null): PSR-18 HTTP client (auto-discovered)
- `requestFactory` (RequestFactoryInterface|null): PSR-17 request factory (auto-discovered)
- `streamFactory` (StreamFactoryInterface|null): PSR-17 stream factory (auto-discovered)
- `logger` (LoggerInterface|null): Optional PSR-3 logger
**PSR-18 Auto-Discovery:**
The transport automatically discovers PSR-18 HTTP clients from:
- `php-http/guzzle7-adapter`
- `php-http/curl-client`
- `symfony/http-client`
- And other PSR-18 compatible implementations
```bash
# Install any PSR-18 client - discovery works automatically
composer require php-http/guzzle7-adapter
```
## Connecting to Servers
### Establishing Connection
```php
$client->connect($transport);
```
The `connect()` method performs the MCP initialization handshake:
1. Opens the transport connection
2. Sends InitializeRequest with client capabilities
3. Waits for InitializeResult from server
4. Sends InitializedNotification
> [!IMPORTANT]
> Always wrap connection in try/catch to handle `ConnectionException` for failed connections.
### Checking Connection State
```php
if ($client->isConnected()) {
// Client is connected and initialized
}
```
### Disconnecting
```php
$client->disconnect();
```
Always disconnect when finished to clean up resources:
```php
try {
$client->connect($transport);
// ... use the client ...
} finally {
$client->disconnect();
}
```
## Server Information
After successful connection, retrieve server metadata:
```php
// Get server implementation info
$serverInfo = $client->getServerInfo();
echo "Server: {$serverInfo->name} v{$serverInfo->version}\n";
// Get server instructions
$instructions = $client->getInstructions();
if ($instructions) {
echo "Instructions: {$instructions}\n";
}
```
## Working with Tools
### Listing Tools
```php
$toolsResult = $client->listTools();
foreach ($toolsResult->tools as $tool) {
echo "- {$tool->name}: {$tool->description}\n";
}
// Handle pagination
if ($toolsResult->nextCursor) {
$moreTools = $client->listTools($toolsResult->nextCursor);
}
```
### Calling Tools
```php
$result = $client->callTool(
name: 'calculate',
arguments: ['a' => 5, 'b' => 3, 'operation' => 'add'],
);
// Access results
foreach ($result->content as $content) {
if ($content instanceof TextContent) {
echo $content->text;
}
}
```
### Progress Notifications
Hook into tool execution progress (if server supports it):
```php
$result = $client->callTool(
name: 'long_running_task',
arguments: ['data' => 'large_dataset'],
onProgress: static function (float $progress, ?float $total, ?string $message) {
$percent = $total > 0 ? round(($progress / $total) * 100) : 0;
echo "Progress: {$percent}% - {$message}\n";
}
);
```
> [!NOTE]
> Progress notifications are only received if the server sends them. The callback will not be invoked if the server doesn't support or send progress updates.
## Working with Resources
### Listing Resources
```php
$resourcesResult = $client->listResources();
foreach ($resourcesResult->resources as $resource) {
echo "- {$resource->uri}: {$resource->name}\n";
}
```
### Listing Resource Templates
```php
$templatesResult = $client->listResourceTemplates();
foreach ($templatesResult->resourceTemplates as $template) {
echo "- {$template->uriTemplate}: {$template->name}\n";
}
```
### Reading Resources
```php
$resourceResult = $client->readResource('config://app/settings');
foreach ($resourceResult->contents as $content) {
if ($content instanceof TextResourceContents) {
echo "Text: {$content->text}\n";
} elseif ($content instanceof BlobResourceContents) {
echo "Binary data (base64): {$content->blob}\n";
}
}
```
Resources also support progress notifications:
```php
$result = $client->readResource(
uri: 'file://large-file.bin',
onProgress: static function (float $progress, ?float $total, ?string $message) {
echo "Reading: {$progress}/{$total} bytes\n";
}
);
```
## Working with Prompts
### Listing Prompts
```php
$promptsResult = $client->listPrompts();
foreach ($promptsResult->prompts as $prompt) {
echo "- {$prompt->name}: {$prompt->description}\n";
}
```
### Getting Prompts
```php
$promptResult = $client->getPrompt(
name: 'code_review',
arguments: ['language' => 'php', 'code' => '...'],
);
foreach ($promptResult->messages as $message) {
echo "{$message->role->value}: {$message->content->text}\n";
}
```
Prompts also support progress notifications:
```php
$result = $client->getPrompt(
name: 'generate_report',
arguments: ['topic' => 'quarterly_analysis'],
onProgress: static function (float $progress, ?float $total, ?string $message) {
echo "Generating: {$message}\n";
}
);
```
### Requesting Completions
Request auto-completion suggestions for prompt or resource arguments:
```php
use Mcp\Schema\PromptReference;
$completionResult = $client->complete(
ref: new PromptReference('code_review'),
argument: ['name' => 'language', 'value' => 'ph'],
);
foreach ($completionResult->values as $value) {
echo "Suggestion: {$value}\n";
}
```
## Server-Initiated Communication
The client can receive requests and notifications from the server when configured with appropriate handlers.
### Logging Notifications
Receive structured log messages from the server:
```php
use Mcp\Client\Handler\Notification\LoggingNotificationHandler;
use Mcp\Schema\Notification\LoggingMessageNotification;
use Mcp\Schema\Enum\LoggingLevel;
$loggingHandler = new LoggingNotificationHandler(
static function (LoggingMessageNotification $notification) {
// Route to your application's logging system
$level = $notification->level;
$message = $notification->data;
match ($level) {
LoggingLevel::Debug => logger()->debug($message),
LoggingLevel::Info => logger()->info($message),
LoggingLevel::Warning => logger()->warning($message),
LoggingLevel::Error => logger()->error($message),
default => logger()->info($message),
};
}
);
$client = Client::builder()
->addNotificationHandler($loggingHandler)
->build();
// Set minimum log level (optional)
$client->setLoggingLevel(LoggingLevel::Info);
```
### Sampling (LLM Requests)
Handle server requests for LLM completions:
```php
use Mcp\Client\Handler\Request\SamplingRequestHandler;
use Mcp\Client\Handler\Request\SamplingCallbackInterface;
use Mcp\Exception\SamplingException;
use Mcp\Schema\ClientCapabilities;
use Mcp\Schema\Request\CreateSamplingMessageRequest;
use Mcp\Schema\Result\CreateSamplingMessageResult;
use Mcp\Schema\Content\TextContent;
use Mcp\Schema\Enum\Role;
class LlmSamplingCallback implements SamplingCallbackInterface
{
public function __invoke(CreateSamplingMessageRequest $request): CreateSamplingMessageResult
{
try {
// Call your LLM provider
$response = $this->llmClient->complete(
messages: $request->messages,
maxTokens: $request->maxTokens,
temperature: $request->temperature ?? 0.7,
);
return new CreateSamplingMessageResult(
role: Role::Assistant,
content: new TextContent($response->text),
model: $response->model,
stopReason: $response->stopReason,
);
} catch (\Throwable $e) {
// Throw SamplingException to surface error to server
throw new SamplingException(
"LLM sampling failed: {$e->getMessage()}",
(int) $e->getCode(),
$e
);
}
}
}
$client = Client::builder()
->setCapabilities(new ClientCapabilities(sampling: true))
->addRequestHandler(new SamplingRequestHandler(new LlmSamplingCallback))
->build();
```
> [!IMPORTANT]
> **Error Handling in Sampling Callbacks:**
>
> When implementing sampling callbacks, error handling is critical:
>
> - **Throw `SamplingException`** to forward specific error messages to the server
> - **Any other exception** will be logged but return a generic error to the server
>
> This distinction allows you to control what error information the server receives:
>
> ```php
> // Good: Server receives "Rate limit exceeded" message
> throw new SamplingException('Rate limit exceeded. Retry after 60 seconds.');
>
> // Bad: Server receives generic "Error while sampling LLM" message
> throw new \RuntimeException('Rate limit exceeded');
> ```
## Error Handling
The client throws exceptions for various error conditions:
### ConnectionException
Thrown when connection or initialization fails:
```php
use Mcp\Exception\ConnectionException;
try {
$client->connect($transport);
} catch (ConnectionException $e) {
echo "Failed to connect: {$e->getMessage()}\n";
}
```
### RequestException
Thrown when a request returns an error response:
```php
use Mcp\Exception\RequestException;
try {
$result = $client->callTool('unknown_tool', []);
} catch (RequestException $e) {
echo "Request failed: {$e->getMessage()}\n";
echo "Error code: {$e->getCode()}\n";
}
```
## Complete Example
Here's a comprehensive example demonstrating client usage:
```php
<?php
use Mcp\Client;
use Mcp\Client\Handler\Notification\LoggingNotificationHandler;
use Mcp\Client\Handler\Request\SamplingCallbackInterface;
use Mcp\Client\Handler\Request\SamplingRequestHandler;
use Mcp\Client\Transport\StdioTransport;
use Mcp\Exception\SamplingException;
use Mcp\Schema\ClientCapabilities;
use Mcp\Schema\Content\TextContent;
use Mcp\Schema\Enum\LoggingLevel;
use Mcp\Schema\Enum\Role;
use Mcp\Schema\Notification\LoggingMessageNotification;
use Mcp\Schema\Request\CreateSamplingMessageRequest;
use Mcp\Schema\Result\CreateSamplingMessageResult;
// Configure logging notification handler
$loggingHandler = new LoggingNotificationHandler(
static function (LoggingMessageNotification $notification) {
echo "[LOG {$notification->level->value}] {$notification->data}\n";
}
);
// Configure sampling callback
$samplingCallback = new class implements SamplingCallbackInterface {
public function __invoke(CreateSamplingMessageRequest $request): CreateSamplingMessageResult
{
echo "[SAMPLING] Processing request (max {$request->maxTokens} tokens)\n";
try {
// Integration with your LLM provider
$response = "This is a mock LLM response for: " .
json_encode($request->messages);
return new CreateSamplingMessageResult(
role: Role::Assistant,
content: new TextContent($response),
model: 'mock-llm',
stopReason: 'end_turn',
);
} catch (\Throwable $e) {
throw new SamplingException(
"Sampling failed: {$e->getMessage()}",
0,
$e
);
}
}
};
// Build client
$client = Client::builder()
->setClientInfo('Example Client', '1.0.0')
->setInitTimeout(30)
->setRequestTimeout(120)
->setCapabilities(new ClientCapabilities(sampling: true))
->addNotificationHandler($loggingHandler)
->addRequestHandler(new SamplingRequestHandler($samplingCallback))
->build();
// Create transport
$transport = new StdioTransport(
command: 'php',
args: [__DIR__ . '/server.php'],
);
// Connect and use server
try {
echo "Connecting to server...\n";
$client->connect($transport);
// Get server info
$serverInfo = $client->getServerInfo();
echo "Connected to: {$serverInfo->name} v{$serverInfo->version}\n\n";
// List capabilities
echo "Available tools:\n";
$tools = $client->listTools();
foreach ($tools->tools as $tool) {
echo " - {$tool->name}\n";
}
echo "\nAvailable resources:\n";
$resources = $client->listResources();
foreach ($resources->resources as $resource) {
echo " - {$resource->uri}\n";
}
// Set logging level
$client->setLoggingLevel(LoggingLevel::Debug);
// Call tool with progress
echo "\nCalling tool with progress...\n";
$result = $client->callTool(
name: 'process_data',
arguments: ['dataset' => 'large_file.csv'],
onProgress: static function (float $progress, ?float $total, ?string $message) {
$percent = $total > 0 ? round(($progress / $total) * 100) : 0;
echo " Progress: {$percent}% - {$message}\n";
}
);
echo "\nResult:\n";
foreach ($result->content as $content) {
if ($content instanceof TextContent) {
echo $content->text . "\n";
}
}
} catch (\Throwable $e) {
echo "Error: {$e->getMessage()}\n";
echo $e->getTraceAsString() . "\n";
} finally {
$client->disconnect();
echo "\nDisconnected.\n";
}
```

View file

@ -7,7 +7,8 @@ specific features and can be run independently to understand how the SDK works.
- [Getting Started](#getting-started)
- [Running Examples](#running-examples)
- [Examples](#examples)
- [Server Examples](#server-examples)
- [Client Examples](#client-examples)
## Getting Started
@ -58,7 +59,7 @@ curl -X POST http://localhost:8000 \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","clientInfo":{"name":"test","version":"1.0.0"},"capabilities":{}}}'
```
## Examples
## Server Examples
### Discovery Calculator
@ -353,3 +354,149 @@ npx @modelcontextprotocol/inspector php examples/server/elicitation/server.php
1. **book_restaurant** - Multi-field reservation form with number, date, and enum fields
2. **confirm_action** - Simple boolean confirmation dialog
3. **collect_feedback** - Rating and comments form with optional fields
## Client Examples
### STDIO Discovery Calculator (Client)
**File**: `examples/client/stdio_discovery_calculator.php`
**What it demonstrates:**
- Basic MCP client usage with STDIO transport
- Connecting to a local MCP server process
- Listing and calling tools
- Reading resources
**Key Features:**
```php
$client = Client::builder()
->setClientInfo('STDIO Example Client', '1.0.0')
->setInitTimeout(30)
->setRequestTimeout(60)
->build();
$transport = new StdioTransport(
command: 'php',
args: [__DIR__.'/../server/discovery-calculator/server.php'],
);
$client->connect($transport);
$tools = $client->listTools();
$result = $client->callTool('calculate', ['a' => 5, 'b' => 3, 'operation' => 'add']);
$resourceContent = $client->readResource('config://calculator/settings');
```
**Usage:**
```bash
# Run the client (automatically starts the server)
php examples/client/stdio_discovery_calculator.php
```
### HTTP Discovery Calculator (Client)
**File**: `examples/client/http_discovery_calculator.php`
**What it demonstrates:**
- MCP client with HTTP transport
- Connecting to remote MCP servers
- Listing tools, resources, and prompts
**Key Features:**
```php
$transport = new HttpTransport('http://localhost:8000');
$client->connect($transport);
$tools = $client->listTools();
$resources = $client->listResources();
$prompts = $client->listPrompts();
```
**Usage:**
```bash
# Start the server first
php -S localhost:8000 examples/server/http-discovery-calculator/server.php
# Then run the client
php examples/client/http_discovery_calculator.php
```
### STDIO Client Communication
**File**: `examples/client/stdio_client_communication.php`
**What it demonstrates:**
- Server-to-client communication (logging, progress, sampling)
- Handling logging notifications from server
- Implementing sampling callbacks for LLM requests
- Progress tracking during tool execution
**Key Features:**
```php
use Mcp\Client\Handler\Notification\LoggingNotificationHandler;
use Mcp\Client\Handler\Request\SamplingRequestHandler;
use Mcp\Client\Handler\Request\SamplingCallbackInterface;
use Mcp\Schema\ClientCapabilities;
$loggingHandler = new LoggingNotificationHandler(
static function (LoggingMessageNotification $n) {
echo "[LOG {$n->level->value}] {$n->data}\n";
}
);
$samplingHandler = new SamplingRequestHandler(new class implements SamplingCallbackInterface {
public function __invoke(CreateSamplingMessageRequest $request): CreateSamplingMessageResult
{
// Perform LLM sampling and return result
}
});
$client = Client::builder()
->setCapabilities(new ClientCapabilities(sampling: true))
->addNotificationHandler($loggingHandler)
->addRequestHandler($samplingHandler)
->build();
// Call tool with progress tracking
$result = $client->callTool(
name: 'run_dataset_quality_checks',
arguments: ['dataset' => 'customer_orders_2024'],
onProgress: static function (float $progress, ?float $total, ?string $message) {
$percent = $total > 0 ? round(($progress / $total) * 100) : '?';
echo "[PROGRESS {$percent}%] {$message}\n";
}
);
```
**Usage:**
```bash
# Run the client (automatically starts the communication server)
php examples/client/stdio_client_communication.php
```
### HTTP Client Communication
**File**: `examples/client/http_client_communication.php`
**What it demonstrates:**
- Server-to-client communication over HTTP
- Receiving logging and progress notifications via SSE streaming
- Implementing sampling for HTTP-based servers
- Progress tracking with long-running operations
**Key Features:**
- Same client-side code as STDIO version
- Uses HttpTransport instead of StdioTransport
- Demonstrates SSE-based real-time notifications
- Shows HTTP session management
**Usage:**
```bash
# Start the server
php -S 127.0.0.1:8000 examples/server/client-communication/server.php
# Run the client
php examples/client/http_client_communication.php
```
> [!NOTE]
> For sampling with HTTP transport, the server must support concurrent request processing (e.g., using Symfony CLI, PHP-FPM, or a production web server). PHP's built-in development server cannot handle the concurrent requests required for sampling.

View file

@ -2,6 +2,7 @@
- [MCP Elements](mcp-elements.md) — Core capabilities (Tools, Resources, Resource Templates, and Prompts) with registration methods.
- [Server Builder](server-builder.md) — Fluent builder class for creating and configuring MCP server instances.
- [Client](client.md) — Client SDK for connecting to and communicating with MCP servers.
- [Transports](transports.md) — STDIO and HTTP transport implementations with guidance on choosing between them.
- [Server-Client Communication](server-client-communication.md) — Methods for servers to communicate back to clients: sampling, logging, progress, and notifications.
- [Examples](examples.md) — Example projects demonstrating attribute-based discovery, dependency injection, HTTP transport, and more.