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.tshandles 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
safeCallinsrc/main/mcp.tsusePromise.raceto 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_connectionandinvalid_responsefor 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →