gmail-mcp
by klodr
README.md
# π§ gmail-mcp
> Read, search, send, draft, label, filter, and thread Gmail from any MCP-enabled AI assistant. Wraps the [Gmail API](https://developers.google.com/gmail/api) with scope-gated tools and in-process safeguards.
[](https://github.com/klodr/gmail-mcp/actions/workflows/ci.yml)
[](https://github.com/klodr/gmail-mcp/actions/workflows/codeql.yml)
[](https://vitest.dev)
[](https://codecov.io/gh/klodr/gmail-mcp)
[](https://scorecard.dev/viewer/?uri=github.com/klodr/gmail-mcp)
[](https://www.bestpractices.dev/projects/12613)
[](https://socket.dev/npm/package/@klodr/gmail-mcp)
[](https://coderabbit.ai)
[](https://www.npmjs.com/package/@klodr/gmail-mcp)
[](https://www.npmjs.com/package/@klodr/gmail-mcp)
[](https://nodejs.org)
[](https://modelcontextprotocol.io)
[](https://modelcontextprotocol.io)
[](https://github.com/klodr/gmail-mcp/pulls)
[](https://github.com/sponsors/klodr)
[](https://www.patreon.com/klodr)
[](https://ko-fi.com/klodr)
> [!NOTE]
> Hardened + enhanced fork of [GongRzhe/Gmail-MCP-Server](https://github.com/GongRzhe/Gmail-MCP-Server) (archived 2026-03-03), via [ArtyMcLabin/Gmail-MCP-Server](https://github.com/ArtyMcLabin/Gmail-MCP-Server). Since the divergence point: **180+ commits** and an extensive rewrite β security hardening, Gmail-surface improvements (reply-all, send-as alias, thread-level tools, download-to-disk, recipient pairing, batch ops with retryβ¦), supply-chain hygiene, and CI gating. Every PR goes through CodeRabbit + dual-model Qodo Merge before merge. See [SECURITY.md](.github/SECURITY.md) for the controls and threat model, and the [comparison table](#-why-this-mcp) below for the parent-forks delta.
A Model Context Protocol (MCP) server that lets AI assistants (Claude Desktop, Claude Code, Cursor, Continue, OpenClawβ¦) read and manage a Gmail account through scope-gated tools. Exposes the Gmail v1 API surface you actually need (messages, threads, labels, filters, attachments, drafts, reply-all) behind a single `npx` install.
> [!IMPORTANT]
> **Positioning.** Upstream calls this fork the *maximalist* one. The label is accurate from the outside, but it misframes the intent. `klodr/gmail-mcp` is built around four properties β and the code, tests and CI/CD all exist to pay rent on these and only these:
>
> - **Secure** β input sanitization, recipient pairing, rate limiting, sender resolution, and audit logging are enforced *in the server* (not in the operator's attention). The Gmail surface is mediated, not exposed raw.
> - **Autonomous** β designed to stay safe when no human is watching every action. Different threat model from upstream's "I use it daily in my own workflow"; the in-process middleware is what bridges that gap.
> - **Tested** β 781 vitest cases across 39 files, **99.25%** statement coverage (89% branch), plus a fast-check property-based fuzz suite and a dedicated hardening test file.
> - **Resilient under acceleration** β 17 CI/CD workflows (CodeQL, OSV, Gitleaks, leak-detect, Scorecard, Socket, SLSA, lockfile lint, signed releasesβ¦) track the security ecosystem at the pace it actually changes β days, sometimes hours. Recent concrete instance: **qs CVE-2026-8723**, surfaced and shipped within hours of the public advisory across the klodr/* MCP family.
>
> Full rationale and the per-tool design trade-offs live in [`docs/DESIGN_DECISIONS.md`](docs/DESIGN_DECISIONS.md).
## β¨ Why this MCP?
Comparison of the three maintained forks of the original Gmail MCP server, focusing on what an agent platform actually needs β prompt-injection safety, supply-chain integrity, and operational hygiene:
| Capability | [GongRzhe/Gmail-MCP-Server](https://github.com/GongRzhe/Gmail-MCP-Server) (original, unmaintained) | [ArtyMcLabin/Gmail-MCP-Server](https://github.com/ArtyMcLabin/Gmail-MCP-Server) (intermediate fork) | **klodr/gmail-mcp** (this repo) |
|---|:---:|:---:|:---:|
| **Core Gmail surface** | | | |
| Send / draft / read / search messages | β
| β
| β
|
| Label CRUD | β
| β
| β
|
| Filter CRUD | β οΈ `list_filters` broken | β
fixed | β
|
| Batch modify / delete | β
| β
| β
|
| Reply threading (`In-Reply-To` / `References`) | β orphaned replies | β
| β
|
| Reply-all tool | β | β
| β
|
| Send-as alias (`from` parameter) | β | β
| β
|
| Thread-level tools (`get_thread`, `list_inbox_threads`, `get_inbox_with_threads`) | β | β
| β
|
| Download email to disk (`json`/`eml`/`txt`/`html`) | β | β
| β
|
| Download attachment | β
| β
| β
|
| **OAuth / authorization** | | | |
| `--scopes` flag for least-privilege auth | β | β
| β
|
| Tool list filtered by granted scopes | β | β
| β
|
| OAuth credentials file mode `0o600` | β | β
| β
|
| **Security β input handling** | | | |
| CRLF header injection sanitization (`\r\n\0`) | β | β οΈ partial | β
|
| Path traversal in `download_attachment` | β | β
fixed | β
|
| Path **jails** (attachment source + download destination) blocking prompt-injected exfiltration | β | β | β
|
| Symlink-safe writes (`O_NOFOLLOW` + post-`mkdir` realpath re-verification, TOCTOU defense) | β | β | β
|
| Zod bounds on `maxResults` / `batchSize` / `messageIds` length | β | β | β
|
| Cryptographic MIME boundary (`crypto.randomBytes`, not `Math.random`) | β | β | β
|
| **MCP protocol & tool surface** | | | |
| MCP SDK version | v0.4.x | v1.27.x | v1.29.x |
| Tool annotations (`readOnlyHint` / `destructiveHint` / `idempotentHint`) | β | β
| β
|
| `llms-install.md` (LLM-readable install guide) | β | β | β
|
| **Publishing / discoverability** | | | |
| Published on npm | β stale β no future releases (repo archived) | β (consumed as a GitHub install from the intermediate fork) | β
dedicated scoped package, signed releases |
| Active maintenance (last 30 d) | β (archived 2026-03-03) | β οΈ sporadic | β
daily review cycle (CodeRabbit + human) |
| **Supply-chain integrity** | | | |
| Node.js floor | β `>=14` ([EOL April 2023](https://nodejs.org/en/about/previous-releases)) | β `>=14` ([EOL April 2023](https://nodejs.org/en/about/previous-releases)) | β
`>=22` (Maintenance LTS until 2027-04-30) |
| CI: CodeQL Advanced (`javascript-typescript` + `actions`) | β | β | β
|
| CI: OpenSSF Scorecard (weekly scan + badge) | β | β | β
|
| CI: Socket Security supply-chain alerts | β | β | β
|
| CI: CodeRabbit assertive reviews on every PR | β | β | β
|
| Release: signed builds (Sigstore + SLSA in-toto attestation + npm provenance) | β | β | β
|
| Release: single-file ESM bundle | β | β | β
|
| **Testing** | | | |
| Unit/property tests | β (0 tests) | β οΈ (97 tests) | β
(**781 tests**, 39 files) |
| Statement coverage across `src/**` | 0% | 16.14% | **99.25%** (89% branch) |
| Fast-check property-based fuzz suite | β | β | β
|
| Hardening-specific test file (jails, CRLF, O_EXCL) | β | β | β
|
| **CI/CD hardening** | | | |
| Shell-injection-safe GitHub Actions workflows | β | β
| β
|
| Workflows use least-privilege `permissions:` scopes | β | β
| β
|
| All GitHub Actions pinned by full commit SHA | β | β | β
|
| **Operational** | | | |
| `CHANGELOG.md` (Keep-a-Changelog) | β | β | β
|
| `SECURITY.md` (vulnerability reporting) | β | β | β
|
`klodr/gmail-mcp` is the only one of the three with **(a)** source-path jails that make prompt-injection attachment exfiltration inert, **(b)** a modern supply chain (Scorecard, Socket, Sigstore), and **(c)** an in-repo review policy (`.coderabbit.yaml`) that every PR must pass before merge.
## π¦ Installation
```bash
npm install -g @klodr/gmail-mcp
```
Or directly via `npx`:
```bash
npx -y @klodr/gmail-mcp
```
Requires **Node.js 22+**.
## βοΈ Configuration
### 1οΈβ£ Google Cloud OAuth credentials
1. Open the [Google Cloud Console](https://console.cloud.google.com/).
2. Create a project and enable the **Gmail API**.
3. Under **APIs & Services β Credentials**, create an **OAuth 2.0 Client ID** (Desktop or Web). For Web, add `http://localhost:3000/oauth2callback` to the authorized redirect URIs.
4. Download the JSON, rename it to `gcp-oauth.keys.json`, place it at `~/.gmail-mcp/gcp-oauth.keys.json` (or override with `GMAIL_OAUTH_PATH=/abs/path/gcp-oauth.keys.json`).
### 2οΈβ£ Authenticate (once)
```bash
npx -y @klodr/gmail-mcp auth --scopes=gmail.readonly
```
Always pass `--scopes` with the minimum you actually need β the MCP filters the tool list at startup based on the granted scopes, so a read-only token doesn't expose write tools to the LLM. A browser opens for Google's consent flow; tokens are written to `~/.gmail-mcp/credentials.json` (mode `0o600`).
### 3οΈβ£ Register the server with your MCP client
```json
{
"mcpServers": {
"gmail": {
"command": "npx",
"args": ["-y", "@klodr/gmail-mcp"]
}
}
}
```
Client-specific config file:
- **Claude Code**: `~/.claude.json`
- **Claude Desktop**: `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) / `%APPDATA%\Claude\claude_desktop_config.json` (Windows)
- **Cursor**: `~/.cursor/mcp.json`
- **OpenClaw**: `~/.openclaw/openclaw.json`
See [llms-install.md](./llms-install.md) for an LLM-readable install guide.
## π OAuth scopes
| Scope shorthand | Full Gmail scope | What it grants |
|---|---|---|
| `gmail.readonly` | `β¦/auth/gmail.readonly` | Read messages, threads, labels (filter tools require `gmail.settings.basic`) |
| `gmail.modify` | `β¦/auth/gmail.modify` | Readonly + apply/remove labels, delete messages |
| `gmail.compose` | `β¦/auth/gmail.compose` | Create drafts |
| `gmail.send` | `β¦/auth/gmail.send` | Send messages |
| `gmail.labels` | `β¦/auth/gmail.labels` | Manage labels only |
| `gmail.settings.basic` | `β¦/auth/gmail.settings.basic` | Manage filters |
Recipes:
```bash
# Read-only browsing
npx @klodr/gmail-mcp auth --scopes=gmail.readonly
# Read + send (mailing-list bot)
npx @klodr/gmail-mcp auth --scopes=gmail.readonly,gmail.send
# Everything (default; explicit)
npx @klodr/gmail-mcp auth --scopes=gmail.modify,gmail.settings.basic
# Default + permanent delete (delete_email / batch_delete_emails)
# gmail.modify authorizes trash; mail.google.com is the only scope
# that authorizes purging from Trash. Both are listed because the
# tool gate does exact scope-name matching β a token holding only
# mail.google.com would not enable the gmail.modify-gated tools,
# even though Google's scope hierarchy would technically accept the
# same calls.
npx @klodr/gmail-mcp auth --scopes=gmail.modify,mail.google.com,gmail.settings.basic
```
## π‘οΈ Safeguards
### In-process middleware (autonomy enforcement)
The runtime constraints that an autonomous LLM client cannot violate are enforced *in the server*, not in the operator's attention span. Seven in-process modules sit on the tool entry path; the ones that are opt-in only activate when the matching env var is set.
**Always-on** (active for every call out of the box):
- **`sanitize.ts`** β strips/escapes input the LLM provides (CRLF injection, control characters, NUL bytes) before it reaches Gmail headers, filenames, or filesystem paths.
- **`sender-resolver.ts`** β resolves the `from` parameter against the account's authorized send-as aliases only. Spoofing the sender field is rejected before the Gmail API send call.
- **`rate-limit.ts`** β bounds Gmail API call volume per tool family (`send`, `delete`, `modify`, `drafts`, `labels`, `filters`) over rolling daily/monthly windows. The state is persisted to disk so it survives restarts. Disable with `GMAIL_MCP_RATE_LIMIT_DISABLE=true` for test runs only.
- **`gmail-errors.ts`** β normalizes Gmail API failure modes into typed errors so retries and surfacing to the LLM are deterministic.
- **`middleware.ts`** β composes the always-on chain and binds it to every tool entry point. New tools inherit the pipeline by construction.
**Opt-in** (off by default, set the env var to engage):
- **`recipient-pairing.ts`** β refuses to send mail to addresses that are not on the operator-controlled allow-list (`~/.gmail-mcp/paired.json`). No fresh outbound destinations from a hallucinated address. Engages when `GMAIL_MCP_RECIPIENT_PAIRING=true`; gates the `send_email` / `reply_*` / `forward_email` / `draft_email` / `update_draft` family only.
- **`audit-log.ts`** β append-only JSONL trail of every tool call (name, redacted args, outcome) with structural-key-preserving redaction so operators can replay decisions post-hoc. Engages when `GMAIL_MCP_AUDIT_LOG=/abs/path/audit.jsonl` is set.
Configure each module via the env vars below:
| Knob | Env var | Default | Notes |
|---|---|---|---|
| Attachment jail | `GMAIL_MCP_ATTACHMENT_DIR=/abs/path` | `~/GmailAttachments/` (auto-created mode `0o700`) | Every attachment path (`send_email`, `draft_email`, `update_draft`, `reply_all`, `reply_to_email`, `forward_email`) must live inside this directory after `realpath` canonicalization. Symlinks pointing outside are rejected. Blocks prompt-injected exfiltration of `~/.ssh/id_rsa`, `~/.gmail-mcp/credentials.json`, `~/.claude.json`, etc. |
| Download jail | `GMAIL_MCP_DOWNLOAD_DIR=/abs/path` | `~/GmailDownloads/` (auto-created mode `0o700`) | `download_email` and `download_attachment` write exclusively here. The leaf is opened with `O_NOFOLLOW`; post-`mkdir` the resolved path is re-verified against the jail root (TOCTOU defense). |
| OAuth keys path | `GMAIL_OAUTH_PATH=/abs/path/gcp-oauth.keys.json` | `~/.gmail-mcp/gcp-oauth.keys.json` | Google Desktop/Web OAuth client credentials. |
| Credentials path | `GMAIL_CREDENTIALS_PATH=/abs/path/credentials.json` | `~/.gmail-mcp/credentials.json` | Access/refresh tokens. File mode `0o600`. |
| Rate limit state dir | `GMAIL_MCP_STATE_DIR=/abs/path` | `~/.gmail-mcp/` | Where the rolling call-history for rate limiting is persisted (`ratelimit.json`, mode `0o600`). Same directory is reused for any future state files. |
| Rate limit overrides | `GMAIL_MCP_RATE_LIMIT_<bucket>=D/day,M/month` | see below | Override the per-bucket daily/monthly caps. Buckets: `send` (100/2000), `delete` (200/2000), `modify` (500/5000), `drafts` (300/3000), `labels` (50/500), `filters` (20/200). The `send` cap is sized at the upper end of a human professional workload (~40 emails/day with a 2.5Γ cushion); raise it via `GMAIL_MCP_RATE_LIMIT_send=400/day,6000/month` if you need the pre-v0.30.2 default. The bucket name is lowercase and matches the tool family. |
| Rate limit disable | `GMAIL_MCP_RATE_LIMIT_DISABLE=true` | unset (limiter active) | Kill-switch for the entire limiter. Use only for test suites or controlled batch operations. |
| Audit log | `GMAIL_MCP_AUDIT_LOG=/abs/path/audit.jsonl` | unset (no audit trail) | Opt-in append-only JSONL log of every tool call (name, redacted args, outcome). File mode `0o600`. Must be an absolute path; relative paths are rejected at startup. Redaction keeps structural keys and drops values under an allowlist. |
| Dry-run | `GMAIL_MCP_DRY_RUN=true` | unset (real calls) | When `"true"` (strict match), every write tool (`send_email`, `reply_all`, `reply_to_email`, `forward_email`, `draft_email`, `update_draft`, `delete_draft`, `send_draft`, `delete_email`, `modify_email`, `batch_modify_emails`, `batch_delete_emails`, `create_label`, `update_label`, `delete_label`, `get_or_create_label`, `create_filter`, `delete_filter`, `create_filter_from_template`, `modify_thread`) short-circuits before reaching Gmail and returns the redacted payload it would have sent. Useful for CI smoke tests, agent debugging, and human-in-the-loop approval flows. Read tools ignore the flag (nothing to preview). Matches `MERCURY_MCP_DRY_RUN` / `FAXDROP_MCP_DRY_RUN` on the sibling servers. |
## π οΈ Tools
The exact set depends on the OAuth scopes granted at `auth` time. Full catalog:
- **Messages** β `send_email`, `draft_email`, `read_email`, `search_emails`, `modify_email`, `delete_email`, `download_email`, `download_attachment`, `batch_modify_emails`, `batch_delete_emails`, `reply_all`, `reply_to_email`, `forward_email`
- **Drafts** β `list_drafts`, `get_draft`, `update_draft`, `delete_draft`, `send_draft` (full `users.drafts.*` surface; `draft_email` above creates the initial draft)
- **Threads** β `get_thread`, `list_inbox_threads`, `get_inbox_with_threads`, `modify_thread`
- **Labels** β `list_email_labels`, `create_label`, `update_label`, `delete_label`, `get_or_create_label`
- **Filters** β `list_filters`, `get_filter`, `create_filter`, `delete_filter`, `create_filter_from_template`
- **Recipient pairing** β `pair_recipient` (manage the `~/.gmail-mcp/paired.json` allowlist when `GMAIL_MCP_RECIPIENT_PAIRING=true`)
Every write tool is annotated with `destructiveHint` / `readOnlyHint` / `idempotentHint` per the MCP spec so policy-aware clients can gate on HITL confirmation.
### π `search_emails` query syntax
`search_emails` accepts Gmail's native search operators β `from:`, `to:`, `subject:`, `has:attachment`, `after:YYYY/MM/DD`, `before:YYYY/MM/DD`, `is:unread`, `label:<name>`, etc. They combine freely: `from:alice@example.com after:2026/01/01 has:attachment`. Full reference: [Google's Gmail search operators cheat sheet](https://support.google.com/mail/answer/7190).
## πΊοΈ Roadmap
See [ROADMAP.md](docs/ROADMAP.md).
## π Ecosystem
### Other MCP servers in the klodr family
- π§ [klodr/gmail-mcp](https://github.com/klodr/gmail-mcp) β Gmail (you are here)
- π [klodr/faxdrop-mcp](https://github.com/klodr/faxdrop-mcp) β Send real faxes via FaxDrop
- π¦ [klodr/mercury-invoicing-mcp](https://github.com/klodr/mercury-invoicing-mcp) β Mercury banking + invoicing
### Wider Gmail-MCP landscape
29 standalone repositories and 349 forks of the original GongRzhe server are reviewed in [docs/COMPETITORS.md](./docs/COMPETITORS.md) β which ideas we borrowed, which we chose not to, and where `klodr/gmail-mcp` sits on the maturity axes.
## π€ Contributing
See [CONTRIBUTING.md](.github/CONTRIBUTING.md) for the test / build / lint checklist and release process.
## π Security
See [SECURITY.md](.github/SECURITY.md) for the vulnerability-reporting process and the current security model, and [ASSURANCE_CASE.md](docs/ASSURANCE_CASE.md) for the threat model, trust boundaries, and CWE/OWASP mitigation table.
## π Project continuity
See [CONTINUITY.md](docs/CONTINUITY.md) for the handover plan if the maintainer becomes unavailable.
## π License
MIT β see [LICENSE](./LICENSE).
## π History
`klodr/gmail-mcp` is the maintenance fork of a two-step upstream chain:
- **[GongRzhe/Gmail-MCP-Server](https://github.com/GongRzhe/Gmail-MCP-Server)** β the original server. Unmaintained since August 2025 (7+ months with zero maintainer activity and 72+ unmerged pull requests).
- **[ArtyMcLabin/Gmail-MCP-Server](https://github.com/ArtyMcLabin/Gmail-MCP-Server)** β Arty MacKiewicz's active fork. Since divergence from Gong, it merged community contributions including: reply-all ([#3](https://github.com/ArtyMcLabin/Gmail-MCP-Server/pull/3) by @MaxGhenis), `list_filters` fix ([#4](https://github.com/ArtyMcLabin/Gmail-MCP-Server/pull/4) by @nicholas-anthony-ai), CI/CD shell-injection hardening ([#9](https://github.com/ArtyMcLabin/Gmail-MCP-Server/pull/9) by @JF10R), `download_email` ([#13](https://github.com/ArtyMcLabin/Gmail-MCP-Server/pull/13) by @icanhasjonas), tool annotations ([#14](https://github.com/ArtyMcLabin/Gmail-MCP-Server/pull/14) by @bryankthompson), CC/BCC fields in `read_email` ([#21](https://github.com/ArtyMcLabin/Gmail-MCP-Server/pull/21) by @panghy), and draft lifecycle tools `send_draft` / `delete_draft` / `update_draft` ([#30](https://github.com/ArtyMcLabin/Gmail-MCP-Server/pull/30) by @thisisambros). Reply-threading auto-resolution and the `--scopes` flag were folded in directly by the maintainer.
`klodr/gmail-mcp` carries all of the above forward and adds the supply-chain / path-jail / review-policy layer (see comparison table above). Credit to every PR author along the chain.
Maintenance
ActivitySlowing
ResponsivenessUnresponsive