Skip to main content

Standalone MCP server that indexes IMAP email accounts for semantic and structured search

Project description

mcp-email-index

Indexes your IMAP email locally so you can search it fast — both semantically ("that email about the project deadline from last month") and structurally ("all emails from bob@example.com in March 2024"). Full email bodies and attachments are fetched live from IMAP on demand; only metadata and embeddings are stored locally.

Works with any IMAP mailbox (Gmail, self-hosted, etc.). Supports multiple accounts searched together or independently.

Requires Python 3.10+. Uses Qdrant (embedded) for vector search, SQLite with FTS5 for structured search, and sentence-transformers for embeddings.


Quick Start

1. Configure

Edit ~/.mcp-email-index/config.toml (auto-created on first run):

[account.work]
imap_host         = "mail.example-corp.com"
imap_port         = 993
imap_ssl          = true
imap_verify_ssl   = true                                 # set false for self-signed certs
username          = "alice@example-corp.com"
password          = "..."
max_connections   = 10                                   # max concurrent IMAP connections
# default_from    = "Alice <alice@example-corp.com>"     # backfill empty From headers
# exclude_folders = ["[Gmail]/All Mail", "[Gmail]/Spam"] # glob patterns for folders to skip
# smime_pfx       = "~/.mcp-email-index/certs/work.pfx" # S/MIME decryption certificate
# smime_password  = "..."                                # or env MCP_EMAIL_INDEX_WORK_SMIME_PASSWORD

[account.personal]
imap_host         = "imap.example-personal.com"
imap_port         = 993
imap_ssl          = true
imap_verify_ssl   = true
username          = "alice@example-personal.com"
password          = "..."

Add as many [account.<name>] sections as you need. The <name> is what you use in --account flags and MCP tool parameters.

Passwords can also be set via environment variables: MCP_EMAIL_INDEX_<ACCOUNT>_PASSWORD (uppercase account name) takes priority over the config file value. Same pattern for S/MIME: MCP_EMAIL_INDEX_<ACCOUNT>_SMIME_PASSWORD.

2. Add to your MCP client

{
  "mcpServers": {
    "email-index": {
      "command": "uvx",
      "args": ["mcp-email-index"]
    }
  }
}

Multiple clients can use the same config — the first one starts the daemon, the rest connect to it. When the last client disconnects, the daemon auto-shuts down after 60 seconds (MCP_EMAIL_INDEX_GRACE env var to override).

3. Index your email

email index

This starts the server (if not already running) and indexes all configured accounts. Run it once after setup, then periodically to pick up new mail.


CLI Commands

All commands use uvx --from mcp-email-index mcp-email-index-cli. Tip — add an alias to ~/.zshrc:

alias email='uvx --from mcp-email-index mcp-email-index-cli'

Then: email index, email stop, email scan, etc.

The server is started automatically when needed.

serve — Start the server

email serve

Daemonizes on Unix, writes PID to ~/.mcp-email-index/server.pid, logs to ~/.mcp-email-index/server.log, and waits until the server is ready before exiting.

The server auto-shuts down when idle. You don't normally need to call serve manually — the MCP shim starts it automatically.

stop — Shut down the server

email stop

If an indexing job is running, it is cancelled (progress saved) before shutdown.

index — Build or update the index

# Incremental — only new emails since last run
email index

# Specific accounts
email index --account work
email index --account work --account personal

# Specific folders (suffix-wildcard glob, only trailing * supported)
email index --account work --folder "Archive/2025/*"

# Full reindex — wipes existing data and rebuilds from scratch
email index --full
email index --full --account work --folder "Archive/2025/2025-03"
Flag What it does
--account NAME / -a Restrict to this account. Repeat for multiple. Omit for all.
--folder GLOB / -f Restrict to folders matching this glob. Only trailing * wildcard. Requires exactly one --account.
--full Delete existing index data first, then reindex. Required when the embedding model changes.

The CLI shows the live progress stream after starting the job. Press Ctrl-C to choose: run in background (default) or cancel the job.

During indexing the server automatically:

  • Detects and prunes emails deleted from IMAP (stale UIDs)
  • Removes folders that no longer exist on IMAP or are now in exclude_folders
  • Leaves other already-indexed folders untouched when using --folder to scope a run
  • Validates the embedding model fingerprint — blocks incremental indexing if the model changed (use --full to confirm)

index cancel — Stop the current indexing job

email index cancel

Finishes the current batch, saves progress, and stops. No data is lost.

index remove — Remove indexed data

email index remove -a work -f "Archive/2024/2024-03"  # specific folder
email index remove -a work                            # entire account
email index remove                                    # everything

Prompts for confirmation before deleting.

scan — Discover new and removed folders

email scan

Compares live IMAP folder lists against the index. Removes stale folders from the index and shows an ASCII tree of all folders with their index status, including counts of new (unindexed) messages.

status — Live indexing progress

email status

Streams a live progress display while an indexing job is running. Press Ctrl-C to exit — the job keeps running.

Command interactions

Situation What happens
index while a job is running Shows the running job's status. Cancel first to start a new one.
scan while a job is running Rejected — cancel the job first.
stop while a job is running Cancels the job (progress saved), then shuts down.
serve while server is already running Detects existing process, exits with a message.

MCP Tools

These tools are exposed to MCP clients. All search tools query across all configured accounts by default.

search_emails — Semantic search

Natural language search across email subjects and bodies.

Parameter Type Default Description
query string required Natural language query
accounts list[string] [] (all) Restrict to specific accounts
n_results int 10 Number of results
since string | null null ISO date lower bound
before string | null null ISO date upper bound
folder_glob string | null null Folder path glob (trailing * only)
from_addr string | null null Sender substring
to_addr string | null null To/CC recipient substring
has_attachment bool | null null Filter by attachment presence
overfetch int 5 Candidate multiplier for address filters

find_emails — Structured search

Field-specific lookups with FTS5 keyword search. Supports pagination.

Parameter Type Default Description
accounts list[string] [] (all) Restrict to specific accounts
from_addr string | null null Sender substring
to_addr string | null null To/CC recipient substring
subject string | null null Subject keyword search (FTS5)
body string | null null Body preview keyword search (FTS5)
since string | null null ISO date lower bound
before string | null null ISO date upper bound
folder_glob string | null null Folder path glob
has_attachment bool | null null Filter by attachment presence
limit int 50 Page size (capped at max_find_results)
offset int 0 Pagination offset

get_email — Fetch full email content

Fetches the complete email body live from IMAP.

Parameter Type Description
account string Account name
folder string IMAP folder path
uid string IMAP UID

Returns full plain-text body (HTML stripped if no text/plain part), headers, and attachment filenames. If the email has been moved to a different folder on the server, the index is updated automatically. If the email no longer exists on IMAP, it is pruned from the index.

get_metadata_by_message_id — Look up by Message-ID

Instant lookup from the local index — no IMAP connection needed.

Parameter Type Default Description
message_id string required RFC 2822 Message-ID
accounts list[string] [] (all) Restrict to specific accounts

get_thread — Reconstruct email thread

Returns all indexed emails in the same conversation, sorted by date ascending. Thread reconstruction uses In-Reply-To and References headers.

Parameter Type Description
account string Account name
message_id string Message-ID of any email in the thread

save_attachments — Download attachments

Fetches the email live from IMAP and saves attachments to a local directory. Handles filename collisions by appending a counter.

Parameter Type Default Description
account string required Account name
folder string required IMAP folder path
uid string required IMAP UID
dest_dir string "." Local path to save into (auto-created if needed)
names list[string] [] (all) Specific filenames to save; empty means all

list_folders — See what's indexed

Lists all indexed folders with email counts and last indexing timestamp.

Parameter Type Default Description
account string | null null Specific account, or null for all

Search Tips

  • search_emails for natural language: "that email about the project deadline".
  • find_emails for precise lookups: exact sender, date range, subject keywords.
  • folder_glob narrows scope: "Archive/2024/*" searches only 2024 folders.
  • from_addr and to_addr are substring matches — "bob" matches bob@example.com.
  • get_thread after finding an interesting email to see the full conversation.

S/MIME Support

If your IMAP server stores S/MIME encrypted or signed emails (common with Exchange), configure a PFX/P12 certificate for transparent decryption:

[account.work]
smime_pfx      = "~/.mcp-email-index/certs/work.pfx"
smime_password = "pfx-password"

The PFX can contain multiple key+cert pairs (e.g. current + historical). All pairs are tried during decryption. OpenSSL is used as a fallback for BER-encoded PKCS7 and legacy algorithms (RC2, DES).

Handled formats:

  • Standard S/MIME enveloped-data (encrypted)
  • Opaque-signed messages (signature unwrapped to extract body)
  • Detached signatures (multipart with text + pkcs7 signature)
  • Exchange "Microsoft Mail Internet Headers" wrapper
  • Exchange TNEF-wrapped S/MIME (application/ms-tnef with PKCS7 body)
  • Apple Mail S/MIME (missing Content-Type, only filename=smime.p7m)

Account Options

default_from — Backfill empty From headers

Old Exchange Sent folders sometimes store emails with an empty From header. Set default_from to backfill it during indexing and live fetches:

[account.work]
default_from = "Your Name <you@example.com>"

exclude_folders — Skip folders during indexing

Glob patterns for folders to exclude from indexing and scanning:

[account.work]
exclude_folders = ["[Gmail]/All Mail", "[Gmail]/Spam", "Drafts"]

Configuration Reference

~/.mcp-email-index/config.toml:

Section Key Default Description
[account.<name>] imap_host IMAP server hostname
imap_port 993 IMAP port
imap_ssl true Use SSL
imap_verify_ssl true Verify SSL certs (set false for self-signed)
username IMAP username
password IMAP password (or env MCP_EMAIL_INDEX_<NAME>_PASSWORD)
max_connections 10 Max concurrent IMAP connections
default_from Backfill empty From headers (e.g. "Name <email>")
exclude_folders List of glob patterns for folders to skip
smime_pfx Path to PFX/P12 certificate for S/MIME decryption
smime_password PFX password (or env MCP_EMAIL_INDEX_<NAME>_SMIME_PASSWORD)
[index] data_dir ~/.mcp-email-index/data Where index data is stored (Qdrant + SQLite)
embedding_model nomic-ai/nomic-embed-text-v1.5 Sentence-transformers model (changing requires --full reindex)
batch_size 50 Emails per IMAP fetch batch
max_find_results 500 Hard cap on find_emails results
preview_chars 500 Body preview length stored in index
embed_chars 8000 Body text fed to embedding model (must be ≥ preview_chars)
[server] host 127.0.0.1 Bind address
port 6644 HTTP port for MCP (SSE) and CLI
log_level INFO DEBUG | INFO | WARNING | ERROR
semaphore_timeout_s 30 Timeout (seconds) waiting for IMAP connection slot

Server files:

  • PID file: ~/.mcp-email-index/server.pid
  • Log file: ~/.mcp-email-index/server.log
  • Config: ~/.mcp-email-index/config.toml (auto-created with defaults on first run, chmod 600)

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_email_index-0.1.6.tar.gz (170.5 kB view details)

Uploaded Source

Built Distribution

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

mcp_email_index-0.1.6-py3-none-any.whl (65.2 kB view details)

Uploaded Python 3

File details

Details for the file mcp_email_index-0.1.6.tar.gz.

File metadata

  • Download URL: mcp_email_index-0.1.6.tar.gz
  • Upload date:
  • Size: 170.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for mcp_email_index-0.1.6.tar.gz
Algorithm Hash digest
SHA256 ea79753bb38edb50b3f1a3d3291a8d74033964bc2422e964a00551f2522a3992
MD5 f22e2e2420c257e17206dce813cb9649
BLAKE2b-256 31a21df1c1fd84f9f0f77565710b063d1d4fd73485b6c1314929bd58aaef5235

See more details on using hashes here.

File details

Details for the file mcp_email_index-0.1.6-py3-none-any.whl.

File metadata

File hashes

Hashes for mcp_email_index-0.1.6-py3-none-any.whl
Algorithm Hash digest
SHA256 9ba6081bc243f0c1739af0939f2330dbcfc792fa48a21605cfbaf1ff55170edd
MD5 6722d8a63ce1f145b4bbea6a08858f3d
BLAKE2b-256 de756debbbc98c8d2fa5435c9050e5854105516a331ddb0b8bc2661d3c2da6c9

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