Closing a legacy Client during initialize leaves an unhandled rejection under workerd
@modelcontextprotocol/client@2.0.0 can reject a Promise without a handler when its transport closes during the pre-2026 initialize handshake. The application's client.connect(transport) Promise already has a rejection handler. Under workerd, Vitest reports SdkError: Not connected from Client._notificationViaCodec via _legacyHandshake; Node does not report an unhandled rejection for the same sequence.
Reproduction in a Vitest project using workerd:
import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
import { McpServer } from "@modelcontextprotocol/server";
import { it } from "vitest";
it("closes during legacy initialize", async () => {
for (let ticks = 0; ticks < 30; ticks++) {
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
const server = new McpServer({ name: "downstream", version: "1.0.0" });
await server.connect(serverTransport);
const client = new Client(
{ name: "probe", version: "1.0.0" },
{ versionNegotiation: { mode: "legacy" } },
);
const connecting = client.connect(clientTransport).catch(() => {});
for (let tick = 0; tick < ticks; tick++) await Promise.resolve();
await clientTransport.close();
await connecting;
await server.close();
}
});
Expected: all failures caused by close belong to connecting; no unhandled rejection.
Actual: workerd reports four unhandled NOT_CONNECTED rejections at _legacyHandshake's notifications/initialized send. The first such report was reproduced with workerd 1.20260722.1 and @modelcontextprotocol/client@2.0.0. The issue can occur when a host cancels a connection after initialize replies but before notifications/initialized is sent.
Connecta temporarily skips that notification only when its own connection close has already aborted the handshake. A fix in the SDK would let us remove this workaround.
Closing a legacy Client during initialize leaves an unhandled rejection under workerd
@modelcontextprotocol/client@2.0.0can reject a Promise without a handler when its transport closes during the pre-2026initializehandshake. The application'sclient.connect(transport)Promise already has a rejection handler. Under workerd, Vitest reportsSdkError: Not connectedfromClient._notificationViaCodecvia_legacyHandshake; Node does not report an unhandled rejection for the same sequence.Reproduction in a Vitest project using workerd:
Expected: all failures caused by close belong to
connecting; no unhandled rejection.Actual: workerd reports four unhandled
NOT_CONNECTEDrejections at_legacyHandshake'snotifications/initializedsend. The first such report was reproduced with workerd 1.20260722.1 and@modelcontextprotocol/client@2.0.0. The issue can occur when a host cancels a connection afterinitializereplies but beforenotifications/initializedis sent.Connecta temporarily skips that notification only when its own connection close has already aborted the handshake. A fix in the SDK would let us remove this workaround.