Skip to content

Closing a legacy Client during initialize leaves an unhandled rejection in workerd #2864

Description

@zackbart

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    v2Ideas, requests and plans for v2 of the SDK which will incorporate major changes and fixes

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions