# How the Preload Script Exposes MCP APIs via contextBridge in Electron

> Learn how the preload script securely exposes MCP APIs to your Electron app's web UI using contextBridge. Access main process features safely from the renderer.

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

---

**The preload script acts as a secure intermediary that exposes curated MCP (Modular Chat Platform) APIs to the renderer process through Electron's `contextBridge`, enabling the web UI to invoke main process capabilities without direct Node.js access.**

In the `ai-ql/chat-mcp` repository, the preload script plays a critical role in the Electron security model. Since renderer processes run in a sandboxed environment with restricted privileges, the **preload script exposing MCP APIs via contextBridge** provides the only sanctioned pathway for the frontend to communicate with MCP servers and access tools, prompts, and resources managed by the main process.

## Why Electron Requires a Preload Script for MCP Access

Electron enforces a strict security boundary between the **main process** (full Node.js/Electron access) and the **renderer process** (sandboxed web environment). Direct access to `ipcRenderer` or Node modules from the frontend would violate security best practices and expose the application to remote code execution risks.

The preload script runs in an **isolated context** with elevated privileges, allowing it to import Electron modules and build a controlled API surface. By using `contextBridge.exposeInMainWorld`, the script selectively exposes only the necessary MCP functionality to the renderer, maintaining the principle of least privilege while enabling rich desktop integration.

## How the Preload Script Works in chat-mcp

Located at [`src/preload/preload.ts`](https://github.com/ai-ql/chat-mcp/blob/main/src/preload/preload.ts), the preload script implements a four-stage pipeline to marshal MCP capabilities from the main process to the renderer.

### Loading IPC Helpers from Electron

The script begins by importing the essential Electron modules required for inter-process communication and API exposure.

```typescript
const { contextBridge, ipcRenderer } = require('electron');

```

This import establishes the foundation for all subsequent MCP API bridging, with `ipcRenderer` handling message passing and `contextBridge` managing secure exposure to the renderer.

### Querying Registered MCP Clients

The preload script queries the main process for available MCP client configurations using a specific IPC channel. This discovery mechanism allows the script to dynamically adapt to whatever MCP servers are registered at runtime.

```typescript
async function listClients(): Promise<CLIENT[]> {
  return await ipcRenderer.invoke('list-clients');
}

```

The `list-clients` handler, implemented in [`src/main/main.ts`](https://github.com/ai-ql/chat-mcp/blob/main/src/main/main.ts), returns an array of client objects containing metadata about available tools, prompts, and resources for each MCP server.

### Building Dynamic API Wrappers

For each client discovered, the preload script generates thin wrapper functions that forward calls to the main process. The `createAPIMethods` utility dynamically constructs these wrappers based on method mappings provided by the main process.

```typescript
const createAPIMethods = (methods: Record<string, string>) => {
  const result: Record<string, (...args: any) => Promise<any>> = {};
  Object.keys(methods).forEach(key => {
    const methodName = methods[key];
    result[key] = (...args: any) => ipcRenderer.invoke(methodName, ...args);
  });
  return result;
};

```

This approach encapsulates all IPC plumbing, presenting a clean, promise-based API to the renderer while maintaining strict control over which main process methods are accessible.

### Exposing the API via contextBridge

Once the API object is fully constructed with `tools`, `prompts`, and `resources` namespaces for each client, the script exposes it to the renderer through the context bridge.

```typescript
contextBridge.exposeInMainWorld('mcpServers', api);

```

This makes the MCP API available globally as `window.mcpServers` in the renderer process, allowing the React frontend to interact with MCP servers without importing Electron modules or breaking sandbox security.

## Accessing MCP APIs in the Renderer Process

With the preload script exposing MCP APIs via contextBridge, the renderer can access MCP functionality through the global `window.mcpServers` object. The TypeScript definitions below illustrate how a React component would consume these APIs.

```typescript
declare global {
  interface Window {
    mcpServers: {
      [clientName: string]: {
        tools?: Record<string, (...args: any) => Promise<any>>;
        prompts?: Record<string, (...args: any) => Promise<any>>;
        resources?: Record<string, (...args: any) => Promise<any>>;
      };
    };
  }
}

// Example: Invoke the "list" method on the "search" tool from the "openai" client
async function listOpenAiSearchTools() {
  const result = await window.mcpServers.openai.tools?.list?.();
  console.log('OpenAI search tools:', result);
}

```

This pattern ensures that the renderer remains sandboxed while still enabling rich integration with MCP servers managed by the main process.

## Summary

- The **preload script** in [`src/preload/preload.ts`](https://github.com/ai-ql/chat-mcp/blob/main/src/preload/preload.ts) serves as the secure intermediary between Electron's main and renderer processes.
- It uses **`contextBridge.exposeInMainWorld`** to safely expose MCP APIs to the frontend without granting direct Node.js access.
- The script dynamically discovers available MCP clients via **`ipcRenderer.invoke('list-clients')`** and builds wrapper functions for each tool, prompt, and resource.
- This architecture maintains **sandbox security** while enabling the React frontend to interact with MCP servers through the global `window.mcpServers` object.

## Frequently Asked Questions

### What is the contextBridge in Electron and why is it necessary?

The `contextBridge` is an Electron module that allows the preload script to expose specific APIs to the renderer process in a secure, controlled manner. It is necessary because the renderer process runs in a sandboxed environment without direct access to Node.js or Electron internals. Without `contextBridge`, the renderer cannot communicate with the main process or access desktop capabilities, making it essential for building secure Electron applications with rich functionality.

### How does the preload script discover available MCP clients?

The preload script discovers MCP clients by invoking the `list-clients` IPC channel through `ipcRenderer.invoke('list-clients')`. This sends a message to the main process, which returns an array of client configurations containing metadata about available tools, prompts, and resources. The preload script then dynamically generates API wrappers for each client based on this configuration, ensuring the renderer always has access to the current set of registered MCP servers without hardcoding client-specific logic.

### Can the renderer process directly call MCP servers without the preload script?

No, the renderer process cannot directly call MCP servers without the preload script. Electron's security model isolates the renderer in a sandbox that prohibits direct access to Node.js modules, network sockets, or the file system. MCP servers typically require these capabilities to function. The preload script acts as the only authorized intermediary, using `contextBridge` to expose carefully controlled methods that forward requests to the main process, which then handles the actual MCP server communication.