Skip to main content

Gmail Model Context Protocol server with recipient allowlist and local audit log.

Project description

mcp-gmail-manager

🌐 Leia em português (pt-BR) →

PyPI version Python versions License: MIT MCP Compatible

A comprehensive Gmail Model Context Protocol server: 33 tools covering send, reply, forward, drafts, search, read, attachments, trash, labels, filters, signature, and vacation responder.

Five defence-in-depth features that distinguish it from other Gmail MCPs:

  • Tamper-evident audit log (on by default) — every write/send/modify/download appends a JSON line to audit.jsonl, chained by SHA-256 so partial tampering is detectable. Metadata only, no body content. Optional read auditing via audit_log.include_reads.
  • Recipient allowlist (off by default) — when enabled, every outbound operation (send_email, create_draft, reply_to_message, forward_message, plus create_filter with a forward action) checks recipients against configured domains and explicit addresses.
  • Attachment path allowlist + denylist (denylist on by default) — the MCP refuses to attach or overwrite obvious credential files (~/.ssh/, ~/.aws/, id_rsa, .env, token.json, etc.), closing the "LLM exfils SSH key as attachment" attack. See Security notes for the full default deny set.
  • Prompt-injection tainted-content markers — read tools (get_message, get_thread, search_threads, list_drafts) wrap message bodies and snippets in <untrusted-email-content>...</untrusted-email-content> tags. Tool descriptions instruct the LLM to treat wrapped content as data, not instructions.
  • Least-privilege OAuth scopes — requests gmail.modify + gmail.settings.basic only. Does NOT request mail.google.com, so permanent delete is intentionally unavailable.

See examples/config.with-allowlist.json for an institutional-mode configuration.

Tools (33)

Group Tools
Send / reply / forward send_email, reply_to_message, forward_message
Drafts create_draft, list_drafts, send_draft, update_draft, delete_draft
Read / profile get_profile, get_message, search_threads, get_thread
Attachments get_message_attachments, download_attachment
Trash trash_message, untrash_message, trash_thread, untrash_thread
Labels list_labels, create_label, update_label, delete_label, label_message, unlabel_message, label_thread, unlabel_thread
Filters list_filters, create_filter, delete_filter
Signature get_signature, update_signature
Vacation responder get_vacation_responder, set_vacation_responder

OAuth scopes requested: gmail.modify + gmail.settings.basic. Does not request the https://mail.google.com/ superuser scope — permanent delete is intentionally unsupported.

Requirements

  • Python ≥ 3.10
  • A Google Cloud project with the Gmail API enabled and an OAuth 2.0 client (Desktop type)
  • A way to forward localhost:8765 to your auth host (typically ssh -L 8765:localhost:8765 user@host)

Install

Recommended — pipx (installs in an isolated venv, exposes the entry points on $PATH):

pipx install mcp-gmail-manager

If pipx is missing:

sudo apt install pipx        # Debian / Ubuntu / Mint
brew install pipx            # macOS
pipx ensurepath              # makes ~/.local/bin available, may need shell restart

Alternative — manual venv:

python3 -m venv ~/.venv-mcp-gmail
~/.venv-mcp-gmail/bin/pip install mcp-gmail-manager
# Use the absolute path when registering with Claude Code (see below)

Why not plain pip install system-wide? On modern Debian-based distros it fails with error: externally-managed-environment (PEP 668) — the OS protects its Python. The two methods above are the canonical workarounds.

From source:

git clone https://github.com/arthjhon/mcp-gmail-manager.git
cd mcp-gmail-manager
pipx install .

Google Cloud setup (one-time, ~10 minutes)

  1. Go to Google Cloud Console and create a new project (or pick an existing one).
  2. Enable the Gmail API (not "Gmail MCP API" — that's Google's own remote MCP; not what we want).
  3. Configure the OAuth consent screen:
    • User type: Internal if your account is part of a Google Workspace (no token expiration); otherwise External in Testing mode (up to 100 users, refresh tokens expire every 7 days — see Token expiration below).
    • Scopes: add https://www.googleapis.com/auth/gmail.modify and https://www.googleapis.com/auth/gmail.settings.basic. Do not add anything else.
    • Test users (External only): add the Gmail address you'll authenticate with.
  4. Create an OAuth Client ID:
    • Application type: Desktop app
    • Download the JSON. Save it as credentials.json.

First-time auth

Move your credentials into the config directory (default ~/.config/mcp-gmail-manager/):

mkdir -p ~/.config/mcp-gmail-manager
mv ~/Downloads/client_secret_*.json ~/.config/mcp-gmail-manager/credentials.json
chmod 600 ~/.config/mcp-gmail-manager/credentials.json

Run the auth flow:

mcp-gmail-manager-auth

This binds to localhost:8765 and prints a Google authorisation URL. Open it in a browser on a machine that can reach localhost:8765 on the auth host:

  • Local desktop: the printed URL works directly.
  • Remote / headless server: forward the port from your laptop first:
    ssh -L 8765:localhost:8765 user@your-server
    
    Then run mcp-gmail-manager-auth inside that SSH session.

Authorise with the Google account that will own outbound mail. On success the script writes token.json and exits.

Token expiration

The refresh token's lifetime depends on how the OAuth consent screen is configured:

Setup Refresh token lifetime Re-auth required?
Internal (Google Workspace) No expiration Never (until user revokes)
External + Testing 7 days (Google's policy for unverified apps) Yes — weekly
External + Production verified No expiration Never, but verification requires a paid Google security assessment

When the refresh token expires in Testing mode you'll see invalid_grant or Token has been expired or revoked errors. To recover:

rm ~/.config/mcp-gmail-manager/token.json
mcp-gmail-manager-auth

Takes ~30 seconds. Your credentials.json is not affected — only the user's token.

How to avoid the weekly rotation

  • Workspace users: configure the consent screen as Internal instead of External. Token never expires.
  • Personal Gmail users: weekly re-auth is the only practical option today. Production verification for gmail.modify requires a Google security assessment (paid, weeks of process) — not feasible for most personal projects.
  • Set a calendar reminder or a cron job to nudge you weekly. A future release may add proactive in-tool warnings before expiry.

Register with Claude Code

If installed via pipx:

claude mcp add gmail-manager -- mcp-gmail-manager

If installed in a manual venv that isn't on $PATH:

claude mcp add gmail-manager -- ~/.venv-mcp-gmail/bin/mcp-gmail-manager

Restart your Claude Code session so the new tool schemas load.

Multiple Gmail accounts

Each MCP instance handles one Gmail account. To use several accounts from the same Claude Code session (e.g. personal + work), register the MCP once per account with a distinct GMAIL_MCP_CONFIG_DIR. Each instance gets its own credentials, token, audit log, and config — fully isolated.

Setup per account

# 1. Dedicated config directory
mkdir -p ~/.config/mcp-gmail-<name> && chmod 700 ~/.config/mcp-gmail-<name>

# 2. Reuse the same OAuth client (one credentials.json works for any user in the same GCP project)
cp ~/.config/mcp-gmail-<other>/credentials.json ~/.config/mcp-gmail-<name>/
chmod 600 ~/.config/mcp-gmail-<name>/credentials.json

# 3. Authenticate with the target Gmail account
GMAIL_MCP_CONFIG_DIR=$HOME/.config/mcp-gmail-<name> mcp-gmail-manager-auth

# 4. Register with the env override
claude mcp add gmail-<name> -s user \
  -e GMAIL_MCP_CONFIG_DIR=$HOME/.config/mcp-gmail-<name> \
  -- mcp-gmail-manager

Restart Claude Code. The tools appear under separate namespaces:

  • mcp__gmail-personal__send_email → sends from the personal account
  • mcp__gmail-work__send_email → sends from the work account

You can prompt Claude with "send via gmail-work" and it picks the right namespace.

Per-account configuration

Each <config_dir>/config.json is independent. Useful patterns:

// ~/.config/mcp-gmail-work/config.json — strict allowlist
{
  "allowlist": {
    "enabled": true,
    "domains": ["yourcompany.com"]
  }
}
// ~/.config/mcp-gmail-personal/config.json — silence the audit log
{
  "audit_log": { "enabled": false }
}

Compromise of one account's token does not leak the other — each lives in a separate directory with chmod 600.

Configuration

~/.config/mcp-gmail-manager/config.json is optional — if it doesn't exist, sensible defaults apply (no allowlist, audit log enabled). Two ready-to-copy examples are provided:

Schema reference:

{
  "allowlist": {
    "enabled": false,
    "domains": [],
    "emails": []
  },
  "audit_log": {
    "enabled": true,
    "include_reads": false,
    "path": null
  },
  "attachments": {
    "max_total_bytes": 20971520,
    "allowed_paths": [],
    "deny_patterns": [],
    "use_default_deny_patterns": true
  }
}
Field Default Meaning
allowlist.enabled false When false, any recipient is accepted. Enable explicitly for institutional use.
allowlist.domains [] Lower-case domain suffixes accepted as recipients.
allowlist.emails [] Explicit lower-case email addresses accepted regardless of domain.
audit_log.enabled true Append every write/send/modify to JSONL.
audit_log.include_reads false Also log read operations (get_message, search_threads, get_thread, list_drafts, get_message_attachments). Useful for detecting silent reconnaissance.
audit_log.path null null<config_dir>/audit.jsonl. Override to centralise logs.
audit_log.max_size_bytes 10485760 (10 MB) Rotate to audit.jsonl.1..N when the current file exceeds this size. Chain resets across rotations; verify each file separately with the CLI.
audit_log.max_backups 5 Number of rotated backups to keep. Older ones are overwritten.
audit_log.verify_on_startup false Walk the chain on server start and emit a stderr warning if broken. Cheap for logs up to a few MB.
attachments.max_total_bytes 20971520 (20 MB) Combined size cap per send. Gmail's hard limit is 25 MB raw.
attachments.allowed_paths [] When populated, attach/download sources and destinations MUST be under one of these bases. Empty = only deny patterns apply.
attachments.deny_patterns [] Extra regex patterns to reject (matched against absolute path). Added on top of defaults.
attachments.use_default_deny_patterns true Include the built-in deny set (~/.ssh/, ~/.aws/, id_rsa, .env, token.json, credential files, browser stores).
rate_limit.enabled false When true, cap outbound sends per hour per running instance. In-memory sliding window — resets on server restart.
rate_limit.sends_per_hour 60 Applied to send_email, reply_to_message, forward_message, and send_draft combined.

Verifying the audit log

Run mcp-gmail-manager-verify-log to walk the hash chain and confirm no entry has been edited or removed:

mcp-gmail-manager-verify-log                       # verify the active log
mcp-gmail-manager-verify-log ~/.config/.../audit.jsonl.1   # verify a rotated backup

Exit codes: 0 OK, 1 log not found, 2 malformed JSON, 3 chain broken.

Environment variable overrides

Variable Default
GMAIL_MCP_CONFIG_DIR $XDG_CONFIG_HOME/mcp-gmail-manager or ~/.config/mcp-gmail-manager
GMAIL_MCP_CREDENTIALS <config_dir>/credentials.json
GMAIL_MCP_TOKEN <config_dir>/token.json

Security notes

  • Threat model: this MCP is primarily hardened against a misbehaving LLM — prompt injection, hallucinated recipients, tricked-into-exfil scenarios. It is NOT a substitute for host security; an attacker with local access can read token.json and call Gmail directly, bypassing every guardrail here.
  • Token storage: token.json is written chmod 600. Treat it as a password.
  • No remote attestation: this server runs entirely on your machine. No telemetry, no third-party calls beyond googleapis.com.
  • OAuth scope is deliberately narrow-ish: gmail.modify covers send/read/label/trash/drafts. It does NOT request mail.google.com, so permanent delete is unavailable — deletes go to Trash and can be undone with untrash_*. If you only need to send, fork and replace the scope with gmail.send.
  • Recipient guardrails cover forward-in-filters: create_filter with an action.forward targeting a non-allowlisted address is rejected. Filters were a common bypass of send-only allowlists.
  • Read tools mark content as untrusted: bodies and snippets are wrapped in <untrusted-email-content>...</untrusted-email-content>. Tool descriptions instruct downstream LLMs to treat wrapped content as data. Any occurrence of the closing tag inside a message body is escaped to prevent break-out.
  • Default attachment deny set (source and destination) covers common credential / secret paths: ~/.ssh/, ~/.aws/, ~/.gnupg/, ~/.docker/config.json, ~/.kube/, .env, .env.*, credentials.json, token.json, id_rsa/id_ed25519/id_ecdsa/id_dsa, .git-credentials, .netrc, wallet.dat, .bash_history, .zsh_history, ~/.mozilla/*/logins.json, authorized_keys, known_hosts. Extend via attachments.deny_patterns or narrow further via attachments.allowed_paths.
  • Audit log is tamper-evident, not tamper-proof: each entry includes prev_hash = sha256(previous line). Partial modification breaks the chain and is detectable. A full log rewrite by an attacker with file-write is NOT prevented — pair with off-host log shipping (roadmap) for stronger guarantees.
  • What is NOT mitigated: rate limiting (a compromised agent can burn Gmail quota fast), outbound content pattern scanning (no secret regex on bodies), signature/vacation phishing (allowlist doesn't cover their content), full log rewrite by a local attacker. See SECURITY.md for the current threat model and roadmap.

Limitations

  • OAuth "Production" verification for gmail.modify requires a paid Google security assessment. Stay in "Internal" (Workspace, no expiration) or "Testing" (≤ 100 users, 7-day refresh token rotation — see Token expiration) to avoid this.
  • HTML email body composition is not exposed as a first-class field. Send via create_draft + manual HTML editing in the Gmail UI, or extend _build_mime in a fork.
  • Push notifications (Pub/Sub watch/stop) not implemented — out of scope.

Contributing

Issues and PRs welcome. Keep changes scoped, document any new tool with a schema example, and add an audit-log entry for anything that mutates state.

License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mcp_gmail_manager-0.2.1.tar.gz (31.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mcp_gmail_manager-0.2.1-py3-none-any.whl (24.9 kB view details)

Uploaded Python 3

File details

Details for the file mcp_gmail_manager-0.2.1.tar.gz.

File metadata

  • Download URL: mcp_gmail_manager-0.2.1.tar.gz
  • Upload date:
  • Size: 31.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.2

File hashes

Hashes for mcp_gmail_manager-0.2.1.tar.gz
Algorithm Hash digest
SHA256 635930b46d3fd927992397acac487ae2d2b539929e2266d7ec21ade73c4b4657
MD5 5770fe3f38da055b44f4839da437a888
BLAKE2b-256 ff6c9bbc437094ae02169ec40b39a996277805f2b5d6000695421385df23fe12

See more details on using hashes here.

File details

Details for the file mcp_gmail_manager-0.2.1-py3-none-any.whl.

File metadata

File hashes

Hashes for mcp_gmail_manager-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 5133d4d10019e97abd27fa2221d9599493083faaadae493dec5bf962d37a5365
MD5 c4eb3649daa83da139edfc01bd8fb5ed
BLAKE2b-256 0cd60c1f642f00f19a7e3e421a485ca33069487a30635196a178d51e367a48a0

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page