Skip to main content
Glama
japan08

multi-gmail-mcp

by japan08
README.md
# multi-gmail-mcp

Multi-account Gmail MCP server for Claude Desktop, Cursor, and other MCP hosts.

It lets your assistant:
- connect one or more Gmail accounts
- scan the inbox with lightweight **list** mode (metadata only)
- load **one full thread at a time** with `get_thread` (plain or quote-stripped bodies)
- draft replies from real message text (not Gmail snippets)
- send **replies** (`send`) or **new outbound** mail (`send_new`), including HTML bodies
- save drafts to Gmail
- send only after explicit approval
- manage follow-up reminders for email threads

**Safety rule:** nothing is ever sent automatically. `send`, `send_new`, and `followup_send` must only be called after human approval.

---

## What This MCP Does

This server is **thread-first**, not message-first, and **metadata-first** for inbox triage.

- `multi_gmail_fetch` with **`mode=list`** (default) returns small per-thread metadata — snippets are **not** full emails
- for each thread you care about, call **`multi_gmail_get_thread` once** — never load many full threads in one LLM context
- `multi_gmail_get_thread` returns a chronological transcript (`format`, `stripped`, `latestN`)
- `multi_gmail_send` sends an approved **reply** (`messageId` required); **`multi_gmail_send_new`** starts a **new thread** (standalone outbound mail)
- outbound tools support **`format`**: `text/plain` (default) or `text/html` (pricing tables, CTAs)
- legacy `mode=full` on fetch still batch-loads and auto-drafts (token-heavy — avoid for normal inbox review)
- `multi_gmail_followup_due` refreshes the thread before showing a due follow-up

This project is designed for setups where one person may handle:
- multiple Gmail accounts
- multiple chats against one MCP server session
- inbox review plus follow-up workflows in the same toolset

---

## Requirements

- Node.js `18+`
- a Gmail account
- Claude Desktop, Cursor, or another MCP-compatible host
- a Google Cloud project with Gmail API enabled

Check Node:

```bash
node --version
```

---

## Install

```bash
npm install -g @nitsantechnologies/multi-gmail-mcp
mkdir -p ~/.multi-gmail-mcp
```

Set this env var anywhere you run the server or auth command:

```bash
export MULTI_GMAIL_MCP_HOME="$HOME/.multi-gmail-mcp"
```

Useful commands:

```bash
multi-gmail-mcp
multi-gmail-mcp-auth
```

Local development from git still works:

```bash
git clone https://github.com/nitsan-ai/Multi-Gmail-MCP.git
cd Multi-Gmail-MCP
npm install
npm start
npm run auth
```

---

## Google OAuth Setup

1. Open [Google Cloud Console](https://console.cloud.google.com)
2. Create or select a project
3. Enable **Gmail API**
4. Go to **APIs & Services -> Credentials**
5. Create **OAuth client ID**
6. Choose **Desktop app**
7. Download the JSON file
8. Save it in your config home as:

```text
~/.multi-gmail-mcp/credentials.json
```

Set a different location with `MULTI_GMAIL_MCP_HOME`. For local git development, project root still works by default.

OAuth scopes used by this MCP:
- `https://www.googleapis.com/auth/gmail.readonly`
- `https://www.googleapis.com/auth/gmail.modify`
- `https://www.googleapis.com/auth/gmail.send`
- `https://www.googleapis.com/auth/gmail.settings.basic` (Gmail signature on send/draft)

**After upgrading:** re-authenticate every connected account once so tokens include `gmail.settings.basic` (required for automatic signature appending on `send`, `set_draft`, and `followup_send`):

```bash
MULTI_GMAIL_MCP_HOME="$HOME/.multi-gmail-mcp" multi-gmail-mcp-auth --alias <your-alias>
```

Then reconnect in your MCP client (`connect` + `connect_finish`).

---

## Local Account Auth

Authenticate the first Gmail account:

```bash
MULTI_GMAIL_MCP_HOME="$HOME/.multi-gmail-mcp" multi-gmail-mcp-auth
```

Add another account with an alias:

```bash
MULTI_GMAIL_MCP_HOME="$HOME/.multi-gmail-mcp" multi-gmail-mcp-auth --alias work
```

Notes:
- token files are saved under `$MULTI_GMAIL_MCP_HOME/accounts/`
- each alias gets its own token JSON
- `--account` is accepted as a synonym for `--alias`

---

## Connect to Claude Desktop

Edit:

```text
~/Library/Application Support/Claude/claude_desktop_config.json
```

Example:

```json
{
  "mcpServers": {
    "multi-gmail-mcp": {
      "command": "npx",
      "args": [
        "-y",
        "@nitsantechnologies/multi-gmail-mcp"
      ],
      "env": {
        "MULTI_GMAIL_MCP_HOME": "/Users/you/.multi-gmail-mcp"
      }
    }
  }
}
```

Replace `/Users/you/.multi-gmail-mcp` with your real path.

After saving:
- fully quit Claude Desktop
- reopen Claude Desktop

---

## Connect to Cursor

Open **Cursor Settings -> MCP -> Add server** and use:

```json
{
  "multi-gmail-mcp": {
    "command": "npx",
    "args": [
      "-y",
      "@nitsantechnologies/multi-gmail-mcp"
    ],
    "env": {
      "MULTI_GMAIL_MCP_HOME": "/Users/you/.multi-gmail-mcp"
    }
  }
}
```

Replace `/Users/you/.multi-gmail-mcp` with your real path.

---

## First-Time Flow Inside Claude or Cursor

Run this once per account:

1. `multi_gmail_status`
2. `multi_gmail_connect` with `Connect you@example.com personal`
3. `multi_gmail_connect_finish`
4. `multi_gmail_set_signer`
5. `multi_gmail_fetch` with `mode="list"`
6. `multi_gmail_get_thread` for each thread you will read or reply to
7. review drafts
8. use `multi_gmail_send` (reply) or `multi_gmail_send_new` (new outbound) only after approval

Typical signer example:

```text
Set signer name to Jane Smith
```

---

## Daily Workflow

### Recommended inbox review (list → one thread at a time)

Ask:

```text
Fetch my inbox with mode list, then get_thread for threads I need to reply to
```

**Step 1 — triage (small payload):**

```json
{ "mode": "list", "maxResults": 10 }
```

You get per thread: `threadId`, `latestMessageId`, subject, participants, date, direction, **snippet** (preview only).

**Step 2 — full bodies (one thread per call):**

```json
{
  "threadId": "<from fetch>",
  "format": "full",
  "stripped": false
}
```

- `stripped=false` (default) — full plain-text body per message (best for reading mail)
- `stripped=true` — quote/signature-stripped text (best when drafting in long threads)
- `format=latest` + `latestN` — first message + last N messages, with omission markers

Present `message.text` **verbatim** to the user — do not summarize snippets or bodies.

**Step 3 — draft and send:**

- Reply in an existing thread → `multi_gmail_send` with `messageId` from fetch / `get_thread`
- New outbound email → `multi_gmail_send_new` (no `messageId`)

Optional: `mode=full` on fetch still auto-drafts every thread in one batch (legacy; can cause token overflow).

With `mode=list`, follow-up-labeled threads are excluded from normal inbox review. Optional markdown export is written unless `writeMarkdownFile: false`. Nothing is sent until you approve.

After review, per item you can:
- `send` or `send_new`
- `set_draft`
- `edit`
- `cancel` / `skip`

### Follow-up review

Ask:

```text
Show due follow-ups
```

What happens:
- due reminders are loaded
- the thread is refreshed from Gmail first
- if the recipient already replied, the reminder is resolved automatically
- otherwise a fresh follow-up draft is shown

---

## Tool Reference

The MCP registers both prefixed and unprefixed names:
- `multi_gmail_fetch` and `fetch`
- `multi_gmail_followup_due` and `followup_due`
- etc.

In practice, most hosts will show the `multi_gmail_*` names.

### Setup and status tools

#### `multi_gmail_help`
Shows the first-time setup flow.

Input: none

#### `multi_gmail_status`
Shows:
- connected account
- signer status
- due follow-ups
- last inbox batch
- useful local paths

Input:
- `accountAlias` optional
- `chatScope` optional

#### `multi_gmail_connect`
Starts Gmail login for one account and opens the browser.

Input:
- `command` required

Format:

```text
Connect you@example.com personal
Connect you@example.com work
```

#### `multi_gmail_connect_finish`
Completes the login started by `connect`.

Input:
- `code` optional
- `pendingAlias` optional
- `chatScope` optional

Normally you do not need to paste the code manually; the local callback server completes it.

#### `multi_gmail_accounts`
Lists all saved local account aliases.

Input: none

#### `multi_gmail_set_signer`
Stores the display name used in draft replies for the current session.

Input:
- `name` required
- `followUpLabel` optional
- `accountAlias` optional
- `chatScope` optional

#### `multi_gmail_set_mode`
Switches response mode for the current chat scope.

Input:
- `mode` required: `standard` or `compact`

#### `multi_gmail_diagnostics`
Checks:
- credentials file
- accounts directory
- reminder store
- active account binding

Input:
- `accountAlias` optional
- `chatScope` optional

#### `multi_gmail_setup_labels`
Ensures the configured Gmail labels exist.

Useful if labels do not appear after connect or fetch.

Input:
- `accountAlias` optional
- `chatScope` optional

---

### Inbox and thread tools

#### `multi_gmail_fetch`
Lists inbox threads for triage, or (legacy) batch-loads full threads and auto-drafts replies.

**Recommended:** `mode=list` (default), then `get_thread` per selected thread.

| `mode` | Behavior |
|--------|----------|
| `list` (default) | Metadata only: `threadId`, `latestMessageId`, subject, participants, snippet, dates, direction. Snippets are **not** full emails. |
| `full` | Legacy: loads full bodies and drafts every thread in one call — **token-heavy**; avoid for normal inbox review. |

Important behavior:
- `maxResults` means **unique inbox threads**
- `list` returns `messageId` / `threadId` for `send` and `get_thread`
- `includeLatestBody` (list only): optional latest-message plain body per thread, capped at 15 threads — still prefer `get_thread` for one thread
- Response includes `gmailListQuery` — the exact Gmail `q` string sent to the API

| `queryMode` | Behavior |
|-------------|----------|
| `inbox` (default) | Prepends inbox review filters (`in:inbox`, excludes follow-up label) |
| `raw` | Passes `query` directly to Gmail — use for sent mail, archives, all-mail, date filters |

Input:
- `mode` optional: `list` or `full`, default `list`
- `maxResults` optional, default `20`, max `100`
- `query` optional Gmail search string (combined with inbox filters when `queryMode=inbox`)
- `queryMode` optional: `inbox` or `raw`, default `inbox`
- `includeLatestBody` optional, default `false` (list mode only)
- `saveGmailDrafts` optional, default `false` (mainly `full` mode)
- `writeMarkdownFile` optional, default `true` (`full` mode / review export)
- `accountAlias` optional
- `chatScope` optional

Examples:

```json
{
  "mode": "list",
  "maxResults": 10
}
```

```json
{
  "mode": "list",
  "maxResults": 15,
  "query": "newer_than:7d"
}
```

Sent-mail / historical analysis (raw Gmail query):

```json
{
  "mode": "list",
  "queryMode": "raw",
  "query": "in:sent after:2026/01/01 before:2026/04/01",
  "maxResults": 50
}
```

Legacy batch mode (avoid for daily triage):

```json
{
  "mode": "full",
  "maxResults": 5,
  "writeMarkdownFile": false
}
```

#### `multi_gmail_send`
Sends one approved **reply** in an existing thread. Marks the source message read and returns IDs for follow-up threading.

For **new outbound** mail (no source message), use `multi_gmail_send_new` instead.

Input:
- `messageId` required — from `fetch` or `get_thread`
- `to` required
- `subject` required
- `body` required unless legacy `html` is set
- `format` optional: `text/plain` (default) or `text/html` — when `text/html`, `body` is the HTML part (plain part auto-generated)
- `html` optional — legacy HTML part (prefer `format` + `body`)
- `cc` / `bcc` optional — single email, comma-separated string, array, or `Name <email@example.com>` (multiple recipients)
- `quoteOriginal` optional, default `true` — append Gmail-style quoted parent history below your new body (`false` = new body only)
- `accountAlias` optional
- `chatScope` optional

Response includes: `sentMessageId`, `threadId`, `isNewThread: false`, `markedReadMessageId`.

Example (plain reply):

```json
{
  "messageId": "19e90840fbfd1961",
  "to": "recipient@example.com",
  "subject": "Re: Pricing",
  "body": "Thanks — here is the updated quote."
}
```

#### `multi_gmail_send_new`
Sends one approved **new** email. Use for standalone outbound mail or follow-ups in the same thread.

Input:
- `to` required
- `threadId` optional — from a prior `send_new` response; adds this message to that Gmail thread (follow-up in same thread)
- `subject` required — Unicode (–, ü, €, etc.) is RFC 2047–encoded automatically
- `body` required unless legacy `htmlBody` is set
- `format` optional: `text/plain` (default) or `text/html`
- `htmlBody` optional — legacy HTML (prefer `format` + `body`)
- `cc` optional
- `bcc` optional
- `accountAlias` optional
- `chatScope` optional

Response includes: `sentMessageId`, `threadId`, `isNewThread: true` — store `threadId` for phase-2 threading.

Example (HTML outbound):

```json
{
  "to": "recipient@example.com",
  "subject": "Product — pricing overview",
  "format": "text/html",
  "body": "<h1>Hello</h1><p>See the <a href=\"https://example.com/pricing\">pricing table</a>.</p>"
}
```

#### `multi_gmail_set_draft`
Creates or updates a Gmail draft reply without sending.

Input:
- `messageId` required
- `to` required
- `subject` required
- `body` required unless legacy `html` is set
- `format` optional: `text/plain` (default) or `text/html`
- `html` optional — legacy HTML part
- `cc` / `bcc` optional — comma-separated string, array, or `Name <email@example.com>`
- `quoteOriginal` optional, default `true` — append quoted parent history (Gmail `···` expander)
- `accountAlias` optional
- `chatScope` optional

#### `multi_gmail_get_thread`
Loads **one** Gmail thread as an ordered transcript. Call after `fetch` `mode=list` when you need real message bodies.

| `format` | Behavior |
|--------|----------|
| `full` (default) | All messages with bodies |
| `latest` | First message + latest `latestN` messages; middle messages collapsed with `[N earlier messages omitted]` |
| `metadata` | Headers/dates only — no bodies |

Body options (when `format` is not `metadata`):
- `stripped` default `false` — full plain-text per message
- `stripped=true` — removes quoted reply history and signatures (drafting in long threads)
- `includeRaw=true` — adds `rawText` alongside stripped `text` for debugging

Input:
- `threadId` required — from `fetch`, `followup_due`, or `archive`
- `format` optional: `metadata`, `latest`, or `full` (default `full`)
- `latestN` optional, default `5`, max `50` (when `format=latest`)
- `stripped` optional, default `false`
- `includeRaw` optional, default `false`
- `accountAlias` optional
- `chatScope` optional

Example (read full mail):

```json
{
  "threadId": "19e913bf5e640ea2",
  "format": "full",
  "stripped": false
}
```

Example (draft in a long thread):

```json
{
  "threadId": "19e913bf5e640ea2",
  "format": "full",
  "stripped": true
}
```

#### `multi_gmail_archive`
Archives one thread by removing the `INBOX` label.

Input:
- `threadId` required
- `accountAlias` optional
- `chatScope` optional

#### `multi_gmail_fetch_drafts`
Lists messages in Gmail Drafts.

Input:
- `maxResults` optional
- `accountAlias` optional
- `chatScope` optional

#### `multi_gmail_fetch_sent` (deprecated)
Lists the latest messages in Gmail Sent without query filters.

**Prefer** `multi_gmail_fetch` with `queryMode=raw` and a Gmail query such as `in:sent after:2026/01/01 before:2026/04/01` for filtered sent-mail and historical analysis.

Input:
- `maxResults` optional
- `accountAlias` optional
- `chatScope` optional

---

### Follow-up tools

#### `multi_gmail_followup_trigger`
Creates a new follow-up plan for a thread, or updates the existing open plan for that same thread.

Input:
- `messageId` required
- `pattern` optional
- `daysList` optional
- `businessDaysOnly` optional, default `false`
- `dueWeekday` optional
- `createGmailDraft` optional, default `false`
- `accountAlias` optional
- `chatScope` optional

Rules:
- use either `pattern` or `daysList`
- not both
- `daysList` must be ascending
- duplicates are not allowed

Examples:

```json
{
  "messageId": "gmail-message-id",
  "daysList": [1, 3]
}
```

```json
{
  "messageId": "gmail-message-id",
  "pattern": "1, 3, 7 business days",
  "businessDaysOnly": true
}
```

Scheduling behavior:
- first entry is due from now
- later entries are **chained**
- example: `[1, 3]` means:
  - follow-up 1 in 1 day
  - follow-up 2 in 3 days **after follow-up 1 is sent**

#### `multi_gmail_followup_due`
Lists due follow-up reminders for the active account.

Behavior:
- refreshes the thread before returning results
- includes full `threadContext`
- skips reminders if the recipient already replied

Input:
- `accountAlias` optional
- `chatScope` optional

#### `multi_gmail_followup_send`
Sends one approved follow-up email.

Input:
- `reminderId` required
- `to` optional
- `subject` optional
- `body` optional (unless legacy `html` is set)
- `format` optional: `text/plain` (default) or `text/html`
- `html` optional — legacy HTML part
- `cc` / `bcc` optional — comma-separated string, array, or `Name <email@example.com>`
- `quoteOriginal` optional, default `true` — append quoted parent history below the follow-up body
- `accountAlias` optional
- `chatScope` optional

Only use this after the user explicitly approves the draft.

#### `multi_gmail_followup_cleanup`
Deletes follow-up reminder records from the local store.

Input filters:
- `reminderIds`
- `messageId` for Gmail internal message id
- `messageHeaderId` for RFC `Message-ID` from Gmail **Show original**
- `sourceThreadId`
- `followUpChainId`
- `deleteAll` with `confirm: true`
- `statuses`
- `cancelChain`
- `removeGmailLabel`
- `accountAlias`
- `chatScope`

Examples:

Delete by RFC `Message-ID`:

```json
{
  "messageHeaderId": "<abc123@example.com>"
}
```

Delete one chain:

```json
{
  "reminderIds": ["reminder-id"],
  "cancelChain": true
}
```

Delete all reminders for an account:

```json
{
  "deleteAll": true,
  "confirm": true
}
```

---

## Multiple Accounts and Shared Sessions

### `accountAlias`

Use `accountAlias` when you want to target a specific saved local account:

```json
{
  "accountAlias": "work"
}
```

### `chatScope`

Some MCP hosts reuse one server session across many chats.

In that case, pass the same `chatScope` on:
- `connect`
- `connect_finish`
- `set_signer`
- every later Gmail tool in that same chat

Example:

```json
{
  "chatScope": "work-inbox"
}
```

This keeps one chat’s active account binding separate from another chat’s.

---

## Files and Data

Important local paths under `$MULTI_GMAIL_MCP_HOME`:

- `credentials.json`
  Google OAuth client credentials
- `accounts/`
  saved Gmail OAuth tokens, one JSON file per alias
- `data/followup-reminders.json`
  local follow-up reminder store
- `data/inbox-reviews/latest-inbox-review.md`
  latest markdown inbox review export

Project layout:

```text
~/.multi-gmail-mcp/
├── credentials.json
├── accounts/
└── data/
    ├── followup-reminders.json
    └── inbox-reviews/
```

Useful env vars:

| Name | Purpose |
|------|---------|
| `MULTI_GMAIL_MCP_HOME` | base directory for credentials, tokens, and local data |
| `GOOGLE_CREDENTIALS_PATH` | absolute or config-home-relative path to OAuth credentials |
| `ACCOUNTS_DIR` | absolute or config-home-relative path to token files |
| `FOLLOWUP_REMINDERS_PATH` | absolute or config-home-relative reminder store path |
| `GMAIL_REVIEW_MARKDOWN_DIR` | absolute or config-home-relative inbox export directory |

**Dependencies** (for HTML bodies and quote stripping): `email-reply-parser`, `planer`, `jsdom`.

---

## Gmail Labels

This MCP can create Gmail user labels automatically.

Expected labels:
- `Inbox-review`
- `Multi-Gmail-MCP Follow-up`

Use `multi_gmail_setup_labels` if they do not appear.

---

## Troubleshooting

### Server not showing in Claude or Cursor

- make sure `MULTI_GMAIL_MCP_HOME` points at your real config directory
- make sure `node --version` is `18+`
- fully quit and reopen the app
- if `npx` is unavailable inside the app, install globally and use the full path to `multi-gmail-mcp`

### `multi_gmail_status` not available

Usually this means the MCP server did not start at all.

Check:
- JSON config syntax
- absolute path to the server entry
- Node availability
- app restart after config change

### Token expired or auth errors

Re-authenticate:

```bash
rm ~/.multi-gmail-mcp/accounts/*.json
MULTI_GMAIL_MCP_HOME="$HOME/.multi-gmail-mcp" multi-gmail-mcp-auth
```

### Permission or scope errors

Make sure:
- Gmail API is enabled
- OAuth client is a **Desktop app**
- scopes include `gmail.modify`, `gmail.send`, and `gmail.settings.basic`

Then re-authenticate (see **Local Account Auth** above).

### Multiple accounts not working

- run `multi_gmail_accounts`
- verify the alias exists
- re-authenticate missing accounts:

```bash
MULTI_GMAIL_MCP_HOME="$HOME/.multi-gmail-mcp" multi-gmail-mcp-auth --alias work
```

### Labels missing in Gmail

Run:

```text
multi_gmail_setup_labels
```

If that still fails, re-authenticate so the token includes `gmail.modify`.

### Setup state unclear

Run:

```text
multi_gmail_diagnostics
```

---

## Security Notes

- `credentials.json` contains your Google OAuth client secret
- `accounts/` contains Gmail refresh/access tokens
- never commit either of those paths
- `.gitignore` already excludes them
- token files are written with mode `0600`

This repository may also write:
- local reminder data
- local markdown inbox review exports

Treat those as sensitive personal data.

---

## License

MIT

---

## Company

Developed by [NITSAN Technologies](https://nitsan.ai/ "https://nitsan.ai/")

TDQS

C2.9/5.0

Scored across 42 tools

Disambiguation2/5

Every tool appears twice with and without the 'multi_gmail_' prefix, making it hard for an agent to distinguish which version to call. Additionally, 'fetch_sent' is deprecated but still present, causing overlap with the more general 'fetch' tool.

Naming Consistency2/5

The tool naming is inconsistent due to the dual prefixes. While each group individually follows a verb_noun pattern, the coexistence of unprefixed and 'multi_gmail_' prefixed tools violates consistency and suggests redundancy.

Tool Count2/5

With 42 tools, the count is inflated because of deliberate duplication. The actual unique tool set is about 21, which would be appropriate for a Gmail MCP, but the duplication makes the tool surface unnecessarily large and confusing.

Completeness4/5

The unique tool set covers essential Gmail operations: fetch, read, send, draft, archive, follow-ups, account management, and diagnostics. Minor gaps exist (e.g., no delete or label management), but core workflows are well-supported.

Maintenance

ActivityInactive
ResponsivenessNo issues