Skip to main content
Glama
vasparshin

zoho-multi-mcp

by vasparshin
README.md
# zoho-multi-mcp

An [MCP (Model Context Protocol)](https://modelcontextprotocol.io) server that connects AI assistants like Claude to your Zoho Mail account(s). Read, send, search, reply to, and delete emails directly from your AI workflow — and run **any number of Zoho mailboxes from one lightweight process**, instead of one process per mailbox.

This is a fork of [FujiwaraChoki/zoho-mail-mcp](https://github.com/FujiwaraChoki/zoho-mail-mcp) (MIT licensed, see `LICENSE`) with multi-account support added. All credit for the original single-account server goes to the upstream author.

## Why this fork exists

If you run several Zoho mailboxes through MCP (a team, a set of project-specific senders, whatever), the natural setup is one server process per mailbox. That works, but each process pays the same fixed Bun/V8 runtime memory overhead — roughly 75-85MB RSS — regardless of how small or idle the mailbox is. Three mailboxes cost three times that baseline for no benefit, since the actual per-account state (an OAuth token, a folder cache) is tiny.

This fork adds an opt-in multi-account mode: one process can serve several Zoho accounts, either as one endpoint where the caller specifies which account per call, or — the safer option for an existing setup — as several listeners on their own fixed ports, each behaving exactly like a plain single-account server. Measured in production: 3 separate single-account processes at ~230MB combined RSS, consolidated into one process at ~90MB RSS serving the same 3 mailboxes. Everything below the "Multi-account mode" section is unchanged from upstream and works exactly as it always has if you only need one mailbox.

## Tools

| Tool | Description |
|------|-------------|
| `list_folders` | List all mail folders |
| `list_emails` | List emails in a folder with filtering and pagination |
| `read_email` | Read the full content of a specific email |
| `search_emails` | Search emails by keyword across all folders |
| `send_email` | Send a new email |
| `reply_email` | Reply to an existing email (reply / reply-all) |
| `delete_email` | Delete an email (trash or permanent) |

## Setup

### 1. Create a Zoho API Client

1. Go to the [Zoho API Console](https://api-console.zoho.eu/) (use `.com` for US datacenter)
2. Click **Add Client** > **Self Client**
3. Note your **Client ID** and **Client Secret**

### 2. Generate a Refresh Token

Run the interactive setup helper:

```bash
bun run setup.ts
```

Or manually:

1. In the Self Client, generate a grant code with these scopes:
   ```
   ZohoMail.accounts.READ,ZohoMail.folders.READ,ZohoMail.messages.READ,ZohoMail.messages.CREATE,ZohoMail.messages.DELETE
   ```
2. Set duration to 10 minutes, add a description, and click **Create**
3. Copy the generated code and exchange it using the setup script

### 3. Verify Credentials

```bash
ZOHO_CLIENT_ID=your_id ZOHO_CLIENT_SECRET=your_secret ZOHO_REFRESH_TOKEN=your_token bun run setup.ts --verify
```

### 4. Configure Your MCP Client

#### Single account — Claude Code (`~/.claude.json`)

```json
{
  "mcpServers": {
    "zoho-mail": {
      "command": "bun",
      "args": ["run", "/path/to/zoho-multi-mcp/src/index.ts"],
      "env": {
        "ZOHO_CLIENT_ID": "your_client_id",
        "ZOHO_CLIENT_SECRET": "your_client_secret",
        "ZOHO_REFRESH_TOKEN": "your_refresh_token",
        "ZOHO_DATACENTER": "eu"
      }
    }
  }
}
```

Same shape for Claude Desktop's `claude_desktop_config.json`.

## Multi-account mode

Repeat any of the steps above once per Zoho account you want to run through this server, then choose one of two modes.

### Mode A — one endpoint, caller picks the account

Set `ZOHO_ACCOUNTS_JSON` to a JSON array of accounts and start the server with `ZOHO_HTTP_PORT` as normal (or stdio). Every tool gains a required `account` field (a string enum of the ids you gave), and every call must say which account it's for.

```bash
export ZOHO_ACCOUNTS_JSON='[
  {"id": "work", "clientId": "...", "clientSecret": "...", "refreshToken": "..."},
  {"id": "personal", "clientId": "...", "clientSecret": "...", "refreshToken": "..."}
]'
export ZOHO_HTTP_PORT=8010
bun run src/index.ts
```

Good when your MCP client is fine with an explicit `account` argument on every call and you want a single URL to configure.

### Mode B — one process, several fixed-account listeners

Set `ZOHO_ACCOUNTS_JSON` as above, and set `ZOHO_MULTI_PORT_JSON` to bind each account to its own port. Every listener behaves like a genuine single-account server — no `account` field on any tool, byte-identical to a plain single-account setup on that port.

```bash
export ZOHO_ACCOUNTS_JSON='[
  {"id": "work", "clientId": "...", "clientSecret": "...", "refreshToken": "..."},
  {"id": "personal", "clientId": "...", "clientSecret": "...", "refreshToken": "..."}
]'
export ZOHO_MULTI_PORT_JSON='[
  {"id": "work", "port": 8010, "name": "zoho-mail-work"},
  {"id": "personal", "port": 8011, "name": "zoho-mail-personal"}
]'
bun run src/index.ts
```

This is the mode to use when migrating an EXISTING multi-process setup: point your MCP client config at the same ports it already uses, start this one process instead of the several you used to run, and nothing else needs to change. `name` is optional and only affects log line labels.

Neither mode is required — omit both `ZOHO_ACCOUNTS_JSON` and `ZOHO_MULTI_PORT_JSON` and the server behaves exactly as the original single-account upstream project does, reading `ZOHO_CLIENT_ID`/`ZOHO_CLIENT_SECRET`/`ZOHO_REFRESH_TOKEN` directly.

## Environment Variables

| Variable | Required | Description |
|----------|----------|-------------|
| `ZOHO_CLIENT_ID` | Single-account mode only | OAuth2 Client ID from Zoho API Console |
| `ZOHO_CLIENT_SECRET` | Single-account mode only | OAuth2 Client Secret |
| `ZOHO_REFRESH_TOKEN` | Single-account mode only | OAuth2 Refresh Token (generated via setup) |
| `ZOHO_DATACENTER` | No | Zoho datacenter: `eu` (default), `us`, `in`, `au`, `jp` |
| `ZOHO_ACCOUNTS_JSON` | Multi-account mode only | JSON array of `{id, clientId, clientSecret, refreshToken, datacenter?}` |
| `ZOHO_MULTI_PORT_JSON` | Multi-port mode only | JSON array of `{id, port, name?}`; each `id` must appear in `ZOHO_ACCOUNTS_JSON` |
| `ZOHO_HTTP_PORT` | No | Run as an HTTP server on this port instead of stdio |
| `ZOHO_SERVER_NAME` | No | Log-line label for stdio / single-port HTTP mode |

## Features

- **OAuth2 with auto-refresh** - tokens are refreshed automatically, no manual intervention needed, cached per-account in multi-account mode
- **Rate limiting** - built-in sliding window rate limiter (30 req/min) to stay within Zoho API limits
- **Retry on 401** - automatically refreshes token and retries on authentication failures
- **HTML to plain text** - email content is converted to clean plain text for AI consumption
- **Multi-account, low memory** - see "Why this fork exists" above

## Requirements

- [Bun](https://bun.sh) runtime
- One or more Zoho Mail accounts with API access

## License

MIT (c) 2026 Sami Hindi — see `LICENSE`. Multi-account/multi-port additions in this fork are released under the same license.

Maintenance

ActivityMaintained
ResponsivenessNo issues