[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 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. 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 ## Installation
```bash ```bash
composer require mcp/sdk 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
<?php use Mcp\Server;
use Mcp\Server\Transport\StdioTransport;
namespace App;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Capability\Attribute\McpResource; 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] #[McpTool]
public function add(int $a, int $b): int public function add(int $a, int $b): int
{ {
return $a + $b; return $a + $b;
} }
/** #[McpResource(uri: 'config://calculator/settings')]
* 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'
)]
public function getSettings(): array 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() $server = Server::builder()
->setServerInfo('Calculator Server', '1.0.0') ->setServerInfo('Calculator Server', '1.0.0')
->setDiscovery(__DIR__, ['.']) ->setDiscovery(__DIR__, ['.']) // Auto-discover attributes
->build(); ->build();
$transport = new StdioTransport(); $transport = new StdioTransport();
$server->run($transport); $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 ### Registration Methods
{
"mcpServers": {
"php-calculator": {
"command": "php",
"args": ["/absolute/path/to/your/server.php"]
}
}
}
```
### 4. Test Your Server There are multiple ways to register your MCP capabilities—choose the approach that best fits your application's architecture:
```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:
**1. Attribute-Based Discovery** — Define capabilities using PHP attributes for automatic discovery:
```php ```php
// Tool with automatic name and description from method
#[McpTool] #[McpTool]
public function generateReport(): string { /* ... */ } public function generateReport(): string { /* ... */ }
// Tool with custom name #[McpResource(uri: 'config://app/settings')]
#[McpTool(name: 'custom_name')]
public function myMethod(): string { /* ... */ }
// Resource with URI and metadata
#[McpResource(uri: 'config://app/settings', mimeType: 'application/json')]
public function getConfig(): array { /* ... */ } public function getConfig(): array { /* ... */ }
``` ```
### Manual Registration **2. Manual Registration** — Register capabilities programmatically without attributes:
Register capabilities programmatically:
```php ```php
$server = Server::builder() $server = Server::builder()
->addTool([MyClass::class, 'myMethod'], 'tool_name') ->addTool([Calculator::class, 'add'], 'add_numbers')
->addResource([MyClass::class, 'getData'], 'data://config') ->addResource([Config::class, 'get'], 'config://app')
->build(); ->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 ```php
$transport = new StdioTransport(); $transport = new StdioTransport();
$server->run($transport); $server->run($transport);
``` ```
**HTTP Transport** (Web-based communication): **2. HTTP Transport** — For web-based servers and distributed systems:
```php ```php
$transport = new StreamableHttpTransport($request, $responseFactory, $streamFactory); $transport = new StreamableHttpTransport($request, $responseFactory, $streamFactory);
$response = $server->run($transport); $response = $server->run($transport);
// Handle $response in your web application
``` ```
### Session Management ### 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 ```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() $server = Server::builder()
->setSession(ttl: 7200) // 2 hours ->setSession(ttl: 7200) // 2 hours
->build(); ->build();
```
// Override with file-based storage **File-Based** (suitable for single-server HTTP deployments):
```php
$server = Server::builder() $server = Server::builder()
->setSession(new FileSessionStore(__DIR__ . '/sessions')) ->setSession(new FileSessionStore(__DIR__ . '/sessions'))
->build(); ->build();
```
// Override with in-memory storage and custom TTL **PSR-16 Cache** (for example with Redis for scaled deployments):
$server = Server::builder() ```php
->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'
);
$server = Server::builder() $server = Server::builder()
->setSession(new Psr16SessionStore( ->setSession(new Psr16SessionStore(
cache: new Psr16Cache($redisAdapter), cache: new Psr16Cache($redisAdapter),
@ -215,60 +156,141 @@ $server = Server::builder()
->build(); ->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 ```php
use Symfony\Component\Cache\Adapter\FilesystemAdapter; use Mcp\Client;
use Symfony\Component\Cache\Psr16Cache; 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() // Connect to a server
->setDiscovery( $transport = new StdioTransport(
basePath: __DIR__, command: 'php',
scanDirs: ['.', 'src'], // Default: ['.', 'src'] args: ['/path/to/server.php'],
excludeDirs: ['vendor'], // Default: ['vendor', 'node_modules'] );
cache: $cache
) $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(); ->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 ## Documentation
**Core Concepts:** ### 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
**Learning:** - **[Server Builder](docs/server-builder.md)** — Complete ServerBuilder reference and configuration
- [Examples](docs/examples.md) - Comprehensive example walkthroughs - **[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:** ### Learning & Examples
- [Model Context Protocol documentation](https://modelcontextprotocol.io)
- [Model Context Protocol specification](https://spec.modelcontextprotocol.io) - **[Examples](docs/examples.md)** — Comprehensive example walkthroughs for servers and clients
- [Officially supported servers](https://github.com/modelcontextprotocol/servers) - **[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 ## PHP Libraries Using the MCP SDK
* [pronskiy/mcp](https://github.com/pronskiy/mcp) - Additional DX layer - [pronskiy/mcp](https://github.com/pronskiy/mcp) — Additional developer experience layer
* [symfony/mcp-bundle](https://github.com/symfony/mcp-bundle) - Symfony integration bundle - [symfony/mcp-bundle](https://github.com/symfony/mcp-bundle) — Symfony integration bundle
* [josbeir/cakephp-synapse](https://github.com/josbeir/cakephp-synapse) - CakePHP integration plugin - [josbeir/cakephp-synapse](https://github.com/josbeir/cakephp-synapse) — CakePHP integration plugin
## Contributing ## Contributing
We are passionate about supporting contributors of all levels of experience and would love to see you get involved in We are passionate about supporting contributors of all levels of experience and would love to see you get involved in the project.
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).
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 ## Credits
The starting point for this SDK was the [PHP-MCP](https://github.com/php-mcp/server) project, initiated by 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.
[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 ## 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 full support for elicitations
- [ ] Implement OAuth2 authentication for server - [ ] Implement OAuth2 authentication for server
- **Client** - **Client**
- [ ] Implement client-side support - [x] Implement client-side support
- [ ] Implement client examples and documentation - [x] Implement client examples and documentation
- **Schema** - **Schema**
- [ ] Implement schema generation based on TS or JSON 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) - [Getting Started](#getting-started)
- [Running Examples](#running-examples) - [Running Examples](#running-examples)
- [Examples](#examples) - [Server Examples](#server-examples)
- [Client Examples](#client-examples)
## Getting Started ## 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":{}}}' -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 ### 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 1. **book_restaurant** - Multi-field reservation form with number, date, and enum fields
2. **confirm_action** - Simple boolean confirmation dialog 2. **confirm_action** - Simple boolean confirmation dialog
3. **collect_feedback** - Rating and comments form with optional fields 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. - [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. - [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. - [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. - [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. - [Examples](examples.md) — Example projects demonstrating attribute-based discovery, dependency injection, HTTP transport, and more.