From d9b3c0ba4e9d25727912b217294b201a85643a57 Mon Sep 17 00:00:00 2001 From: Christopher Hertel Date: Fri, 14 Aug 2026 08:22:20 +0200 Subject: [PATCH] [Client] Reject non-positive timeouts in Configuration (#415) * [Client] Reject non-positive timeouts in Configuration * [Client] Drop redundant comments on timeout validation --- src/Client/Configuration.php | 8 +++ tests/Unit/Client/ConfigurationTest.php | 92 +++++++++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 tests/Unit/Client/ConfigurationTest.php diff --git a/src/Client/Configuration.php b/src/Client/Configuration.php index 8c7e909..98ad2d8 100644 --- a/src/Client/Configuration.php +++ b/src/Client/Configuration.php @@ -11,6 +11,7 @@ namespace Mcp\Client; +use Mcp\Exception\InvalidArgumentException; use Mcp\Schema\ClientCapabilities; use Mcp\Schema\Enum\ProtocolVersion; use Mcp\Schema\Implementation; @@ -30,5 +31,12 @@ class Configuration public readonly int $requestTimeout = 120, public readonly int $maxRetries = 3, ) { + if ($initTimeout < 1) { + throw new InvalidArgumentException(\sprintf('The initialization timeout must be a positive number of seconds, got %d.', $initTimeout)); + } + + if ($requestTimeout < 1) { + throw new InvalidArgumentException(\sprintf('The request timeout must be a positive number of seconds, got %d.', $requestTimeout)); + } } } diff --git a/tests/Unit/Client/ConfigurationTest.php b/tests/Unit/Client/ConfigurationTest.php new file mode 100644 index 0000000..ae50a49 --- /dev/null +++ b/tests/Unit/Client/ConfigurationTest.php @@ -0,0 +1,92 @@ +expectException(InvalidArgumentException::class); + $this->expectExceptionMessage(\sprintf('The initialization timeout must be a positive number of seconds, got %d.', $seconds)); + + $this->createConfiguration(initTimeout: $seconds); + } + + #[TestDox('a non-positive request timeout of $seconds seconds is rejected')] + #[DataProvider('provideNonPositiveTimeouts')] + public function testNonPositiveRequestTimeoutIsRejected(int $seconds): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage(\sprintf('The request timeout must be a positive number of seconds, got %d.', $seconds)); + + $this->createConfiguration(requestTimeout: $seconds); + } + + /** + * @return iterable + */ + public static function provideNonPositiveTimeouts(): iterable + { + yield 'zero' => [0]; + yield 'negative' => [-1]; + } + + #[TestDox('the builder rejects a non-positive initialization timeout')] + public function testBuilderRejectsNonPositiveInitTimeout(): void + { + $builder = Client::builder()->setInitTimeout(0); + + $this->expectException(InvalidArgumentException::class); + + $builder->build(); + } + + #[TestDox('the builder rejects a non-positive request timeout')] + public function testBuilderRejectsNonPositiveRequestTimeout(): void + { + $builder = Client::builder()->setRequestTimeout(-5); + + $this->expectException(InvalidArgumentException::class); + + $builder->build(); + } + + #[TestDox('positive timeouts are accepted')] + public function testPositiveTimeoutsAreAccepted(): void + { + $config = $this->createConfiguration(initTimeout: 1, requestTimeout: 1); + + $this->assertSame(1, $config->initTimeout); + $this->assertSame(1, $config->requestTimeout); + } + + private function createConfiguration(int $initTimeout = 30, int $requestTimeout = 120): Configuration + { + return new Configuration( + clientInfo: new Implementation('test-client', '1.0.0'), + capabilities: new ClientCapabilities(), + initTimeout: $initTimeout, + requestTimeout: $requestTimeout, + ); + } +}