# Electron IPC Communication Flow Between Main and Renderer Processes in Chat MCP

> Explore the Electron IPC communication flow in Chat MCP. Learn how renderer processes securely invoke main process methods via context-bridge for efficient data exchange and responsive UI.

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

---

**The Chat MCP application implements a secure context-bridge pattern where the renderer process invokes methods on an exposed `mcpServers` object that proxies calls through `ipcRenderer.invoke` to the main process, which handles requests via `ipcMain.handle` and forwards them to the Model-Context-Protocol client before returning results to the UI.**

The ai-ql/chat-mcp repository provides a desktop chat client built on Electron's multi-process architecture. Understanding the Electron IPC communication flow between main and renderer processes is essential for developers extending this Model-Context-Protocol (MCP) application, as it demonstrates secure inter-process messaging without exposing Node.js APIs to the web content.

## The Six-Step Communication Cycle

The IPC implementation follows a structured request-response pattern that bridges the sandboxed renderer with the main process's Node.js capabilities.

### Step 1: Renderer Invocation

JavaScript running in the UI calls methods on the **`mcpServers`** object that the preload script exposes on `window`. This object serves as the sole entry point for renderer-side code to access backend functionality.

### Step 2: Preload Bridge

In [`src/preload/preload.ts`](https://github.com/ai-ql/chat-mcp/blob/main/src/preload/preload.ts) (lines 42-44), each method on the `mcpServers` object forwards calls to **`ipcRenderer.invoke`**, passing a unique channel name constructed as `${serverName}-${method}`. For example, invoking `window.mcpServers.myServer.tools.list()` triggers `ipcRenderer.invoke('myServer-tools/list')`.

### Step 3: Main Process Handling

The main process registers handlers for every possible channel using **`ipcMain.handle`** in [`src/main/main.ts`](https://github.com/ai-ql/chat-mcp/blob/main/src/main/main.ts) (lines 46-57, 73-80). When a message arrives, the handler builds a request object and forwards it to the appropriate MCP client via the `manageRequests` function.

### Step 4: MCP Client Execution

Located in [`src/main/client.ts`](https://github.com/ai-ql/chat-mcp/blob/main/src/main/client.ts) (lines 36-50), the `manageRequests` function calls `client.request(requestObject, schema)`. The MCP client communicates with the underlying LLM server (started as a child process) and returns a typed result according to the schema definition.

### Step 5: Response Propagation

The result from `client.request` returns from the `ipcMain.handle` callback in [`main.ts`](https://github.com/ai-ql/chat-mcp/blob/main/main.ts) (lines 52-54), which automatically resolves the original `ipcRenderer.invoke` promise in the renderer process.

### Step 6: Renderer Completion

The renderer receives the resolved value and updates the UI accordingly, completing the asynchronous round-trip.

## Channel Naming Conventions

The application dynamically constructs IPC channels based on configured MCP server capabilities.

### Listing Configured Servers

To retrieve available servers, the renderer calls `ipcRenderer.invoke('list-clients')`. The main process responds with the `features` array built from each client's capabilities (defined in [`src/main/main.ts`](https://github.com/ai-ql/chat-mcp/blob/main/src/main/main.ts), lines 37-39).

### Capability-Specific Channels

For each configured MCP server, the main process creates channels following the pattern **`${name}-${type}/${action}`**:

- **`myServer-tools/list`** – Lists available tools for the server named "myServer"
- **`myServer-prompts/get`** – Fetches a specific prompt definition
- **`myServer-resources/read`** – Reads a resource file content

The `registerHandler` function in [`main.ts`](https://github.com/ai-ql/chat-mcp/blob/main/main.ts) (lines 48-55) implements this naming convention by iterating through each server's capabilities and registering distinct handlers for every type/action combination.

## Security Architecture

The implementation enforces strict process isolation to prevent security vulnerabilities.

### Context Isolation

With **`contextIsolation: true`** and **`nodeIntegration: false`** configured, the renderer cannot directly access Node.js or Electron APIs. This prevents untrusted web content from executing arbitrary system commands.

### Controlled Exposure

The **preload script** acts as the sole, vetted gateway between processes. It exposes only the `mcpServers` object, ensuring that renderer code cannot invoke arbitrary IPC channels or access filesystem APIs directly.

## Implementation Details by File

### Preload Script ([`src/preload/preload.ts`](https://github.com/ai-ql/chat-mcp/blob/main/src/preload/preload.ts))

This file creates the bridge between renderer and main processes. It constructs the `mcpServers` object with methods that proxy calls to `ipcRenderer.invoke`. The implementation validates method names and ensures only whitelisted channels can be invoked from the renderer.

### Main Process ([`src/main/main.ts`](https://github.com/ai-ql/chat-mcp/blob/main/src/main/main.ts))

This file contains the IPC handler registration logic. After creating the `BrowserWindow`, it iterates through the MCP server configuration and registers handlers using `ipcMain.handle` for each capability. The `manageRequests` wrapper ensures proper request formatting and error handling before dispatching to the MCP client.

### MCP Client ([`src/main/client.ts`](https://github.com/ai-ql/chat-mcp/blob/main/src/main/client.ts))

This module manages the actual communication with LLM servers. The `initializeClient` function establishes stdio transports to child processes, while `manageRequests` dispatches IPC-originated requests to the appropriate client instance and enforces schema validation on responses.

## Practical Code Examples

### Listing Available Tools

```javascript
// Renderer process code
(async () => {
  // Access the exposed API from the preload script
  const tools = await window.mcpServers.example.tools.list();
  console.log('Available tools:', tools);
})();

```

Behind the scenes, `window.mcpServers.example.tools.list()` resolves to a function in [`preload.ts`](https://github.com/ai-ql/chat-mcp/blob/main/preload.ts) that executes `ipcRenderer.invoke('example-tools/list')`. The main process handler receives this call, queries the MCP client, and returns the structured tool list.

### Reading a Resource

```javascript
// Renderer process code
(async () => {
  const content = await window.mcpServers.example.resources.read('config.yaml');
  console.log('Resource content:', content);
})();

```

This invocation uses the channel `example-resources/read`, handled by the main process and forwarded to `manageRequests` in [`client.ts`](https://github.com/ai-ql/chat-mcp/blob/main/client.ts), which returns a `ReadResourceResultSchema` validated object.

### Fetching Server Capabilities

```javascript
// Renderer process code
(async () => {
  // Direct invoke for meta-information not wrapped in mcpServers object
  const servers = await window.ipcRenderer.invoke('list-clients');
  console.log('Configured MCP servers:', servers);
})();

```

While the `mcpServers` object encapsulates server-specific methods, the `list-clients` channel provides metadata about all configured servers, returning the `features` array constructed during main process initialization.

## Summary

- **Secure Bridge**: The preload script in [`src/preload/preload.ts`](https://github.com/ai-ql/chat-mcp/blob/main/src/preload/preload.ts) exposes only the `mcpServers` object, preventing direct Node.js access while enabling structured communication.
- **Dynamic Channels**: IPC channels follow the `${name}-${type}/${action}` pattern, dynamically registered in [`src/main/main.ts`](https://github.com/ai-ql/chat-mcp/blob/main/src/main/main.ts) based on server configuration.
- **Async Flow**: The pattern uses `ipcRenderer.invoke` paired with `ipcMain.handle` for promise-based request-response cycles across process boundaries.
- **Schema Validation**: Requests pass through `manageRequests` in [`src/main/client.ts`](https://github.com/ai-ql/chat-mcp/blob/main/src/main/client.ts), which enforces type safety through MCP schema definitions before communicating with LLM child processes.

## Frequently Asked Questions

### How does the preload script maintain security while exposing IPC functionality?

The preload script operates with **`contextIsolation: true`** and **`nodeIntegration: false`**, ensuring the renderer cannot access Node.js APIs directly. It exposes only a curated `mcpServers` object where each method is hardcoded to call specific, whitelisted IPC channels via `ipcRenderer.invoke`, preventing arbitrary code execution or unauthorized system access.

### What determines the IPC channel names used in the application?

Channel names are constructed dynamically in [`src/main/main.ts`](https://github.com/ai-ql/chat-mcp/blob/main/src/main/main.ts) using the pattern `${serverName}-${type}/${action}`, where `type` represents the capability category (tools, prompts, resources) and `action` specifies the operation (list, get, read). For example, a server named "production" exposes `production-tools/list` and `production-resources/read` channels.

### How does the main process route IPC requests to the correct MCP server?

The main process registers dedicated handlers via `ipcMain.handle` for each configured server capability during application startup. When `ipcRenderer.invoke` triggers a channel, the corresponding handler builds a request object and passes it to `manageRequests` in [`src/main/client.ts`](https://github.com/ai-ql/chat-mcp/blob/main/src/main/client.ts), which routes the call to the specific MCP client instance managing that server's child process.

### Can the renderer process directly invoke Node.js APIs or filesystem functions?

No. The renderer operates in a sandboxed environment without direct Node.js access. All filesystem operations, child process management, and LLM communication occur exclusively in the main process. The renderer must use the exposed `mcpServers` object or `ipcRenderer.invoke` to request operations, with the preload script acting as the controlled gateway for these cross-process messages.