Skip to main content
Glama
email-mcp

email-mcp

by swapnilsurdi

email-mcp

Local MCP server for multi-account IMAP/SMTP email (iCloud + Gmail via app-specific passwords). Never marks mail read. Cross-folder search, idempotent sends, TLS verified.

Two surfaces share the same core:

  • stdio MCP server (this README) — single user, runs per-client, config from an accounts file + Keychain/env.

  • HTTP service (pip install "email-mcp[http]", email-mcp-http) — long-lived and multi-tenant: MCP over streamable-HTTP behind scoped per-agent keys, a web dashboard, Matrix-bot onboarding, and owner-approved sends (👍 OTP) for recipients outside the allowlist. Guide: src/email_mcp/http/HELP.md — also served live by the service at GET /help (JSON summary at GET /info).

Status

  • Tested against iCloud only. The design is provider-generic (Gmail config is included as an example), but only iCloud has been exercised end-to-end so far. Gmail/others are untested — use at your own risk and please report back.

  • macOS only (uses the macOS Keychain via keyring).

Related MCP server: 163 Mail MCP

Requirements

  • An app-specific password for each mailbox, stored in your macOS Keychain (see Configure / Get an app-specific password). The password is read from the Keychain at runtime — it is never written to disk, logged, or returned by any tool.

  • You must grant Keychain permission. The first time the server reads the password, macOS shows a dialog: "… wants to use your confidential information stored in 'email-mcp' in your keychain." — click Allow (or Always Allow). This grants access to only that one email-mcp item, nothing else in your Keychain. Note the prompt is tied to the specific Python binary running the server, so it may re-ask if you switch interpreters.

Install

pip install email-mcp        # or: pipx install email-mcp

This provides an email-mcp command (and python -m email_mcp.server).

Configure

  1. Create your account registry at ~/.config/email-mcp/accounts.yml (see config/accounts.example.yml for the format). Override the location with the EMAIL_MCP_ACCOUNTS env var if you prefer.

  2. Store each account's app-specific password in the macOS Keychain:

    python -m email_mcp.setup_cli icloud-personal

    (For local development you can instead set EMAIL_MCP_PASSWORD in a .env.)

Register with Claude Code

claude mcp add email --scope user -- email-mcp

Tools

list_accounts, set_default_account, list_folders, get_emails (recency by default; search via query/filters; per-message attachments metadata), download_attachment, send_email (idempotent; optional attachments), mark_email, move_email.

Security policy

Optional security: section in the accounts file (all patterns are case-insensitive full-match regexes; plain values work as-is — see config/accounts.example.yml):

  • allowed_recipients — when set, every send_email recipient must match one or the send is BLOCKED (recipient_not_allowed) before SMTP and before the dedup ledger. Unset = allow all; an explicitly empty list blocks all sends. Each recipient is first canonicalized to its bare address(es) with the same parser SMTP uses, so a single entry bundling extra addresses behind a comma or display name ("ok@x.com, other@y.com") is checked address-by-address — and that same canonical set is what reaches SMTP and the dedup ledger.

  • Trash is protected by default — the reserved trash names of the major providers (Trash, Bin for Gmail UK, Deleted Messages for iCloud, Deleted Items for Outlook, subfolders included) are read-only: nothing can be moved into or out of them and messages there can't be flagged or expunged. Since moving-to-trash is the only delete this server has, the MCP cannot delete mail at all under the default policy. Opt out with protect_trash: false (applies on the next call). Note: a bare Deleted (some Exchange/O365/Dovecot setups) is not auto-matched — add it to protected_folders.

  • protected_folders — additional read-only folders (same semantics as trash).

  • readable_folders / blocked_folders — gate reading (get_emails, download_attachment, folder listing, and the folder sets searched for mutations). A blocked folder is also refused as a move_email destination. When readable_folders is set only matching folders are readable; blocked_folders always wins. Blocked folders are also omitted from list_folders. Policy violations return structured results (folder_protected, folder_blocked, folders_blocked, recipient_not_allowed) — never exceptions. Config is re-read per call, so edits apply without restarting the server.

Searching & speed

  • get_emails has two modes, reported by searched_window_only in the result:

    • No search terms → fast: the most-recent page*page_size per folder, served from an in-memory cache when warm (a repeat read is ~instant). searched_window_only=true here, so an empty result means "not in the recent window", not "doesn't exist". Pass fresh=true to force a live read.

    • Search (query, from_address, subject, since=YYYY-MM-DD, has_attachment, or raw filters.criteria) → runs server-side over the whole mailbox — query is a full-text IMAP search, so matches outside the recent window are found. searched_window_only=false.

  • body=false omits message bodies (cheap headers + attachment metadata) — ideal for finding a message before opening it. Each message also carries uid/uidvalidity for robust follow-up actions.

  • Background prefetch keeps the latest INBOX warm. Off by default; enable with EMAIL_MCP_PREFETCH_INTERVAL=120 (seconds; delta-fetches only new mail by UID, never marks read). Cache size knobs: EMAIL_MCP_CACHE_ENTRIES (256), EMAIL_MCP_CACHE_BYTES (32MiB), EMAIL_MCP_CACHE_BODY_MAX (64KiB), EMAIL_MCP_CACHE_RECENT_TTL (180s).

  • send_email dedup: by default a 2nd mail to the same recipients within 10 min is BLOCKED. allow_duplicate=true relaxes this to block only a true repeat (same recipients AND subject/body); idempotency_key gives caller-controlled dedup.

Roadmap: running this as one shared HTTP server so a whole fleet of agents share a single warm cache + prefetch — see docs/TODO-http-shared-server.md.

Attachments

  • Reading: get_emails reports an attachments list per message ({index, filename, mime_type, size, inline}) — metadata only, never the bytes. download_attachment(message_id, filename=… | index=…) writes one attachment to disk (read-only; never marks mail read) and returns the saved path. download_all=true saves every attachment; return_base64=true returns small files (≤256KB) inline instead of to disk; uid+folder (from get_emails) locate a message with no/duplicate Message-ID. Files land in EMAIL_MCP_DOWNLOAD_DIR (default ~/.local/state/email-mcp/attachments) unless you pass dest_dir. The email-supplied filename is sanitized and confined to that directory (path-traversal safe); existing files are not clobbered unless overwrite=true.

  • Sending: send_email's optional attachments is a list where each item is either {"path": "/local/file"} (read from disk) or {"content": "<base64>", "filename": "name.ext"}, with an optional "mime_type". Combined size is capped at 25 MB.

Get an app-specific password

  • iCloud: appleid.apple.com -> Sign-In & Security -> App-Specific Passwords.

  • Gmail: myaccount.google.com -> Security -> App passwords.

Develop

python3.12 -m venv .venv && .venv/bin/pip install -e ".[dev]"
.venv/bin/pytest

Available Tools

8 tools
download_attachmentA

Download a message's attachment(s) — read-only, never marks mail read. Select one by filename or index (from get_emails' attachments list); a lone attachment needs neither; download_all=true saves every attachment. Saved into the server download dir (override env EMAIL_MCP_DOWNLOAD_DIR) unless dest_dir is given; the email-supplied filename is sanitized and confined to that directory. return_base64=true returns the bytes inline (small files only, ≤256KB) instead of writing to disk. uid+folder (from get_emails) locate the message directly — use them when a message has no/duplicate Message-ID. Returns {saved_path|content_base64, filename, mime_type, size, ...}.

Inputs: message_id (required), filename, index, dest_dir, +7 more.

get_emailsA

Fetch or search emails. Never marks mail read. Defaults to INBOX + Junk.

Two modes, reported by searched_window_only in the result:

  • No search terms: fast — the most-recent page*page_size per folder, served from an in-memory cache when warm. searched_window_only=true here, so an empty result means 'not in the recent window', not 'doesn't exist'. fresh=true forces a live read past the cache.

  • Search (query, from_address, subject, since=YYYY-MM-DD, has_attachment, or raw filters.criteria): runs SERVER-SIDE over the whole mailbox — query is a full-text IMAP search, so matches outside the recent window are found. searched_window_only=false.

body=false omits message bodies (cheap headers + attachment metadata — ideal for finding a message before opening it). cached=true keeps the 60-min result-set cache for stable pagination. Each message includes uid/uidvalidity for robust follow-up actions.

Inputs: account, filters, query, folders, +11 more.

list_accountsA

List accounts and which is the default.

Inputs: no named parameters.

list_foldersB

List mail folders (folders the security policy blocks from reading are omitted).

Inputs: account.

mark_emailA

Mark a message read or unread. Refused (folder_protected) for messages in a protected folder — Trash/Bin/Deleted * by default, plus security.protected_folders.

Inputs: message_id (required), read (required), account, folders.

move_emailB

Move a message to another folder. Protected folders (Trash/Bin/Deleted * by default, plus security.protected_folders) are read-only: nothing can be moved into or out of them — i.e. with the default policy this server cannot delete mail.

Inputs: message_id (required), dest_folder (required), account, folders.

send_emailA

Send an email. Idempotent: by default a second mail to the same recipients within 10 minutes is BLOCKED, not resent. allow_duplicate=true relaxes this to block only a true repeat (same recipients AND subject/body), so distinct messages to the same person go through. idempotency_key overrides entirely: blocks iff that key was used in the window (caller-controlled dedup). attachments is an optional list; each item is either {"path": "/local/file"} (read from disk) or {"content": "", "filename": "name.ext"}, with an optional "mime_type". Combined size must stay under 25MB. Note: a {"path"} item reads any file this process can access and emails it — only attach paths you intend to send; never a path derived from untrusted/email-supplied content. If security.allowed_recipients is configured, every recipient must match it or the send is BLOCKED with reason=recipient_not_allowed.

Inputs: to (required), subject (required), body (required), account, +4 more.

set_default_accountB

Set the default account (used when account is omitted).

Inputs: name (required).

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.8/5.0

    Scored across 8 tools

    Disambiguation5/5

    Each tool targets a distinct operation (downloading attachments, fetching emails, listing accounts/folders, marking/moving/sending emails, setting default account). There is no overlap or ambiguity.

    Naming Consistency5/5

    All tool names follow a consistent verb_noun pattern with snake_case (e.g., download_attachment, get_emails, list_folders). No mixing of conventions.

    Tool Count5/5

    With 8 tools, the server covers core email operations without being too sparse or overly heavy. Each tool serves a clear purpose.

    Completeness4/5

    Covers essential email operations (send, receive, list, mark, move, attachments) but lacks a direct delete capability; the move_email tool cannot move to protected folders like Trash, limiting deletion.

    Maintenance

    ActivityInactive
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • F
      license
      Not graded
      quality
      D
      maintenance
      A minimal MCP server for reading and sending emails via IMAP/SMTP, supporting multiple accounts in a single instance with zero external dependencies.
      1
      -
    • A
      license
      A
      quality
      D
      maintenance
      Privacy-first local MCP server for personal @163.com mailboxes, connecting via IMAP/SMTP with read-only search and write operations guarded by confirmation tokens.
      12
      MIT
    • A
      license
      Not graded
      quality
      D
      maintenance
      A minimal MCP server for reading, searching, managing, and sending email over IMAP and SMTP.
      MIT