From 47abee707ec2692eef607e3fbd815014c554f6bb Mon Sep 17 00:00:00 2001 From: Christopher Hertel Date: Wed, 7 Oct 2026 23:22:55 +0200 Subject: [PATCH 1/2] [Examples] Ask for log messages in the client communication examples --- examples/client/http_client_communication.php | 4 ++++ examples/client/stdio_client_communication.php | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/examples/client/http_client_communication.php b/examples/client/http_client_communication.php index a1df5fa0..afd0dc1a 100644 --- a/examples/client/http_client_communication.php +++ b/examples/client/http_client_communication.php @@ -38,6 +38,7 @@ use Mcp\Client\Transport\HttpTransport; 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; @@ -83,6 +84,9 @@ public function __invoke(CreateSamplingMessageRequest $request): CreateSamplingM echo "Connecting to MCP server at {$endpoint}...\n"; $client->connect($transport); + // Ask for log messages: from 2026-07-28 on a server sends none unless asked. + $client->setLoggingLevel(LoggingLevel::Info); + $serverInfo = $client->getServerInfo(); echo 'Connected to: '.($serverInfo->name ?? 'unknown')."\n\n"; diff --git a/examples/client/stdio_client_communication.php b/examples/client/stdio_client_communication.php index 453ab6db..38f08b9b 100644 --- a/examples/client/stdio_client_communication.php +++ b/examples/client/stdio_client_communication.php @@ -29,6 +29,7 @@ use Mcp\Client\Transport\StdioTransport; 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; @@ -75,6 +76,9 @@ public function __invoke(CreateSamplingMessageRequest $request): CreateSamplingM echo "Connecting to MCP server...\n"; $client->connect($transport); + // Ask for log messages: from 2026-07-28 on a server sends none unless asked. + $client->setLoggingLevel(LoggingLevel::Info); + $serverInfo = $client->getServerInfo(); echo 'Connected to: '.($serverInfo->name ?? 'unknown')."\n\n"; From 90a65f9e44a903eecc45630ce85f1fc92c5cba60 Mon Sep 17 00:00:00 2001 From: Christopher Hertel Date: Wed, 7 Oct 2026 00:14:54 +0200 Subject: [PATCH 2/2] [Client] Default to protocol version 2026-07-28 --- CHANGELOG.md | 2 + docs/client/connecting.md | 25 ++++++----- docs/protocol-versions.md | 14 +++---- examples/client/stdio_roots.php | 8 ++-- src/Client/Builder.php | 4 +- src/Client/Configuration.php | 2 +- src/Schema/JsonRpc/MessageInterface.php | 2 +- src/Schema/Result/InitializeResult.php | 3 +- tests/Integration/ElicitationTest.php | 6 ++- tests/Integration/Fixture/roots.php | 20 +++++++++ tests/Integration/Fixture/sampling.php | 22 ++++++++++ tests/Integration/HandshakeTest.php | 6 ++- tests/Integration/RootsTest.php | 30 +++++++++++++- tests/Integration/SamplingTest.php | 27 +++++++++++- tests/Integration/SamplingToolsTest.php | 10 +++++ tests/Unit/Client/ConfigurationTest.php | 5 ++- .../Client/Transport/HttpTransportTest.php | 3 +- .../Schema/Result/InitializeResultTest.php | 41 +++++++++++++++++++ 18 files changed, 191 insertions(+), 39 deletions(-) create mode 100644 tests/Unit/Schema/Result/InitializeResultTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index ad49825c..8f4284ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,8 +40,10 @@ All notable changes to `mcp/sdk` will be documented in this file. * [BC Break] `StatelessAwareTransportInterface` declares `setHandshakeVersions()`, so a server without the modern era names only the revisions it negotiates when refusing a `2026-07-28` request, e.g. the one set with `Builder::setProtocolVersion()`. * Answer a bare `initialize` on a `2026-07-28`-only endpoint with `-32022` naming the served revisions, and a request without a session on the handshake leg with its id. * Speak both protocol eras from a client configured with `2026-07-28`: `connect()` probes with `server/discover` and falls back to the `initialize` handshake on `2025-11-25` when the server does not speak the modern era. `Builder::setFallbackProtocolVersion()` picks the fallback revision, or `null` for a modern-only client. +* [BC Break] Bump the client's default protocol version to `2026-07-28`, so a client speaks both protocol eras unless told otherwise. Against a server that speaks both, a default client now settles on `2026-07-28`, where a server can no longer sample or list roots through `ClientGateway`; return those asks as an `InputRequiredResult` instead, or call `Builder::setProtocolVersion(ProtocolVersion::V2025_11_25)` on the client. * On a `2026-07-28` connection, `Client::setLoggingLevel()` stamps the level on every following request, `Client::ping()` sends `server/discover` and `Client::sendRootsListChanged()` sends nothing. * Fail a client request at once when the HTTP server refuses it with an error status or the stdio server process exits, instead of waiting out the timeout. +* [BC Break] Bump `MessageInterface::PROTOCOL_VERSION` to `2026-07-28`. Use `ProtocolVersion::latestHandshake()` where a handshake revision is needed, e.g. in an `initialize` answer. 0.8.0 ----- diff --git a/docs/client/connecting.md b/docs/client/connecting.md index d8ca4fca..78295959 100644 --- a/docs/client/connecting.md +++ b/docs/client/connecting.md @@ -61,34 +61,33 @@ $client = Client::builder() ### Protocol Version -Specify the MCP protocol version to speak (defaults to `V2025_11_25`). A handshake revision opens with `initialize`: - -```php -use Mcp\Schema\Enum\ProtocolVersion; - -$client = Client::builder() - ->setProtocolVersion(ProtocolVersion::V2025_11_25) - ->build(); -``` - -A modern revision makes the client speak both protocol eras: it probes for `2026-07-28` with `server/discover` when +A client speaks both protocol eras by default. It prefers `2026-07-28`, probes for it with `server/discover` when connecting, and falls back to the `initialize` handshake on `2025-11-25` when the server turns out not to speak it. Use `$client->getProtocolVersion()` after connecting to read what the connection settled on. ```php +use Mcp\Schema\Enum\ProtocolVersion; + // Fall back to an older handshake revision instead of 2025-11-25… $client = Client::builder() - ->setProtocolVersion(ProtocolVersion::V2026_07_28) ->setFallbackProtocolVersion(ProtocolVersion::V2025_06_18) ->build(); // …or not at all, refusing servers without the modern era. $client = Client::builder() - ->setProtocolVersion(ProtocolVersion::V2026_07_28) ->setFallbackProtocolVersion(null) ->build(); ``` +Passing a handshake revision to `setProtocolVersion()` skips the probe and opens with `initialize`, the way a client +from before the modern era would: + +```php +$client = Client::builder() + ->setProtocolVersion(ProtocolVersion::V2025_11_25) + ->build(); +``` + The handshake is an offer, not a demand. A server that does not support the requested revision counter-offers one it does, as described in the specification's [protocol version negotiation](https://modelcontextprotocol.io/specification/latest/basic/versioning#protocol-version-negotiation) diff --git a/docs/protocol-versions.md b/docs/protocol-versions.md index 7d4f2b86..a406438a 100644 --- a/docs/protocol-versions.md +++ b/docs/protocol-versions.md @@ -104,14 +104,13 @@ either lifecycle. What changes: ## Speaking it from a client -Ask for `2026-07-28` and the client speaks both eras: it finds out on `connect()` whether -the server does too, and nothing about the [client API](client/index.md) depends on the -answer. +A client speaks both eras out of the box. It prefers `2026-07-28`, and finds out on +`connect()` whether the server does too; nothing about the [client API](client/index.md) +depends on the answer. ```php $client = Client::builder() ->setClientInfo('my-client', '1.0.0') - ->setProtocolVersion(ProtocolVersion::V2026_07_28) ->setCapabilities(new ClientCapabilities(elicitation: true)) ->addRequestHandler($myElicitationHandler) ->build(); @@ -153,9 +152,10 @@ and opens with `initialize`, as a client from before the modern era would. Once modern, a handful of calls change shape under the same API: `setLoggingLevel()` rides on every following request instead of sending the removed `logging/setLevel`, `ping()` becomes a -`server/discover`, and `sendRootsListChanged()` sends nothing, since roots are gone. Sampling -and roots are handshake-era features: a server asking for them on a modern connection fails the -call instead. +`server/discover`, and `sendRootsListChanged()` sends nothing, since the server asks for roots +with each call that needs them. A server can still ask for sampling and roots by +[returning the ask](handlers/input-required.md); calling out for them through `ClientGateway` +is handshake-era only and fails the call on a modern connection. What being modern changes underneath: diff --git a/examples/client/stdio_roots.php b/examples/client/stdio_roots.php index b10e7431..9345eada 100644 --- a/examples/client/stdio_roots.php +++ b/examples/client/stdio_roots.php @@ -13,12 +13,13 @@ * STDIO Roots Example. * * This example demonstrates the "roots" client capability: - * - The client advertises the `roots` capability during initialization. + * - The client declares the `roots` capability. * - It answers server `roots/list` requests via a RootsCallbackInterface, * exposing a couple of `file://` workspace folders. * - Calling the server's `inspect_workspace_roots` tool makes the server issue * a `roots/list` request, so the handler below actually runs. - * - It notifies the server when its list of roots changes. + * - It notifies the server when its list of roots changes, which only a + * connection on the handshake era (before 2026-07-28) sends. * * Usage: php examples/client/stdio_roots.php */ @@ -78,7 +79,8 @@ public function __invoke(ListRootsRequest $request): ListRootsResult } // Whenever the client's workspace folders change, notify the server so it can -// request an updated list via roots/list. +// request an updated list via roots/list. On 2026-07-28 this sends nothing: the +// server asks for the roots again with each call that needs them. echo "\nNotifying the server that the roots list changed...\n"; $client->sendRootsListChanged(); diff --git a/src/Client/Builder.php b/src/Client/Builder.php index aefd1a47..290eda04 100644 --- a/src/Client/Builder.php +++ b/src/Client/Builder.php @@ -67,7 +67,7 @@ public function setClientInfo(string $name, string $version, ?string $descriptio } /** - * Set the protocol version the client prefers, defaults to 2025-11-25; a modern one is probed for first. + * Set the protocol version the client prefers, defaults to 2026-07-28; a modern one is probed for first. */ public function setProtocolVersion(ProtocolVersion $protocolVersion): self { @@ -209,7 +209,7 @@ public function build(): Client $config = new Configuration( clientInfo: $clientInfo, capabilities: $capabilities, - protocolVersion: $this->protocolVersion ?? ProtocolVersion::V2025_11_25, + protocolVersion: $this->protocolVersion ?? ProtocolVersion::V2026_07_28, initTimeout: $this->initTimeout, requestTimeout: $this->requestTimeout, maxRetries: $this->maxRetries, diff --git a/src/Client/Configuration.php b/src/Client/Configuration.php index ea79542b..ac668c57 100644 --- a/src/Client/Configuration.php +++ b/src/Client/Configuration.php @@ -29,7 +29,7 @@ class Configuration public function __construct( public readonly Implementation $clientInfo, public readonly ClientCapabilities $capabilities, - public readonly ProtocolVersion $protocolVersion = ProtocolVersion::V2025_11_25, + public readonly ProtocolVersion $protocolVersion = ProtocolVersion::V2026_07_28, public readonly int $initTimeout = 30, public readonly int $requestTimeout = 120, public readonly int $maxRetries = 3, diff --git a/src/Schema/JsonRpc/MessageInterface.php b/src/Schema/JsonRpc/MessageInterface.php index 9427eaac..9de53118 100644 --- a/src/Schema/JsonRpc/MessageInterface.php +++ b/src/Schema/JsonRpc/MessageInterface.php @@ -21,5 +21,5 @@ interface MessageInterface extends \JsonSerializable { public const JSONRPC_VERSION = '2.0'; - public const PROTOCOL_VERSION = ProtocolVersion::V2025_11_25; + public const PROTOCOL_VERSION = ProtocolVersion::V2026_07_28; } diff --git a/src/Schema/Result/InitializeResult.php b/src/Schema/Result/InitializeResult.php index 39e036d3..05c0f502 100644 --- a/src/Schema/Result/InitializeResult.php +++ b/src/Schema/Result/InitializeResult.php @@ -14,7 +14,6 @@ use Mcp\Exception\InvalidArgumentException; use Mcp\Schema\Enum\ProtocolVersion; use Mcp\Schema\Implementation; -use Mcp\Schema\JsonRpc\MessageInterface; use Mcp\Schema\JsonRpc\Response; use Mcp\Schema\JsonRpc\ResultInterface; use Mcp\Schema\ServerCapabilities; @@ -91,7 +90,7 @@ public static function fromArray(array $data): self */ public function jsonSerialize(): array { - $protocolVersion = $this->protocolVersion ?? MessageInterface::PROTOCOL_VERSION; + $protocolVersion = $this->protocolVersion ?? ProtocolVersion::latestHandshake(); $data = [ 'protocolVersion' => $protocolVersion->value, 'capabilities' => $this->capabilities, diff --git a/tests/Integration/ElicitationTest.php b/tests/Integration/ElicitationTest.php index b421ff5e..b679068e 100644 --- a/tests/Integration/ElicitationTest.php +++ b/tests/Integration/ElicitationTest.php @@ -64,7 +64,7 @@ public function testDeclinedElicitation(): void public function testCapabilityIsVisibleToTheServer(): void { // The tool consults supportsElicitation(), which answers from the - // capabilities this client sent during the handshake. + // capabilities this client declared. $client = $this->connect('elicitation'); $result = $client->callTool('ask_name'); @@ -80,7 +80,9 @@ public function testAdvertisedCapabilityWithoutHandler(): void // the tool as a ClientException rather than leaving it waiting. $client = $this->connect( 'elicitation', - $this->clientBuilder()->setCapabilities(new ClientCapabilities(elicitation: true)), + $this->clientBuilder() + ->setProtocolVersion(ProtocolVersion::V2025_11_25) + ->setCapabilities(new ClientCapabilities(elicitation: true)), ); $result = $client->callTool('ask_name'); diff --git a/tests/Integration/Fixture/roots.php b/tests/Integration/Fixture/roots.php index a4b63824..244f9eb8 100644 --- a/tests/Integration/Fixture/roots.php +++ b/tests/Integration/Fixture/roots.php @@ -13,6 +13,8 @@ * Server for {@see \Mcp\Tests\Integration\RootsTest}. */ +use Mcp\Schema\Request\ListRootsRequest; +use Mcp\Schema\Result\InputRequiredResult; use Mcp\Server; use Mcp\Server\RequestContext; use Mcp\Server\Transport\StdioTransport; @@ -39,5 +41,23 @@ static function (RequestContext $context): string { name: 'inspect_roots', description: 'Reports the workspace roots the client exposes.', ) + ->addTool( + static function (RequestContext $context): string|InputRequiredResult { + $result = $context->getInputContext()?->rootsResult('roots'); + + if (null === $result) { + return new InputRequiredResult(['roots' => new ListRootsRequest()]); + } + + $described = []; + foreach ($result->roots as $root) { + $described[] = sprintf('%s (%s)', $root->uri, $root->name ?? '-'); + } + + return implode(', ', $described); + }, + name: 'inspect_roots_by_asking', + description: 'Reports the workspace roots by returning a roots ask.', + ) ->build() ->run(new StdioTransport()); diff --git a/tests/Integration/Fixture/sampling.php b/tests/Integration/Fixture/sampling.php index 5ea62c8a..59a651c7 100644 --- a/tests/Integration/Fixture/sampling.php +++ b/tests/Integration/Fixture/sampling.php @@ -14,7 +14,11 @@ */ use Mcp\Exception\ClientException; +use Mcp\Schema\Content\SamplingMessage; use Mcp\Schema\Content\TextContent; +use Mcp\Schema\Enum\Role; +use Mcp\Schema\Request\CreateSamplingMessageRequest; +use Mcp\Schema\Result\InputRequiredResult; use Mcp\Server; use Mcp\Server\ClientGateway; use Mcp\Server\RequestContext; @@ -54,5 +58,23 @@ static function (ClientGateway $client, string $text): string { name: 'summarize_via_gateway', description: 'Summarizes text through a directly injected gateway.', ) + ->addTool( + static function (RequestContext $context, string $text): string|InputRequiredResult { + $result = $context->getInputContext()?->samplingResult('summary'); + + if (null === $result) { + return new InputRequiredResult(['summary' => new CreateSamplingMessageRequest( + messages: [new SamplingMessage(Role::User, new TextContent($text))], + maxTokens: 64, + )]); + } + + assert($result->content instanceof TextContent); + + return sprintf('%s said: %s', $result->model, $result->content->text); + }, + name: 'summarize_by_asking', + description: 'Summarizes text by returning a sampling ask.', + ) ->build() ->run(new StdioTransport()); diff --git a/tests/Integration/HandshakeTest.php b/tests/Integration/HandshakeTest.php index c2539cab..7cf1ee4a 100644 --- a/tests/Integration/HandshakeTest.php +++ b/tests/Integration/HandshakeTest.php @@ -47,7 +47,9 @@ public function testNegotiatedVersion(?ProtocolVersion $clientVersion, ?Protocol */ public static function provideNegotiations(): iterable { - yield 'both unconfigured' => [null, null, ProtocolVersion::latestHandshake()]; + // Both ends speak both eras, so they settle on the modern one. + yield 'both unconfigured' => [null, null, ProtocolVersion::V2026_07_28]; + yield 'server without the modern era, client unconfigured' => [null, null, ProtocolVersion::V2025_11_25, true]; // Whichever end of the supported range it sits at. foreach (ProtocolVersion::handshakeVersions() as $version) { @@ -140,7 +142,7 @@ private static function environment(?ProtocolVersion $serverVersion, bool $hands return $env; } - #[TestDox('the handshake carries the server capabilities to the client')] + #[TestDox('the server capabilities reach the client on connect')] public function testServerCapabilitiesAreExchanged(): void { $capabilities = $this->connect('handshake')->getServerCapabilities(); diff --git a/tests/Integration/RootsTest.php b/tests/Integration/RootsTest.php index 6dfd0650..6cc16455 100644 --- a/tests/Integration/RootsTest.php +++ b/tests/Integration/RootsTest.php @@ -16,6 +16,7 @@ use Mcp\Client\Handler\Request\RootsCallbackInterface; use Mcp\Schema\ClientCapabilities; use Mcp\Schema\Content\TextContent; +use Mcp\Schema\Enum\ProtocolVersion; use Mcp\Schema\Request\ListRootsRequest; use Mcp\Schema\Result\ListRootsResult; use Mcp\Schema\Root; @@ -28,6 +29,15 @@ */ final class RootsTest extends IntegrationTestCase { + /** + * The gateway call-out exists only on the handshake era, so a client and + * server that could both settle on the modern era are kept off it. + */ + protected function clientBuilder(): ClientBuilder + { + return parent::clientBuilder()->setProtocolVersion(ProtocolVersion::V2025_11_25); + } + #[TestDox('the roots the client exposes reach the tool that asked')] public function testRootsReachTheTool(): void { @@ -77,7 +87,25 @@ public function testRootsListChangedNotification(): void $this->assertInstanceOf(TextContent::class, $client->callTool('inspect_roots')->content[0]); } + #[TestDox('a roots ask the tool returns is answered on the modern era a default client settles on')] + public function testReturnedAskIsAnsweredOnTheModernEra(): void + { + $client = $this->connect('roots', $this->exposing(parent::clientBuilder(), new Root('file:///workspace/app', 'App'))); + + $this->assertSame(ProtocolVersion::V2026_07_28, $client->getProtocolVersion()); + + $result = $client->callTool('inspect_roots_by_asking'); + + $this->assertInstanceOf(TextContent::class, $result->content[0]); + $this->assertSame('file:///workspace/app (App)', $result->content[0]->text); + } + private function clientExposing(Root ...$roots): ClientBuilder + { + return $this->exposing($this->clientBuilder(), ...$roots); + } + + private function exposing(ClientBuilder $builder, Root ...$roots): ClientBuilder { $callback = new class(array_values($roots)) implements RootsCallbackInterface { /** @param list $roots */ @@ -91,7 +119,7 @@ public function __invoke(ListRootsRequest $request): ListRootsResult } }; - return $this->clientBuilder() + return $builder ->setCapabilities(new ClientCapabilities(roots: true, rootsListChanged: true)) ->addRequestHandler(new ListRootsRequestHandler($callback)); } diff --git a/tests/Integration/SamplingTest.php b/tests/Integration/SamplingTest.php index 2b61acb0..dc48c8ce 100644 --- a/tests/Integration/SamplingTest.php +++ b/tests/Integration/SamplingTest.php @@ -30,6 +30,15 @@ */ final class SamplingTest extends IntegrationTestCase { + /** + * The gateway call-out exists only on the handshake era, so a client and + * server that could both settle on the modern era are kept off it. + */ + protected function clientBuilder(): ClientBuilder + { + return parent::clientBuilder()->setProtocolVersion(ProtocolVersion::V2025_11_25); + } + #[TestDox('the sampled completion reaches the tool that asked for it')] public function testSampledCompletionReachesTheTool(): void { @@ -115,10 +124,24 @@ public function testSamplingIsUnavailableOnTheModernEra(): void } } + #[TestDox('a sampling ask the tool returns is answered on the modern era a default client settles on')] + public function testReturnedAskIsAnsweredOnTheModernEra(): void + { + $client = $this->connect('sampling', $this->clientSampling(on: parent::clientBuilder())); + + $this->assertSame(ProtocolVersion::V2026_07_28, $client->getProtocolVersion()); + + $result = $client->callTool('summarize_by_asking', ['text' => 'a long report']); + + $this->assertInstanceOf(TextContent::class, $result->content[0]); + $this->assertSame('test-model said: a long report', $result->content[0]->text); + } + /** * @param \ArrayObject|null $seen collects what the server asked for + * @param ClientBuilder|null $on a builder other than this test's era-pinned one */ - private function clientSampling(?\ArrayObject $seen = null): ClientBuilder + private function clientSampling(?\ArrayObject $seen = null, ?ClientBuilder $on = null): ClientBuilder { $callback = new class($seen ?? new \ArrayObject()) implements SamplingCallbackInterface { /** @param \ArrayObject $seen */ @@ -137,7 +160,7 @@ public function __invoke(CreateSamplingMessageRequest $request): CreateSamplingM } }; - return $this->clientBuilder() + return ($on ?? $this->clientBuilder()) ->setCapabilities(new ClientCapabilities(sampling: true)) ->addRequestHandler(new SamplingRequestHandler($callback)); } diff --git a/tests/Integration/SamplingToolsTest.php b/tests/Integration/SamplingToolsTest.php index 58725346..661d236a 100644 --- a/tests/Integration/SamplingToolsTest.php +++ b/tests/Integration/SamplingToolsTest.php @@ -18,6 +18,7 @@ use Mcp\Schema\Content\TextContent; use Mcp\Schema\Content\ToolResultContent; use Mcp\Schema\Content\ToolUseContent; +use Mcp\Schema\Enum\ProtocolVersion; use Mcp\Schema\Enum\Role; use Mcp\Schema\Request\CreateSamplingMessageRequest; use Mcp\Schema\Result\CreateSamplingMessageResult; @@ -30,6 +31,15 @@ */ final class SamplingToolsTest extends IntegrationTestCase { + /** + * The gateway call-out exists only on the handshake era, so a client and + * server that could both settle on the modern era are kept off it. + */ + protected function clientBuilder(): ClientBuilder + { + return parent::clientBuilder()->setProtocolVersion(ProtocolVersion::V2025_11_25); + } + #[TestDox('the server runs a full tool loop and gets the model\'s final answer')] public function testToolLoopCompletes(): void { diff --git a/tests/Unit/Client/ConfigurationTest.php b/tests/Unit/Client/ConfigurationTest.php index 52111d4a..24146b81 100644 --- a/tests/Unit/Client/ConfigurationTest.php +++ b/tests/Unit/Client/ConfigurationTest.php @@ -52,11 +52,12 @@ public static function provideNonPositiveTimeouts(): iterable yield 'negative' => [-1]; } - #[TestDox('falls back to the newest handshake revision by default')] - public function testDefaultsToTheNewestHandshakeFallback(): void + #[TestDox('prefers the modern era and falls back to the newest handshake revision by default')] + public function testDefaultsToModernWithHandshakeFallback(): void { $config = new Configuration(new Implementation('client', '1.0.0'), new ClientCapabilities()); + $this->assertSame(ProtocolVersion::V2026_07_28, $config->protocolVersion); $this->assertSame(ProtocolVersion::latestHandshake(), $config->fallbackProtocolVersion); } diff --git a/tests/Unit/Client/Transport/HttpTransportTest.php b/tests/Unit/Client/Transport/HttpTransportTest.php index 207923b8..5a402896 100644 --- a/tests/Unit/Client/Transport/HttpTransportTest.php +++ b/tests/Unit/Client/Transport/HttpTransportTest.php @@ -548,7 +548,8 @@ public function isCancellationRequested(): bool return new Response(200, ['Content-Type' => 'application/json'], $stream); }); - $client = Client::builder()->setClientInfo('test', '1')->build(); + // Reporting a cancellation is a handshake-era exchange over HTTP, see below. + $client = Client::builder()->setClientInfo('test', '1')->setProtocolVersion(ProtocolVersion::V2025_11_25)->build(); $client->connect(new HttpTransport('http://localhost/mcp', [], $httpClient, $this->factory, $this->factory)); try { diff --git a/tests/Unit/Schema/Result/InitializeResultTest.php b/tests/Unit/Schema/Result/InitializeResultTest.php new file mode 100644 index 00000000..dd5abeba --- /dev/null +++ b/tests/Unit/Schema/Result/InitializeResultTest.php @@ -0,0 +1,41 @@ +jsonSerialize()['protocolVersion']; + + $this->assertSame(ProtocolVersion::latestHandshake()->value, $protocolVersion); + $this->assertFalse(ProtocolVersion::from($protocolVersion)->isModern()); + } + + #[TestDox('keeps an explicitly given revision')] + public function testJsonSerializeKeepsExplicitVersion(): void + { + $result = new InitializeResult(new ServerCapabilities(), new Implementation('server', '1.0.0'), protocolVersion: ProtocolVersion::V2024_11_05); + + $this->assertSame(ProtocolVersion::V2024_11_05->value, $result->jsonSerialize()['protocolVersion']); + } +}