# Managing Multiple Concurrent MCP Clients in Chat-MCP: A Complete Guide

> Effectively manage multiple concurrent MCP clients in Chat-MCP. This guide details parallel initialization, timeout protection, and isolated IPC handlers for robust application performance.

- Repository: [AIQL/chat-mcp](https://github.com/ai-ql/chat-mcp)
- Tags: how-to-guide
- Published: 2026-02-23

---

**The Chat-MCP Electron application manages multiple concurrent MCP clients by reading a JSON configuration, initializing all clients in parallel with timeout protection, and registering capability-aware IPC handlers that isolate each server's namespace.**

The `ai-ql/chat-mcp` repository demonstrates a robust architecture for handling multiple concurrent MCP clients within an Electron application. By leveraging configuration-driven discovery and parallel async initialization, the application can connect to an arbitrary number of MCP servers at startup while preventing individual server failures from blocking the entire system.

## Configuration-Driven Client Discovery

### Reading the Server Configuration

The application begins by loading server definitions from a [`config.json`](https://github.com/ai-ql/chat-mcp/blob/main/config.json) file. The `readConfig` function in [`src/main/main.ts`](https://github.com/ai-ql/chat-mcp/blob/main/src/main/main.ts) (lines 44-52) safely parses this file and returns a map of server names to their `ServerConfig` objects.

If parsing fails or the file is missing, the function returns `null`, triggering a *"NO clients initialized"* notification and preventing the application from starting with an invalid configuration.

### Validating Config Structure

The expected configuration structure follows this pattern:

```json
{
  "mcpServers": {
    "dev-server": { "host": "127.0.0.1", "port": 8000 },
    "staging-srv": { "host": "staging.example.com", "port": 8001 }
  }
}

```

Each entry under `mcpServers` becomes a distinct client instance with isolated transport connections and capability namespaces.

## Parallel Initialization with Timeout Protection

### The initClient Function

The `initClient` function (lines 60-78 in [`src/main/main.ts`](https://github.com/ai-ql/chat-mcp/blob/main/src/main/main.ts)) orchestrates the concurrent startup of all configured MCP clients. It uses `Promise.all` over `Object.entries(config.mcpServers)` to launch initialization for every server simultaneously rather than sequentially.

This parallel approach ensures that network latency or slow server responses do not compound linearly, keeping application startup time minimal regardless of how many concurrent MCP clients are configured.

### Per-Client Timeout Handling

Each client initialization is wrapped in `Promise.race` against a 30-second timeout promise. If a server fails to respond within this window, the specific client fails without blocking other initializations.

On success, `initClient` returns an array of objects containing `{ name, client, capabilities }` for each successfully connected server, which the application then uses to register IPC handlers.

## Client Bootstrap and Transport Setup

### initializeClient Implementation

The `initializeClient` function in [`src/main/client.ts`](https://github.com/ai-ql/chat-mcp/blob/main/src/main/client.ts) (lines 4-18) handles the low-level client creation for each server configuration:

```typescript
export async function initializeClient(name: string, config: ServerConfig) {
  const transport = new StdioClientTransport(config);
  const client = new Client({ name: `${name}-client` });
  
  await client.connect(transport);
  
  // Register default request handler for sampling
  client.setRequestHandler(CreateMessageRequestSchema, async (request) => {
    // Handle sampling requests
  });
  
  return client;
}

```

### StdioClientTransport Configuration

The `StdioClientTransport` establishes the actual connection to the MCP server using the host and port specified in the configuration. Each client receives a unique generated name (`${name}-client`) to prevent collisions in logging and debugging contexts.

The transport layer handles the underlying protocol details while the `Client` instance manages the MCP session state and capability negotiation.

## Capability-Aware IPC Namespacing

### Registering Isolated Handlers

After initialization, `registerIpcHandlers` creates isolated IPC channels for each concurrent MCP client. This function builds a feature object containing the client's name and a set of IPC channels mapped to the server's advertised capabilities (tools, prompts, resources).

For every capability reported by the server, a unique IPC handler is registered under the pattern `${serverName}-${method}`. For example, a server named "prod-srv" would register handlers like `prod-srv-tools/list` and `prod-srv-tools/call`.

This namespacing design prevents method collisions between different MCP servers and allows the renderer process to address each server explicitly.

### The manageRequests Bridge

The `manageRequests` function serves as the bridge between IPC handlers and the actual MCP client instances. When an IPC handler receives a request, it forwards to `manageRequests`, which invokes `client.request` with the proper schema for that capability.

Because each request handler is a simple async wrapper, many requests to different servers can be processed in parallel. The underlying `Client` implementation from `@ai-ql/mcp` handles its own request queue and response matching internally.

## Error Handling and Graceful Degradation

If any client fails to initialize—whether due to timeout, transport error, or invalid configuration—the application aborts startup with a notification and `process.exit(1)`. This strict failure mode ensures the UI never runs with a partially-configured set of servers, which could otherwise produce ambiguous runtime errors.

The initialization error handling in [`src/main/main.ts`](https://github.com/ai-ql/chat-mcp/blob/main/src/main/main.ts) (lines 88-98) catches initialization failures and displays them to the user before terminating, preventing the application from entering an inconsistent state.

## Code Examples

### Sample Configuration for Multiple Servers

```json
{
  "mcpServers": {
    "dev-server": { "host": "127.0.0.1", "port": 8000 },
    "staging-srv": { "host": "staging.example.com", "port": 8001 },
    "prod-srv": { "host": "prod.example.com", "port": 8002 }
  }
}

```

### Invoking Server-Specific Tools from the Renderer

```javascript
// renderer.js (via preload bridge)
async function listTools(serverName) {
  // IPC channel follows pattern "<server>-tools/list"
  const channel = `${serverName}-tools/list`;
  const result = await window.electron.invoke(channel);
  return result; // => { tools: [...] }
}

// Usage example
listTools('prod-srv').then(tools => console.log('Production tools:', tools));

```

### Extending Capabilities with Custom Schemas

```typescript
// In src/main/main.ts – extend registerIpcHandlers
const customCapabilitySchemas = {
  analytics: {
    report: MyAnalyticsReportSchema,
  },
};

for (const [type, actions] of Object.entries(customCapabilitySchemas)) {
  if (capabilities?.[type]) {
    feature[type] = {};
    for (const [action, schema] of Object.entries(actions)) {
      feature[type][action] = registerHandler(`${type}/${action}`, schema);
    }
  }
}

```

## Key Implementation Files

| File | Role | Link |
|------|------|------|
| [`src/main/main.ts`](https://github.com/ai-ql/chat-mcp/blob/main/src/main/main.ts) | Application entry-point; reads config, initializes all MCP clients, registers IPC channels | [main.ts](https://github.com/ai-ql/chat-mcp/blob/main/src/main/main.ts) |
| [`src/main/client.ts`](https://github.com/ai-ql/chat-mcp/blob/main/src/main/client.ts) | Helper that creates a `Client`, connects the transport, and installs a default request handler | [client.ts](https://github.com/ai-ql/chat-mcp/blob/main/src/main/client.ts) |
| [`src/main/types.ts`](https://github.com/ai-ql/chat-mcp/blob/main/src/main/types.ts) | Type definitions for `Client`, `ServerConfig`, and all request/response schemas used by the IPC bridge | [types.ts](https://github.com/ai-ql/chat-mcp/blob/main/src/main/types.ts) |
| [`src/preload/preload.ts`](https://github.com/ai-ql/chat-mcp/blob/main/src/preload/preload.ts) | Exposes a safe IPC bridge (`window.electron.invoke`) to the renderer process | [preload.ts](https://github.com/ai-ql/chat-mcp/blob/main/src/preload/preload.ts) |
| [`src/renderer/index.html`](https://github.com/ai-ql/chat-mcp/blob/main/src/renderer/index.html) | UI that consumes the IPC calls (e.g., listing tools, prompts, resources) | [index.html](https://github.com/ai-ql/chat-mcp/blob/main/src/renderer/index.html) |

## Summary

- **Configuration-driven architecture**: The application discovers servers through a JSON config file, making it trivial to add or remove concurrent MCP clients without code changes.
- **Parallel initialization**: `Promise.all` with per-client timeouts ensures fast startup and prevents misbehaving servers from blocking the application.
- **Isolated IPC namespaces**: Each server registers handlers under `${serverName}-${method}`, eliminating collisions and enabling explicit targeting of specific clients.
- **Strict failure handling**: The application exits immediately if any client fails to initialize, preventing partial configurations that could cause runtime ambiguity.
- **Runtime concurrency**: Stateless async handlers allow the renderer process to issue parallel requests to multiple servers, with the underlying MCP client managing internal request queues.

## Frequently Asked Questions

### How does the application prevent one slow MCP server from delaying startup?

The `initClient` function in [`src/main/main.ts`](https://github.com/ai-ql/chat-mcp/blob/main/src/main/main.ts) wraps each client initialization in `Promise.race` against a 30-second timeout. This ensures that a single unresponsive server fails individually while `Promise.all` allows other servers to continue initializing in parallel.

### Can the renderer process communicate with multiple MCP servers simultaneously?

Yes. The IPC architecture registers unique channels for each server using the pattern `${serverName}-${method}`. The renderer can invoke these channels independently, and because each handler is an async wrapper around `client.request`, requests to different servers execute concurrently without blocking each other.

### What happens if one MCP server fails to initialize?

The application implements strict failure handling: if any client throws an error or times out during `initClient`, the catch block in [`src/main/main.ts`](https://github.com/ai-ql/chat-mcp/blob/main/src/main/main.ts) (lines 88-98) displays a notification and calls `process.exit(1)`. This prevents the UI from running with a partially configured client set, which could lead to ambiguous runtime errors.

### How are naming collisions between different MCP servers avoided?

Each server registers its capabilities under a unique namespace derived from its configuration name. For example, a server named "prod-srv" registers handlers like `prod-srv-tools/list` and `prod-srv-tools/call`. This `${serverName}-${method}` pattern ensures that methods from different servers never collide, allowing the renderer to explicitly target specific clients.