# How to Implement Custom Request Handlers for MCP Protocol Messages in chat-mcp

> Learn how to implement custom request handlers for MCP protocol messages in chat-mcp by registering a handler function with client.setRequestHandler() and exposing it via Electron's IPC bridge.

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

---

**You can implement custom request handlers for MCP protocol messages by registering a handler function with `client.setRequestHandler()` in [`src/main/client.ts`](https://github.com/ai-ql/chat-mcp/blob/main/src/main/client.ts), then exposing the capability through Electron's IPC bridge to make it callable from the renderer process.**

The chat-mcp repository provides an Electron-based interface for Model Context Protocol (MCP) integrations, enabling seamless communication between language models and external tools. Implementing custom request handlers allows you to extend the protocol with bespoke operations that fit your specific workflow requirements, leveraging the existing three-layer architecture that connects the MCP client to the UI.

## Understanding the MCP Handler Architecture

The implementation relies on a specific flow across three core files that handle client initialization, IPC registration, and renderer exposure.

### The Three-Layer Architecture

**Layer 1: Client Construction ([`src/main/client.ts`](https://github.com/ai-ql/chat-mcp/blob/main/src/main/client.ts))**

The `initializeClient` function creates the MCP `Client` instance using `StdioClientTransport` and registers default request handlers. This is where you attach custom logic using `client.setRequestHandler()`. The built-in sampling handler (lines 20-30) demonstrates this pattern by registering a handler for `CreateMessageRequestSchema`.

**Layer 2: IPC Registration ([`src/main/main.ts`](https://github.com/ai-ql/chat-mcp/blob/main/src/main/main.ts))**

The `registerIpcHandlers` function creates Electron `ipcMain.handle` channels for every MCP method. It forwards requests to the client via `manageRequests`, which acts as the bridge between Electron's IPC system and the MCP client instance. This loop (lines 48-55) automatically generates channel names in the format `<serverName>-<method>`.

**Layer 3: Renderer Bridge ([`src/preload/preload.ts`](https://github.com/ai-ql/chat-mcp/blob/main/src/preload/preload.ts))**

The `exposeAPIs` function builds an object exposing all MCP servers as `window.mcpServers`. Each server contains callable methods that use `ipcRenderer.invoke` to communicate with the registered IPC handlers, making the custom handlers accessible from frontend code as ordinary async functions.

## Implementing a Custom Request Handler

Follow these steps to add a new request handler for a custom MCP protocol message.

### Step 1: Define the Request Schema

Create a Zod schema that validates the incoming request structure. While optional for simple handlers, explicit typing ensures compatibility with the MCP specification.

```typescript
// src/main/types.ts
import { z } from 'zod';

export const MyCustomRequestSchema = z.object({
  action: z.string(),
  payload: z.record(z.any()),
});

export const MyCustomResultSchema = z.object({
  status: z.enum(['ok', 'error']),
  data: z.record(z.any()),
});

```

### Step 2: Register the Handler in the Client

Import your schema into [`src/main/client.ts`](https://github.com/ai-ql/chat-mcp/blob/main/src/main/client.ts) and register the handler within `initializeClient` using `client.setRequestHandler()`.

```typescript
// src/main/client.ts (inside initializeClient)
import { MyCustomRequestSchema, MyCustomResultSchema } from './types.js';

client.setRequestHandler(MyCustomRequestSchema, async (request) => {
  console.log('Received custom MCP request:', request);

  // Implement your business logic
  const processed = await myBusinessLogic(request.payload);

  return {
    status: 'ok',
    data: processed,
  } as z.infer<typeof MyCustomResultSchema>;
});

```

This follows the same pattern as the built-in sampling handler at lines 20-30, attaching your function to the client instance before it connects to the transport.

### Step 3: Configure the Server Capability

Add the new method to your server configuration so that `registerIpcHandlers` in [`src/main/main.ts`](https://github.com/ai-ql/chat-mcp/blob/main/src/main/main.ts) automatically creates the IPC channel.

```json
// src/main/config.json (example snippet)
{
  "mcpServers": {
    "myServer": {
      "command": "node my-server.js",
      "customMethod": "myServer-tools-customMethod"
    }
  }
}

```

The key `customMethod` maps to the IPC channel name format `<serverName>-<method>`, which the registration loop uses to create the `ipcMain.handle` listener.

### Step 4: Invoke from the Renderer

After the preload script exposes the APIs, call your custom handler from the frontend using the `window.mcpServers` global.

```javascript
// In a Vue component or renderer script
async function invokeCustom() {
  const result = await window.mcpServers.myServer.tools.customMethod({
    action: 'process',
    payload: { foo: 'bar' }
  });
  console.log('Custom result:', result);
}

```

The preload bridge converts this ordinary async function call into an `ipcRenderer.invoke` message, which travels through the IPC channel to your registered handler in the main process.

## Key Files and Their Roles

| File | Purpose | Key Functions |
|------|---------|---------------|
| [`src/main/client.ts`](https://github.com/ai-ql/chat-mcp/blob/main/src/main/client.ts) | Creates the MCP client and registers request handlers | `initializeClient`, `client.setRequestHandler()` |
| [`src/main/main.ts`](https://github.com/ai-ql/chat-mcp/blob/main/src/main/main.ts) | Registers Electron IPC handlers for each MCP method | `registerIpcHandlers`, `manageRequests` |
| [`src/preload/preload.ts`](https://github.com/ai-ql/chat-mcp/blob/main/src/preload/preload.ts) | Bridges IPC to the renderer process | `exposeAPIs`, `window.mcpServers` |

These files form the complete pathway from **custom MCP request handling** in the main process to **frontend invocation** in the renderer.

## Summary

- **Register handlers** using `client.setRequestHandler()` in [`src/main/client.ts`](https://github.com/ai-ql/chat-mcp/blob/main/src/main/client.ts) to process custom MCP protocol messages according to your business logic.
- **Define schemas** with Zod to ensure type safety and validation for incoming requests and outgoing responses.
- **Expose capabilities** through the automatic IPC registration in [`src/main/main.ts`](https://github.com/ai-ql/chat-mcp/blob/main/src/main/main.ts) by adding methods to your server configuration.
- **Invoke from UI** using the `window.mcpServers` API exposed by [`src/preload/preload.ts`](https://github.com/ai-ql/chat-mcp/blob/main/src/preload/preload.ts), treating MCP methods as ordinary async functions.

## Frequently Asked Questions

### What is the MCP protocol used for in chat-mcp?

The Model Context Protocol (MCP) in chat-mcp enables standardized communication between the Electron application and language model servers. It allows the UI to request context, tools, and sampling capabilities from external MCP-compatible servers through a typed request-response pattern.

### Where should I register a custom request handler?

You should register custom request handlers inside the `initializeClient` function in [`src/main/client.ts`](https://github.com/ai-ql/chat-mcp/blob/main/src/main/client.ts). This ensures the handler is attached to the client instance immediately after creation but before the transport connection is established, following the same pattern used for the built-in `CreateMessageRequestSchema` handler.

### How does the frontend communicate with custom MCP handlers?

The frontend communicates through the `window.mcpServers` global object exposed by the preload script. When you call a method like `window.mcpServers.myServer.tools.customMethod()`, the preload script uses `ipcRenderer.invoke` to send the request to the main process, where `manageRequests` in [`src/main/main.ts`](https://github.com/ai-ql/chat-mcp/blob/main/src/main/main.ts) forwards it to the registered handler.