Skip to content

StreamableHTTPClientTransport cannot be restarted after close() — breaks OAuth re-authentication #1641

@semistrict

Description

@semistrict

Bug Description

StreamableHTTPClientTransport.start() throws "StreamableHTTPClientTransport already started!" if called after close(), because close() aborts the _abortController but never resets it to undefined.

This breaks any flow that needs to reconnect after OAuth authentication, because:

  1. transport.start() is called → sets this._abortController
  2. Server returns 401 → OAuth flow begins
  3. transport.close() is called → aborts _abortController but does NOT set it to undefined
  4. After OAuth completes, transport.start() is called again
  5. The guard at line 257 checks if (this._abortController) → still truthy → throws

Reproduction

Any MCP client that uses StreamableHTTPClientTransport with an OAuth auth provider hitting a server that requires authentication will fail on the first connection attempt. The token is saved successfully, but the transport cannot be restarted in the same process.

This is observable with tools like mcporter when connecting to an OAuth-protected MCP server for the first time.

Root Cause

In src/client/streamableHttp.ts, the close() method aborts the controller but doesn't clear the reference:

async close(): Promise<void> {
    // ...
    this._abortController?.abort();  // aborts but keeps reference
    this.onclose?.();
}

While start() guards against re-entry by checking if _abortController exists:

async start(): Promise<void> {
    if (this._abortController) {
        throw new Error('StreamableHTTPClientTransport already started!...');
    }
    this._abortController = new AbortController();
}

Suggested Fix

Reset _abortController to undefined in close():

async close(): Promise<void> {
    // ...
    this._abortController?.abort();
    this._abortController = undefined;
    this.onclose?.();
}

This allows the transport to be restarted after being closed, which is the expected lifecycle for OAuth re-authentication flows.

Environment

  • @modelcontextprotocol/sdk: 1.27.1
  • Runtime: Node.js
  • Transport: StreamableHTTPClientTransport

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions