mail-shadow-mcp

mcp
Security Audit
Fail
Health Warn
  • License — License: Apache-2.0
  • Description — Repository has a description
  • Active repo — Last push 0 days ago
  • Low visibility — Only 7 GitHub stars
Code Fail
  • Hardcoded secret — Potential hardcoded credential in internal/config/testdata/valid.yaml
Permissions Pass
  • Permissions — No dangerous permissions requested

No AI report is available for this listing yet.

SUMMARY

MCP server for structured, read-only email access. Exposes a minimal, auditable API surface — AI agents can search and read emails, but cannot send, delete, or modify your mailbox.

README.md
mail-shadow-mcp logo

Structured email access for AI agents — with built-in safety guarantees.

Build
Latest Release
Go Version
Go Report Card
License

mail-shadow-mcp is a Model Context Protocol (MCP) server that creates a local shadow copy of your IMAP mailboxes in a SQLite database. AI agents query the local database through well-defined MCP tools instead of connecting directly to your IMAP server.

[Remote IMAP Server] ──IMAP──▶ [Sync Engine] ──▶ [SQLite FTS5] ◀──▶ [MCP Server] ◀──▶ [AI Agent]

Features

  • Local shadow database — emails are synced into a local SQLite database; the AI agent never connects to your IMAP server directly
  • Read-only — no STORE, APPEND, or EXPUNGE commands; your mailbox is never modified
  • Incremental sync — only fetches messages newer than the last known UID
  • Full-text search — SQLite FTS5 index for fast body-text queries
  • Multi-account — sync any number of IMAP accounts simultaneously
  • IMAP IDLE — optional real-time push notifications; new mail detected within seconds instead of waiting for the next poll interval
  • Read/replied statusis_read and is_replied flags synced from IMAP and exposed as filters
  • Thread viewget_thread walks full email conversations via Message-ID / In-Reply-To headers
  • Paginated results — all list tools return total_count so agents can page through large result sets
  • On-demand attachments — attachment files are fetched from IMAP only when explicitly requested
  • Safe soft-delete — when an agent calls delete_mail, the MCP server performs an IMAP MOVE to a configurable trash folder; nothing is ever permanently deleted

Safety: Nothing Is Ever Really Deleted

mail-shadow-mcp gives AI agents a delete_mail tool, but this tool never issues a destructive IMAP command. Here is exactly what happens when an agent calls it:

  1. The MCP server looks up the email in the local database.
  2. It opens a short-lived IMAP connection and executes IMAP MOVE — moving the message to the trash_folder you specify in config.yaml (e.g. "llm_delete").
  3. The local database entry is removed so the agent can no longer see the mail in future queries.
  4. The email remains intact on the IMAP server, safely tucked away in the trash folder. You can inspect, restore, or permanently delete it yourself at any time.

The AI agent has no direct IMAP access. It cannot expunge messages, empty folders, or issue any write command other than this controlled move. If trash_folder is not configured for an account, delete_mail returns an error and does nothing.


MCP Tools

Tool Description
list_accounts_and_folders List all synced accounts and their folders
get_recent_activity N most recent emails with optional filters (is_read, has_attachments, pagination)
get_email_content Full body text, read/replied status, and attachment list for a single email
search_emails FTS5 full-text search with subject/sender/date/folder/is_read/sent_by filters
get_thread All emails in the same thread as a given email, sorted by date ascending
download_attachments Fetch attachment files from IMAP and save them to disk
get_download_link Generate a temporary HTTP download URL for attachments (optional fallback)
delete_mail Soft-delete an email by moving it to a configured trash folder (IMAP MOVE, no permanent deletion)

Quick Start

1. Build

make build          # current platform
make release        # cross-compile for all platforms into dist/

Requires Go 1.25+.

2. Configure

Copy the example config and fill in your IMAP credentials:

cp config.example.yaml config.yaml
sync_interval_min: 15

database:
  path: "data/mail.db"

attachment_dir: "data/attachments"

# Optional: structured JSON logs for Loki / Elasticsearch pipelines.
# log_format: json   # json | text (default: text)
# log_level: info    # debug | info | warn | error (default: info)
# log_file: "/var/log/mail-shadow-mcp.log"  # omit to use stderr

# Optional: lightweight HTTP server for temporary attachment download links.
# Only enable this if you need the get_download_link MCP tool (e.g. as a
# fallback when the AI agent cannot transfer files via its normal channels).
fileserver_port: 8787               # TCP port to listen on (disabled if omitted)
fileserver_ttl_min: 15              # minutes before a link expires (default: 15)
fileserver_host: "localhost"        # hostname/IP shown in generated URLs

accounts:
  - id: "[email protected]"
    host: "imap.example.com"
    port: 993
    username: "[email protected]"
    password: "$WORK_IMAP_PASS"   # or plain text
    folders: ["INBOX", "Archive"] # only sync the mentioned folders
    # idle_folders: ["INBOX"]     # optional: IMAP IDLE for real-time push on these folders
    # trash_folder: "llm_delete"  # optional: target for delete_mail (soft-delete via IMAP MOVE)
    
  - id: "[email protected]"
    host: "imap.example.com"
    port: 993
    username: "[email protected]"
    password: "$PRIVATE_IMAP_PASS"   # or plain text

Credentials can be stored as plain text or as $ENV_VAR references that are resolved at runtime.

3. Run

# Start the MCP server (syncs on startup, then every sync_interval_min minutes)
./mail-shadow-mcp serve

# One-shot sync without starting the server
./mail-shadow-mcp sync

# Query from the command line (output is JSON)
./mail-shadow-mcp query --subject "invoice" --body "Q1"
./mail-shadow-mcp query -q "budget" --attachments only   # only emails with attachments
./mail-shadow-mcp query --recent --attachments none      # recent emails without attachments
./mail-shadow-mcp query --recent --limit 10 --offset 10  # page 2

# Download attachments for a specific email
./mail-shadow-mcp attachments --id "[email protected]:INBOX:42"

Docker

Pre-built multi-architecture images (linux/amd64, linux/arm64) are published to the GitHub Container Registry on every release:

docker pull ghcr.io/dryas/mail-shadow-mcp:latest

Required volumes

Volume Purpose
/config Must contain config.yaml. Mount read-only.
/data Persistent storage for the SQLite database and downloaded attachments. Survives container restarts.

Quick Docker run

Step 1 — prepare a config.yaml with transport: http and paths pointing to /data:

transport: http          # StreamableHTTP — required for Docker
http_addr: ":8080"

database:
  path: "/data/mail.db"

attachment_dir: "/data/attachments"

accounts:
  - id: "[email protected]"
    host: "imap.example.com"
    port: 993
    username: "[email protected]"
    password: "$WORK_IMAP_PASS"

Step 2 — run the container:

docker run -d \
  --name mail-shadow-mcp \
  -v ./config.yaml:/config/config.yaml:ro \
  -v mail-shadow-data:/data \
  -p 8080:8080 \
  ghcr.io/dryas/mail-shadow-mcp:latest

The MCP server is now reachable at http://localhost:8080/mcp.

docker-compose example

services:
  mail-shadow-mcp:
    image: ghcr.io/dryas/mail-shadow-mcp:latest
    restart: unless-stopped
    ports:
      - "8080:8080"
    volumes:
      - ./config.yaml:/config/config.yaml:ro   # your config — mount read-only
      - mail-shadow-data:/data                 # persistent DB + attachments
    environment:
      - WORK_IMAP_PASS=your_password_here      # referenced as $WORK_IMAP_PASS in config

volumes:
  mail-shadow-data:

Connecting an AI agent to the Docker container

Point your MCP client at http://localhost:8080/mcp using the StreamableHTTP transport:

{
  "mcpServers": {
    "mail_shadow": {
      "url": "http://localhost:8080/mcp"
    }
  }
}

Build the image yourself

docker build --build-arg VERSION=dev -t mail-shadow-mcp .

Integrating with an AI Agent

Configure your MCP client to launch the server via stdio.

Example for Claude Desktop (claude_desktop_config.json):

{
  "mcpServers": {
    "mail_shadow": {
      "command": "/path/to/mail-shadow-mcp",
      "args": ["serve", "--config", "/path/to/config.yaml"]
    }
  }
}

Example for Hermes Agent (config.yaml):

mcp_servers:
  mail_shadow:
    command: "/path/to/mail-shadow-mcp"
    args: ["serve", "--config", "/path/to/config.yaml"]

Example for OpenClaw (~/.openclaw/openclaw.json):

{
  "mcpServers": {
    "mail_shadow": {
      "command": "/path/to/mail-shadow-mcp",
      "args": ["serve", "--config", "/path/to/config.yaml"],
      "transport": "stdio"
    }
  }
}

TLS Modes

tls_mode Port Description
tls 993 Implicit TLS (default)
starttls 143 STARTTLS upgrade
none 143 No encryption — localhost/testing only

Set tls_skip_verify: true to accept self-signed certificates.


IMAP IDLE (Real-time Push)

By default, mail-shadow-mcp polls for new messages every sync_interval_min minutes. For folders where you want near-instant notifications, enable IMAP IDLE:

accounts:
  - id: "[email protected]"
    # ...
    idle_folders: ["INBOX"]   # IDLE runs on top of regular polling
  • One dedicated IMAP connection is opened per entry in idle_folders
  • When the server sends an EXISTS notification, a sync is triggered immediately
  • Regular polling continues unchanged for all other folders
  • Falls back to polling automatically if the server does not support IDLE
  • Exponential backoff (30 s → 5 min) on persistent connection errors

Attachment Download Server

The optional built-in HTTP server lets the AI agent generate temporary, single-use download links for attachment files — useful as a fallback when the agent cannot transfer files through its normal communication channels (e.g. WhatsApp, email).

Enable it in config.yaml:

fileserver_port: 8787        # TCP port to listen on
fileserver_ttl_min: 15       # minutes before a link expires (default: 15)
fileserver_host: "localhost" # hostname/IP shown in generated URLs

When enabled, the get_download_link MCP tool becomes available. It downloads the attachments, saves them to attachment_dir, and returns one temporary URL per file:

[
  {
    "file": "data/attachments/[email protected]/INBOX/42/invoice.pdf",
    "url": "http://localhost:8787/dl/3f8a1c.../invoice.pdf"
  }
]

Each link is single-use and expires after fileserver_ttl_min minutes. The tool description instructs the AI agent to prefer direct file transfer and only fall back to this mechanism when necessary.


License

Apache 2.0 — see LICENSE for details.
Copyright (c) 2026 Benjamin Kaiser.

Reviews (0)

No results found