MCP Server Reconnection and Timeout Handling in 5ire: Implementation Guide

The 5ire codebase implements a two-layer resilience strategy for MCP (Model Context Protocol) servers: the connection-establishment layer retries failed connections up to 3 times with exponential backoff, while the call-execution layer enforces per-request timeouts and performs a single automatic reconnection before retrying failed RPCs.

The 5ire repository provides a robust TypeScript implementation for managing MCP server connections in desktop AI applications. Understanding how MCP servers handling reconnection attempts and timeouts is critical for building resilient integrations that survive transient network failures and server restarts without blocking the user interface.

Connection Establishment with Retry Logic

The foundation of resilience lies in src/main/services/mcp-connections-manager.ts, specifically within the private #connect method. This layer manages the lifecycle of TCP and stdio transports while implementing defensive retry patterns against transient startup failures.

The #connect Method Implementation

When activating a new MCP server, the manager first checks existing connection states to prevent duplicate attempts. If no active connection exists, it enters a retry loop designed to handle transient startup failures.

The implementation uses a for loop running up to 3 retries (for (let retries = 0; retries < 3; retries++)). Between attempts, it applies an exponential back‑off strategy by waiting 1000 × retries milliseconds, creating delays of 0ms, 1s, and 2s respectively.

// Simplified logic from src/main/services/mcp-connections-manager.ts
for (let retries = 0; retries < 3; retries++) {
  await new Promise(r => setTimeout(r, 1000 * retries));
  
  try {
    const client = new Client(CLIENT_IMPLEMENTATION, { capabilities: CLIENT_CAPABILITIES });
    const capabilities = await client.connect(this.#transport(server), {});
    // Success: store connection, emit event, break loop
    return;
  } catch (e) {
    connectError = e; // Continue to next retry
  }
}
// After loop: mark connection as error

Upon successful connection, the client is stored in the manager’s internal connections map with status connected, and capabilities are negotiated. If all retries exhaust, the status transitions to error alongside the captured exception message.

Request Execution with Timeout Protection

Once established, individual RPC calls are protected by src/main/mcp.ts, which implements timeout enforcement and graceful degradation through the safeCall helper and reconnect methods.

The safeCall Helper

The safeCall(clientKey, fn, timeoutMs?) method wraps every MCP operation, such as listTools or listPrompts. It first validates the client's existence, then executes the provided function fn.

If a timeoutMs parameter is provided, safeCall races the actual request against a setTimeout that rejects with an invalid_connection error after the specified duration. This prevents hung servers from freezing the UI indefinitely.

// Logic from src/main/mcp.ts
private async safeCall(clientKey: string, fn: () => Promise<any>, timeoutMs?: number) {
  if (!this.clients[clientKey]) throw new Error(`Client ${clientKey} not found`);

  try {
    if (!timeoutMs) return await fn();

    const res = await Promise.race([
      fn(),
      new Promise((_r, reject) =>
        setTimeout(() => reject(new Error("invalid_connection")), timeoutMs)
      ),
    ]);
    return res;
  } catch {
    await this.reconnect(clientKey);
    return fn(); // Second attempt after reconnect
  }
}

When any error occurs—including timeouts—safeCall invokes await this.reconnect(clientKey) to refresh the connection before attempting the operation one additional time.

The reconnect Method

The private reconnect method in src/main/mcp.ts performs surgical connection recovery. It closes the existing client instance, re‑activates the server configuration, and throws any activation errors immediately. This ensures that stale transport handles are discarded before the retry attempt.

Practical Implementation Examples

Adding a Server with Automatic Retry

When registering a new MCP server through the store, the connection manager automatically applies the retry logic:

import useMCPStore from '@/stores/useMCPStore';

await useMCPStore.getState().addServer({
  id: "my-server",
  transport: "http",
  endpoint: "https://example.com/mcp",
  config: {},
  active: true,
});
// MCPConnectionsManager.#connect retries up to 3 times if unreachable

Executing Calls with Timeout Protection

Standard operations use safeCall internally to guarantee responsiveness:

import MCP from '@/services/mcp';

const mcp = new MCP();

// Uses LIST_TOOLS_TIMEOUT (≈ 5s) internally via safeCall
const { tools, error } = await mcp.listTools();

if (error) {
  console.error('Failed to fetch tools:', error);
}

Manual Reconnection

For advanced scenarios, explicit reconnection is available:

await mcp.reconnect('my-server-key');
// Closes old client and re-activates configuration

Summary

  • Three-attempt retry logic in src/main/services/mcp-connections-manager.ts handles initial connection failures with exponential backoff (0ms → 1s → 2s).
  • Connection state tracking uses explicit statuses (connected, connecting, error) to prevent duplicate connection attempts.
  • Per-call timeouts via safeCall in src/main/mcp.ts use Promise.race to bound RPC duration and prevent UI blocking.
  • Single automatic reconnection occurs when requests fail, closing stale clients and re-activating configurations before one final retry.
  • Structured error handling returns specific error codes like invalid_connection and invalid_response for programmatic error handling.

Frequently Asked Questions

How many times does 5ire retry a failed MCP connection?

The MCPConnectionsManager attempts to establish a connection up to 3 times before marking the server as errored. This logic resides in the #connect method within src/main/services/mcp-connections-manager.ts, with exponential backoff delays between attempts.

What timeout values does 5ire use for MCP requests?

5ire defines specific timeout constants for different operations, including CONNECT_TIMEOUT, LIST_TOOLS_TIMEOUT, and RETRIEVE_PROMPTS_TIMEOUT. These values are passed to the safeCall method in src/main/mcp.ts, which races the request against a timer that rejects with an invalid_connection error upon expiry.

Does 5ire reconnect automatically when an MCP call fails?

Yes. The safeCall implementation automatically invokes this.reconnect(clientKey) when any RPC throws an error or times out. After reconnecting, it executes the original function once more. If this second attempt fails, the error propagates to the caller.

Where is the MCP connection state managed in the codebase?

Connection states (connected, connecting, error) are managed in the MCPConnectionsManager class at src/main/services/mcp-connections-manager.ts. The high-level RPC wrappers and reconnection logic reside in src/main/mcp.ts, while type definitions are located in src/types/mcp.d.ts.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →