diff --git a/docs/advanced/extensions.md b/docs/advanced/extensions.md index 041f9862..16ca5baf 100644 --- a/docs/advanced/extensions.md +++ b/docs/advanced/extensions.md @@ -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; diff --git a/docs/client/connecting.md b/docs/client/connecting.md index 1fe01e12..eef0bc7a 100644 --- a/docs/client/connecting.md +++ b/docs/client/connecting.md @@ -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(); ``` @@ -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); } }; $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(); ``` diff --git a/docs/client/errors.md b/docs/client/errors.md index 94e27f99..93fc3022 100644 --- a/docs/client/errors.md +++ b/docs/client/errors.md @@ -39,50 +39,23 @@ Here's a comprehensive example demonstrating client usage: 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); } }; @@ -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 @@ -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( diff --git a/docs/client/server-requests.md b/docs/client/server-requests.md index d25cd5c9..7141ce0f 100644 --- a/docs/client/server-requests.md +++ b/docs/client/server-requests.md @@ -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); } ); diff --git a/docs/run/protocol-eras.md b/docs/run/protocol-eras.md index 9c6cb28f..a5200a8c 100644 --- a/docs/run/protocol-eras.md +++ b/docs/run/protocol-eras.md @@ -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: '…') @@ -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]); diff --git a/docs/sdk-tier.md b/docs/sdk-tier.md index 8fe7e8d3..14c523a5 100644 --- a/docs/sdk-tier.md +++ b/docs/sdk-tier.md @@ -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