From c4b163b4c46580cb68ad85785058969b7bed1342 Mon Sep 17 00:00:00 2001 From: Christopher Hertel Date: Sat, 15 Aug 2026 02:11:15 +0200 Subject: [PATCH] [Server] Add ClientGateway::supportsSampling() (#418) Sampling was the one client capability a tool could not check before asking. `supportsRoots()` and `supportsElicitation()` both read the capabilities the client advertised during the handshake; sampling is stored in that same session data but had no accessor, so a tool had to issue the request and catch the refusal. --- CHANGELOG.md | 1 + src/Server/ClientGateway.php | 17 +++++++++++++++++ tests/Unit/Server/ClientGatewayTest.php | 20 ++++++++++++++++++++ 3 files changed, 38 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35fcaf2..162c180 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ All notable changes to `mcp/sdk` will be documented in this file. * Prompt generators returning content as typed arrays (`['type' => 'text', ...]` etc.) no longer lose the optional fields: `annotations` on every content type, and `_meta` and an explicit `mimeType` on embedded resource contents, now carry through to the resulting `PromptMessage` instead of being silently dropped. A missing resource `mimeType` still defaults to `text/plain`/`application/octet-stream` as before. * Add `annotations` support to `ImageContent` (constructor, `fromArray()`, `fromFile()`, `fromString()`, `jsonSerialize()`), matching `TextContent` and `AudioContent`. * Add client-side `roots/list` handler (`ListRootsRequestHandler` + `RootsCallbackInterface`) and `Client::sendRootsListChanged()`, plus server-side `ClientGateway::listRoots()` / `supportsRoots()` and `ListRootsResult::fromArray()`. +* Add `ClientGateway::supportsSampling()`, so a tool can check the client's advertised capabilities before issuing a `sampling/createMessage` request instead of asking and catching the refusal. Matches the existing `supportsRoots()` and `supportsElicitation()`. * Add `Mcp\Schema\Content\ResourceLink` for the spec's `resource_link` content block (protocol revision 2025-06-18+), letting tool results and prompt messages reference a resource by URI/name without embedding its contents. Accepted anywhere `resource` (`EmbeddedResource`) content is (de)serialized: `CallToolResult::fromArray()`, `PromptMessage::fromArray()`, and `PromptResultFormatter`. * Negotiate the protocol revision during the `initialize` handshake: the server echoes a revision it supports and counter-offers `ProtocolVersion::latestHandshake()` otherwise (`Builder::setProtocolVersion()` pins it to exactly one), and the client fails the handshake on a counter-offer it cannot speak rather than continuing on an unagreed revision. Adds `Client::getProtocolVersion()`, the `2026-07-28` revision, and the era helpers on `ProtocolVersion` — revisions from `2026-07-28` on have no `initialize`, so they are excluded from negotiation and from `ProtocolVersionMiddleware`'s default supported set. diff --git a/src/Server/ClientGateway.php b/src/Server/ClientGateway.php index d7d55eb..5205a61 100644 --- a/src/Server/ClientGateway.php +++ b/src/Server/ClientGateway.php @@ -250,6 +250,23 @@ class ClientGateway return \array_key_exists('elicitation', $capabilities); } + /** + * Check if the connected client supports sampling. + * + * Sampling lets a server borrow the client's model during tool execution. + * This method checks the client's advertised capabilities to determine if + * sampling/createMessage requests are supported. + * + * @return bool True if the client supports sampling, false otherwise + */ + public function supportsSampling(): bool + { + $capabilities = (array) $this->session->get('client_capabilities', []); + + // MCP spec: capability presence indicates support (value is typically {} or []) + return \array_key_exists('sampling', $capabilities); + } + /** * Send a request to the client and wait for a response (blocking). * diff --git a/tests/Unit/Server/ClientGatewayTest.php b/tests/Unit/Server/ClientGatewayTest.php index 409669e..e6c2cee 100644 --- a/tests/Unit/Server/ClientGatewayTest.php +++ b/tests/Unit/Server/ClientGatewayTest.php @@ -43,6 +43,26 @@ final class ClientGatewayTest extends TestCase $this->assertFalse($gateway->supportsRoots()); } + public function testSupportsSamplingReturnsTrueWhenAdvertised(): void + { + $session = $this->createMock(SessionInterface::class); + $session->method('get')->with('client_capabilities', [])->willReturn(['sampling' => []]); + + $gateway = new ClientGateway($session); + + $this->assertTrue($gateway->supportsSampling()); + } + + public function testSupportsSamplingReturnsFalseWhenNotAdvertised(): void + { + $session = $this->createMock(SessionInterface::class); + $session->method('get')->with('client_capabilities', [])->willReturn(['roots' => []]); + + $gateway = new ClientGateway($session); + + $this->assertFalse($gateway->supportsSampling()); + } + public function testListRootsReturnsRootsFromClient(): void { $session = $this->createMock(SessionInterface::class);