Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/advanced/extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ in an attribute, so spell it as `new \stdClass()` there.
```php
use Mcp\Capability\Attribute\McpResource;
use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Content\TextResourceContents;
use Mcp\Schema\Extension\Apps\McpApps;
use Mcp\Schema\Extension\Apps\ToolVisibility;
use Mcp\Schema\Extension\Apps\UiToolMeta;
Expand Down
27 changes: 16 additions & 11 deletions docs/client/connecting.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,7 @@ use Mcp\Schema\ClientCapabilities;

$client = Client::builder()
->setCapabilities(new ClientCapabilities(
sampling: true, // Enable LLM sampling requests from server
roots: true, // Enable filesystem root listing
elicitation: true, // Let the server ask the user for input
))
->build();
```
Expand All @@ -122,25 +121,31 @@ $client = Client::builder()

### Request Handlers

Register handlers for server-initiated requests (e.g., sampling). The same handlers answer a
Register handlers for server-initiated requests (e.g., elicitation). The same handlers answer a
[multi round-trip](../handlers/input-required.md) `input_required` result on a modern revision, where the server
returns its ask instead of sending a request:

```php
use Mcp\Client\Handler\Request\SamplingRequestHandler;
use Mcp\Client\Handler\Request\SamplingCallbackInterface;
use Mcp\Schema\Request\CreateSamplingMessageRequest;
use Mcp\Schema\Result\CreateSamplingMessageResult;
use Mcp\Client\Handler\Request\ElicitationCallbackInterface;
use Mcp\Client\Handler\Request\ElicitationRequestHandler;
use Mcp\Schema\ClientCapabilities;
use Mcp\Schema\Enum\ElicitAction;
use Mcp\Schema\Request\ElicitRequest;
use Mcp\Schema\Result\ElicitResult;

$samplingCallback = new class implements SamplingCallbackInterface {
public function __invoke(CreateSamplingMessageRequest $request): CreateSamplingMessageResult
$elicitationCallback = new class implements ElicitationCallbackInterface {
public function __invoke(ElicitRequest $request): ElicitResult
{
// Perform LLM sampling and return result
// Ask the user for the requested input and return their answer.
// Without a user interface, decline the request:
return new ElicitResult(ElicitAction::Decline);
}
Comment thread
chr-hertel marked this conversation as resolved.
};

$client = Client::builder()
->addRequestHandler(new SamplingRequestHandler($samplingCallback))
// the server only sends elicitation requests if the client declares the capability
->setCapabilities(new ClientCapabilities(elicitation: true))
->addRequestHandler(new ElicitationRequestHandler($elicitationCallback))
->build();
Comment thread
chr-hertel marked this conversation as resolved.
```

Expand Down
59 changes: 14 additions & 45 deletions docs/client/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,50 +39,23 @@ Here's a comprehensive example demonstrating client usage:
<?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\Handler\Request\ElicitationCallbackInterface;
use Mcp\Client\Handler\Request\ElicitationRequestHandler;
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";
}
);
use Mcp\Schema\Enum\ElicitAction;
use Mcp\Schema\Request\ElicitRequest;
use Mcp\Schema\Result\ElicitResult;

// Configure sampling callback
$samplingCallback = new class implements SamplingCallbackInterface {
public function __invoke(CreateSamplingMessageRequest $request): CreateSamplingMessageResult
// Configure elicitation callback
$elicitationCallback = new class implements ElicitationCallbackInterface {
public function __invoke(ElicitRequest $request): ElicitResult
{
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: 'endTurn',
);
} catch (\Throwable $e) {
throw new SamplingException(
"Sampling failed: {$e->getMessage()}",
0,
$e
);
}
echo "[ELICITATION] {$request->message}\n";

// This client has no user interface, so it declines every request
return new ElicitResult(ElicitAction::Decline);
}
};

Expand All @@ -91,9 +64,8 @@ $client = Client::builder()
->setClientInfo('Example Client', '1.0.0')
->setInitTimeout(30)
->setRequestTimeout(120)
->setCapabilities(new ClientCapabilities(sampling: true))
->addNotificationHandler($loggingHandler)
->addRequestHandler(new SamplingRequestHandler($samplingCallback))
->setCapabilities(new ClientCapabilities(elicitation: true))
->addRequestHandler(new ElicitationRequestHandler($elicitationCallback))
->build();

// Create transport
Expand Down Expand Up @@ -124,9 +96,6 @@ try {
echo " - {$resource->uri}\n";
}

// Set logging level
$client->setLoggingLevel(LoggingLevel::Debug);

// Call tool with progress
echo "\nCalling tool with progress...\n";
$result = $client->callTool(
Expand Down
20 changes: 7 additions & 13 deletions docs/client/server-requests.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,22 +190,16 @@ Receive structured log messages from the server:

```php
use Mcp\Client\Handler\Notification\LoggingNotificationHandler;
use Mcp\Schema\Notification\LoggingMessageNotification;
use Mcp\Schema\Enum\LoggingLevel;
use Mcp\Schema\Notification\LoggingMessageNotification;

// $logger is your application's PSR-3 logger
$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),
};
static function (LoggingMessageNotification $notification) use ($logger) {
$message = \is_string($notification->data) ? $notification->data : json_encode($notification->data);

// MCP log levels use the same names as the PSR-3 log levels
$logger->log($notification->level->value, $message);
}
);

Expand Down
12 changes: 12 additions & 0 deletions docs/run/protocol-eras.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@
which of them answers. There is nothing to configure:

```php
use Http\Discovery\Psr17Factory;
use Laminas\HttpHandlerRunner\Emitter\SapiEmitter;
use Mcp\Server;
use Mcp\Server\Transport\StreamableHttpTransport;

$request = (new Psr17Factory())->createServerRequestFromGlobals();

$server = Server::builder()
->setServerInfo('My Server', '1.0.0')
->addTool(static fn (string $city): string => "17°C in {$city}", name: 'get_weather', description: '…')
Expand Down Expand Up @@ -128,6 +132,14 @@ For the opposite — an endpoint that serves the modern era and nothing else —
dispatcher on its own and mount it on `StatelessHttpTransport`:

```php
use Http\Discovery\Psr17Factory;
use Laminas\HttpHandlerRunner\Emitter\SapiEmitter;
use Mcp\Schema\Enum\ProtocolVersion;
use Mcp\Server;
use Mcp\Server\Transport\StatelessHttpTransport;

$request = (new Psr17Factory())->createServerRequestFromGlobals();

$protocol = Server::builder()
->setServerInfo('My Server', '1.0.0')
->buildStateless([ProtocolVersion::V2026_07_28]);
Expand Down
9 changes: 5 additions & 4 deletions docs/sdk-tier.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ where the PHP SDK stands and what's still missing to move up a tier.
(2026-08-19, superseding the earlier Tier 3 assessment in #2305). Two things
block Tier 2:

- **Client conformance is 20% (10/50)**, against the ≥80% bar. Almost
entirely OAuth: 38 of 39 scored auth scenarios fail. Every one of those
- **Client conformance was 20% (10/50) in that audit**, against the ≥80% bar.
Almost entirely OAuth: 38 of 39 scored auth scenarios failed. Every one of those
failures is pre-declared in the SDK's own
[`tests/Conformance/conformance-baseline-*.yml`](https://github.com/modelcontextprotocol/php-sdk/tree/main/tests/Conformance)
files and tracked in `ROADMAP.md` — a known, scoped gap, not silent
breakage. Server conformance is 100% (67/67).
- **No stable release ≥ 1.0.0 has ever shipped** (latest: v0.7.1). Tier 2
breakage. Server conformance was 100% (67/67).
- **No stable release ≥ 1.0.0 has shipped yet**, see the
[releases](https://github.com/modelcontextprotocol/php-sdk/releases). Tier 2
requires at least one.

Tier 1 needs both of those plus full (not ≥80%) client conformance and
Expand Down
Loading