MP-301g · Module 2

Reconnection Strategies

3 min read

Connections fail. The network blips, the server restarts, the load balancer rotates. A robust MCP client must detect connection loss and reconnect without losing the user's context. The detection mechanism depends on the transport: stdio clients detect a closed pipe (EPIPE or EOF on stdout), Streamable HTTP clients detect a dropped SSE stream or HTTP timeout. In both cases, the client should attempt reconnection immediately with exponential backoff — first retry after 100ms, then 200ms, 400ms, up to a maximum of 30 seconds. Add jitter (random offset) to prevent thundering herd when many clients reconnect simultaneously after a server restart.

Session resumption after reconnection depends on whether the server supports it. If the server is stateful and the session still exists, the client resends the Mcp-Session-Id and continues where it left off. If the session expired or the server is stateless, the client must re-execute the initialize handshake and rebuild any subscriptions. The client should cache the last-known capabilities and subscriptions locally so it can rebuild the session without user intervention. From the user's perspective, a good reconnection is invisible.

// Exponential backoff with jitter for MCP reconnection
class ReconnectPolicy {
  private attempt = 0;
  private readonly baseMs = 100;
  private readonly maxMs = 30_000;
  private readonly jitterFactor = 0.5;

  nextDelay(): number {
    const exponential = Math.min(
      this.baseMs * Math.pow(2, this.attempt),
      this.maxMs
    );
    const jitter = exponential * this.jitterFactor * Math.random();
    this.attempt++;
    return exponential + jitter;
  }

  reset(): void {
    this.attempt = 0;
  }

  async wait(): Promise<void> {
    const delay = this.nextDelay();
    await new Promise((r) => setTimeout(r, delay));
  }
}

// Usage in reconnection loop
async function reconnectLoop(
  connect: () => Promise<void>,
  policy: ReconnectPolicy
): Promise<void> {
  while (true) {
    try {
      await connect();
      policy.reset(); // success — reset backoff
      return;
    } catch {
      await policy.wait(); // wait with backoff + jitter
    }
  }
}