How TUUI Initializes MCP Servers with a 90-Second Timeout for Runtime Installations

TUUI initializes MCP servers through a three-step pipeline that loads configuration, creates clients, and establishes Stdio transport, while enforcing a 90-second idle timeout to prevent hanging during runtime installations.

TUUI (the AI-powered UI framework) handles Model Context Protocol (MCP) server initialization through a robust pipeline designed to manage runtime installations safely. The implementation in the ai-ql/tuui repository ensures that unresponsive servers cannot block the application by implementing a strict 90-second idle timeout mechanism that aborts stalled connections.

The Three-Step MCP Server Initialization Pipeline

The initialization process in src/main/mcp/init.ts follows a declarative pipeline that transforms configuration entries into active client connections.

Step 1: Loading the MCP Configuration

The process begins with loadConfig(), which reads the JSON file defined in Constants.ASSETS_PATH.mcp. This function returns an array of metadata objects containing { name, configJson } for each declared server.

Source: src/main/mcp/init.ts#L9-L15

Step 2: Creating Clients for Each Entry

The initClients() function iterates over the metadata entries. For every stdio-type entry, it delegates to initSingleClient(), which forwards the configuration to the core initialization logic.

Source: src/main/mcp/init.ts#L32-L48 and src/main/mcp/init.ts#L59-L66

Step 3: Establishing Stdio Transport

The initializeClient() function in src/main/mcp/client.ts performs the actual transport setup. It creates a StdioClientTransport, wires the standard-error stream to an optional progress callback, and connects to the MCP server via connect().

Source: src/main/mcp/client.ts#L44-L80

How the 90-Second Idle Timeout Protects Against Hanging Installations

The critical safety mechanism in TUUI's MCP initialization is the 90-second idle timeout that prevents runtime installations from blocking the application indefinitely.

Timer Setup and Reset Mechanism

The timeout logic centers on a NodeJS.Timeout variable called idleTimer. The resetTimer() function clears any existing timer and schedules a new one that will reject the initialization promise after idleTimeout seconds (defaulting to 90). This timer is reset whenever activity occurs on the stderr stream, indicating the server is still alive during installation.

Source: src/main/mcp/client.ts#L18-L32

The Race Between Transport and Timeout

The initialization uses Promise.race([stdioPromise, timeoutPromise]) to ensure responsiveness. The timeoutPromise is a never-resolving promise that only rejects when the idle timer fires, with the reject function stored in rejectFn for resetTimer() to invoke.

If the server connects and sends data before 90 seconds, the Stdio promise resolves and the timeout is cleared. If the server hangs during runtime installation, the idle timer fires, rejectFn executes, and the initialization fails.

Source: src/main/mcp/client.ts#L34-L42

Error Handling When Timeout Occurs

When the 90-second threshold is exceeded, the promise rejects with a descriptive error message:


Initialization of client for <name> timed out after 90 seconds of inactivity

This error propagates up through initClients(), preventing the stalled server from being added to the active client pool in IPCs.clients.

Practical Implementation Examples

Starting All MCP Servers

The high-level entry point for TUUI's MCP initialization demonstrates the complete pipeline:

import { loadConfig, initClients } from '@/main/mcp/init'

// Load the MCP manifest and start every declared server
async function startAllMcpServers(progressCallback) {
  const metadata = await loadConfig()
  const clients = await initClients(metadata, progressCallback)
  // `clients` now contains { name, connection, configJson } for each running server
  return clients
}

// Example usage in the main runner
await startAllMcpServers((name, msg, status) => {
  console.log(`[${status}] ${name}: ${msg}`)
})

Source: src/main/MainRunner.ts#L151

Configuring the 90-Second Timeout

The timeout duration is configurable when calling the initialization function, though it defaults to 90 seconds:

await initializeClient('example', serverConfig, progressCb, /* idleTimeout = 90 */)
// If the server does not send anything on stderr for 90 seconds,
// the promise rejects with:
//   "Initialization of client for example timed out after 90 seconds of inactivity"

Source: See the timeout logic in [src/main/mcp/client.ts](https://github.com/ai-ql/tuui/blob/main/src/main/mcp/client.ts#L18-L42).

Key Files in the TUUI MCP Initialization System

File Role Link
src/main/mcp/init.ts Loads MCP manifest, orchestrates per-client initialization view
src/main/mcp/client.ts Implements initializeClient, sets up Stdio transport and the 90s idle timeout view
src/main/IPCs.ts Registers IPC handlers, stores initialized client descriptors, exposes them to the renderer view
src/main/MainRunner.ts Entry point that calls IPCs.initializeMCP after clients are ready view
src/main/utils/Constants.ts Holds path constants used for locating the MCP configuration assets view

These files together define the complete lifecycle of MCP server startup, including the safety net of the 90-second idle timeout that guards against unresponsive runtime installations.

Summary

  • TUUI initializes MCP servers through a three-phase pipeline: loading configuration from Constants.ASSETS_PATH.mcp, creating clients via initClients(), and establishing Stdio transport through initializeClient().
  • The 90-second idle timeout is implemented in src/main/mcp/client.ts using a NodeJS.Timeout that rejects the initialization promise if no activity occurs on stderr within 90 seconds.
  • Promise.race() ensures responsiveness by competing the Stdio connection promise against the timeout promise, guaranteeing that stalled runtime installations cannot block the application.
  • Error propagation prevents unresponsive servers from entering the active client pool stored in IPCs.clients, maintaining application stability.

Frequently Asked Questions

What happens if an MCP server takes longer than 90 seconds to install?

If an MCP server exceeds the 90-second idle timeout during initialization, the initializeClient() function in src/main/mcp/client.ts rejects the promise with the error message: "Initialization of client for [name] timed out after 90 seconds of inactivity." This prevents the stalled server from blocking the TUUI application and excludes it from the active client pool.

Can the 90-second timeout duration be customized?

Yes, the timeout duration is configurable through the idleTimeout parameter in the initializeClient() function. While the default value is 90 seconds, you can pass a different number of seconds when calling the function to accommodate slower runtime installations or stricter requirements.

Where does TUUI store the MCP server configuration?

TUUI reads the MCP server configuration from a JSON file located at the path defined in Constants.ASSETS_PATH.mcp, typically found in src/main/utils/Constants.ts. The loadConfig() function in src/main/mcp/init.ts parses this file to extract server metadata including names and configuration objects.

How does TUUI communicate initialized MCP clients to the renderer process?

After successful initialization, client descriptors are stored in IPCs.clients within src/main/IPCs.ts. The renderer process accesses these through the list-clients IPC handler, which reads from IPCs.currentFeatures populated by IPCs.initializeMCP() after all servers have been started.

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 →