From 12012c869132e02c11e295ce411faceb8c48a3ca Mon Sep 17 00:00:00 2001 From: Adam Jamiu Date: Sat, 24 Oct 2026 12:58:10 +0100 Subject: [PATCH 1/2] Fix client transport leak on reconnect Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/client/connecting.md | 2 ++ src/Client.php | 2 ++ tests/Unit/Client/ClientTest.php | 23 +++++++++++++++++++++++ 3 files changed, 27 insertions(+) diff --git a/docs/client/connecting.md b/docs/client/connecting.md index 14f6ea7af..c4e5a15ac 100644 --- a/docs/client/connecting.md +++ b/docs/client/connecting.md @@ -214,6 +214,8 @@ speaks revision `2026-07-28`, the connection is ready. If it doesn't, the client `initialize` handshake and offers `2025-11-25`, or the revision set with `setFallbackProtocolVersion()`. A server process that exits fails the connection instead. +Calling `connect()` again on the same client closes its current transport before opening the new one. + With `setFallbackProtocolVersion(null)`, `connect()` fails instead. See [How the client settles on an era](../protocol-versions.md#how-the-client-settles-on-an-era). diff --git a/src/Client.php b/src/Client.php index f70fdeee5..993511de7 100644 --- a/src/Client.php +++ b/src/Client.php @@ -92,6 +92,8 @@ public static function builder(): Builder */ public function connect(TransportInterface $transport): void { + $this->disconnect(); + $this->transport = $transport; $this->protocol->connect($transport, $this->config); diff --git a/tests/Unit/Client/ClientTest.php b/tests/Unit/Client/ClientTest.php index 29d23ffeb..3c6da9a2a 100644 --- a/tests/Unit/Client/ClientTest.php +++ b/tests/Unit/Client/ClientTest.php @@ -21,6 +21,29 @@ final class ClientTest extends TestCase { + public function testReconnectClosesPreviousTransport(): void + { + $client = Client::builder()->build(); + $firstTransport = $this->createMock(TransportInterface::class); + $firstTransport->expects($this->once())->method('close'); + $firstTransport->method('setState')->willReturnCallback(static function (ClientStateInterface $state): void { + $state->setInitialized(true); + }); + + $secondTransport = $this->createMock(TransportInterface::class); + $secondTransport->expects($this->once())->method('close'); + $secondTransport->method('setState')->willReturnCallback(static function (ClientStateInterface $state): void { + $state->setInitialized(true); + }); + + $client->connect($firstTransport); + $client->connect($secondTransport); + + $this->assertTrue($client->isConnected()); + + $client->disconnect(); + } + public function testSendRootsListChangedSendsNotificationWhenCapabilityDeclared(): void { $sent = []; From 88ebb37d7a17537dbf570651b4b009cbe81933f0 Mon Sep 17 00:00:00 2001 From: Christopher Hertel Date: Tue, 27 Oct 2026 22:09:58 +0100 Subject: [PATCH 2/2] Move reconnect note to Disconnecting and add changelog entry --- CHANGELOG.md | 4 ++++ docs/client/connecting.md | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af2c10576..83668bccf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ All notable changes to `mcp/sdk` will be documented in this file. * Fix tools with an array shape parameter whose keys are nullable or unions (e.g. `array{name: string, nickname: string|null}`) rejecting valid arguments with `-32602`: a `null` inside the shape no longer turns the parameter into `array|null`, and such keys get their own types instead of `object`, including lists and nested shapes (`string[]|null`, `array{x: int}|null`). * Fix `array` and other generics with a union element getting `items` of type `object`. A `null` inside a generic no longer makes the parameter nullable, and neither does a DocBlock class whose name contains `null`. +### Client + +* Fix `Client::connect()` leaving the previous transport open when called again: it closes the current transport first, so no stdio process or HTTP session is left behind. + 0.9.0 ----- diff --git a/docs/client/connecting.md b/docs/client/connecting.md index c4e5a15ac..206641184 100644 --- a/docs/client/connecting.md +++ b/docs/client/connecting.md @@ -214,8 +214,6 @@ speaks revision `2026-07-28`, the connection is ready. If it doesn't, the client `initialize` handshake and offers `2025-11-25`, or the revision set with `setFallbackProtocolVersion()`. A server process that exits fails the connection instead. -Calling `connect()` again on the same client closes its current transport before opening the new one. - With `setFallbackProtocolVersion(null)`, `connect()` fails instead. See [How the client settles on an era](../protocol-versions.md#how-the-client-settles-on-an-era). @@ -250,6 +248,8 @@ try { } ``` +Calling `connect()` again on the same client closes its current transport before opening the new one. + ## Server Information After successful connection, retrieve server metadata: