vercel/ai · error · MCPClientError

MCP HTTP Transport Error: Transport already started. Note: c

Error message

MCP HTTP Transport Error: Transport already started. Note: client.connect() calls start() automatically.

What it means

HttpMCPTransport.start() guards against double-start by checking its internal AbortController; a second start() call throws MCPClientError. The message notes that client.connect() already calls start() for you, so manually invoking start() after connect() is the typical cause.

Source

Thrown at packages/mcp/src/tool/mcp-http-transport.ts:219

    }

    if (!this.authPromise) {
      this.authPromise = auth(this.authProvider, {
        serverUrl: this.url,
        resourceMetadataUrl,
        scope,
        fetchFn: this.fetchFn,
      }).finally(() => {
        this.authPromise = undefined;
      });
    }

    return this.authPromise;
  }

  async start(): Promise<void> {
    if (this.abortController) {
      throw new MCPClientError({
        message:
          'MCP HTTP Transport Error: Transport already started. Note: client.connect() calls start() automatically.',
      });
    }
    this.abortController = new AbortController();

    if (
      this.protocolVersion != null &&
      this.protocolVersion !== LATEST_PROTOCOL_VERSION
    ) {
      this.startInboundSse();
    }
  }

  async close(options?: { signal?: AbortSignal }): Promise<void> {
    this.inboundSseConnection?.close();
    this.abortController?.abort();

View on GitHub (pinned to 69428b1f8b)

Solutions

  1. Do not call start() manually — just use client.connect(), which starts the transport
  2. Create a new HttpMCPTransport (or new client) for each connection attempt; never reuse a started transport
  3. Ensure each client gets its own transport instance

Example fix

// before
const client = await createMCPClient({ transport });
await transport.start(); // throws
// after
const client = await createMCPClient({ transport }); // connect() calls start() internally
Defensive patterns

Strategy: try-catch

Validate before calling

// track start state yourself if you manage the transport manually
let started = false;
async function ensureStarted(transport) {
  if (!started) { await transport.start(); started = true; }
}

Try / catch

try {
  await client.connect(); // do NOT call transport.start() yourself
} catch (error) {
  if (MCPClientError.isInstance(error) && error.message.includes('Transport already started')) {
    // reuse the existing connection instead of starting again
  } else throw error;
}

Prevention

When it happens

Trigger: Calling transport.start() explicitly after (or while) client.connect() is running; sharing one transport instance between two clients; retrying connect() on the same transport instance after a failed connection attempt without creating a new transport.

Common situations: Following low-level transport examples (which call start() manually) together with the higher-level createMCPClient/connect flow; retry loops that reuse the transport object; dependency injection wiring the same transport into multiple clients.

Related errors


AI-assisted analysis of vercel/ai@69428b1f8b (2026-08-30). Data as JSON: /api/errors/3b53b25ebec4a0dc. Report an issue: GitHub.