A community-driven registry for Claude, Cursor, Windsurf, Cline & more. Not affiliated with Anthropic.
Are you the author? Sign in to claim
MCP server for structured email access via a local shadow database. Exposes a minimal, auditable API surface — AI agents
Stop giving your AI direct access to your email. Give it a safe, lightning-fast and feature rich "shadow" copy instead.
Imagine having a personal assistant who has read all your emails, knows exactly what's important, and can answer your questions in seconds — without ever risking your actual mailbox through a misbehaving or hallucinating AI.
With mail-shadow-mcp, you can ask your AI (like OpenClaw, Hermes Agent, Claude, Cursor or any other custom agent):
Most AI agents require direct access to your email (IMAP) to "see" your messages. This is risky — because once an agent has live IMAP credentials, it has the same permissions as you: it can read, move, delete, or even send emails. A single hallucination, a misunderstood instruction, or a bug could lead to an AI accidentally deleting your entire inbox, sending a reply you never intended, or exposing your credentials to a third party.
mail-shadow-mcp solves this by creating a "Safe Zone":
[Remote IMAP Server] ──IMAP──▶ [Sync Engine] ──▶ [SQLite FTS5] ◀──▶ [MCP Server] ◀──▶ [AI Agent]
The recommended way to run mail-shadow-mcp is via Docker. Running it in a container keeps the sync engine, credentials, and database fully isolated from your AI agent, which connects over HTTP. The agent never has access to the host filesystem or your IMAP password — only to the MCP API.
If you prefer to run it locally without Docker, you can download a pre-compiled binary from the Releases page and use stdio transport instead. However, this means the agent process and mail-shadow-mcp share the same user context, which reduces the isolation benefits described above.
Create local directories for config and data, then do a first run to generate the example config:
mkdir -p ./config ./data
docker run --rm \
-v ./config:/config \
-v ./data:/data \
ghcr.io/dryas/mail-shadow-mcp:latest
The container will detect that no config.yaml exists, copy an annotated example config into ./config/, print a message, and exit.
Open ./config/config.yaml (or wherever your /config volume is mounted) and fill in your IMAP details:
sync_interval_min: 15
database:
path: "/data/mail.db"
attachment_dir: "/data/attachments"
transport: http
http_addr: ":8080"
http_bearer_token: "your-secret-token" # generate one: openssl rand -hex 32
accounts:
- id: "work@example.com"
host: "imap.example.com"
port: 993
username: "work@example.com"
password: "$WORK_IMAP_PASS" # resolved from environment variable at startup
tls_mode: tls # tls (default) | starttls | none
tls_skip_verify: false # set true for self-signed certificates
folders: ["INBOX", "Archive"] # omit to sync all folders
idle_folders: ["INBOX"] # optional: instant new-mail push via IMAP IDLE
trash_folder: "llm_delete" # target folder for soft-deletes via delete_mail
Passwords as environment variables: Instead of writing your IMAP password directly into the config file, use a $VARIABLE_NAME placeholder — mail-shadow-mcp will resolve it from the container's environment at startup. In the example above, password: "$WORK_IMAP_PASS" means the container reads the value from the WORK_IMAP_PASS environment variable, which you pass via -e WORK_IMAP_PASS=your_password_here when starting it (see Step 1 or 3). This way no plaintext password ends up in the config file.
folders: The list of IMAP folders to sync. If omitted, all folders are synced. Restricting to the folders you actually care about (e.g. ["INBOX", "Archive"]) keeps the database smaller and initial sync faster.
idle_folders: Optional list of folders for which mail-shadow-mcp opens a persistent IMAP IDLE connection. When the mail server pushes an "EXISTS" notification, a sync is triggered immediately instead of waiting for the next poll interval so you will get informed about new mails in seconds. Keep this list short — each entry holds one open IMAP connection for the lifetime of the container. Best practice is to only add "INBOX" here, or leave it out entirely and rely on regular polling.
trash_folder: The IMAP folder that mail-shadow-mcp moves emails to when the AI agent calls the delete_mail tool. The folder must already exist on your mail server. If this is not set, delete_mail will return an error and do nothing — a safe default. Emails in the trash folder are automatically excluded from all MCP query results (search, recent activity, threads), so the agent can never see them again — regardless of whether the folder is included in the sync configuration. Note: if you ever change trash_folder to a different folder name, the old trash folder will no longer be excluded and its contents will become visible to the agent again on the next sync. Make sure to manually empty the old folder before switching.
http_bearer_token: A secret token that protects the MCP HTTP endpoint. Every request from the AI agent must include it as Authorization: Bearer <token>. Without this, anyone who can reach the port can talk to your MCP server — so always set this when running with http transport. Generate a secure random token with:
# Linux / macOS / WSL
openssl rand -hex 32
# Windows PowerShell
[System.Convert]::ToBase64String((1..32 | ForEach-Object { [byte](Get-Random -Max 256) }))
Copy the output into the config and pass the same value to your AI agent (see Step 4).
docker run -d \
--name mail-shadow-mcp \
--restart unless-stopped \
-v ./config:/config:ro \
-v ./data:/data \
-e WORK_IMAP_PASS=your_password_here \
-p 8080:8080 \
ghcr.io/dryas/mail-shadow-mcp:latest
The MCP server is now reachable at http://localhost:8080/mcp.
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
Since we're running with Docker, the MCP server is reachable via HTTP — and that works with Claude Desktop too, not just remote agents. Add the following to your agent's config:
Claude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"mail_shadow": {
"url": "http://localhost:8080/mcp",
"headers": {
"Authorization": "Bearer your-secret-token"
}
}
}
}
Replace localhost with your server's IP or hostname if mail-shadow-mcp runs on a different machine.
Hermes Agent (config.yaml):
mail-shadow:
url: http://localhost:8080/mcp
headers:
Authorization: Bearer your-secret-token
OpenClaw (~/.openclaw/openclaw.json):
{
"mcp": {
"servers": {
"mail_shadow": {
"url": "http://localhost:8080/mcp",
"transport": "streamable-http",
"headers": {
"Authorization": "Bearer your-secret-token"
}
}
}
}
}
Alternative: local stdio (pre-compiled binary, no Docker)
If you chose to run the binary directly instead of Docker, use the command form:
{
"mcpServers": {
"mail_shadow": {
"command": "/path/to/mail-shadow-mcp",
"args": ["serve", "--config", "/path/to/config.yaml"]
}
}
}
That's it — your AI can now search and read your emails safely.
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:
trash_folder you specify in config.yaml (e.g. "llm_delete").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.
| 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) |
SELECT, UID FETCH); no STORE, APPEND, or EXPUNGE is ever sent to your mail serveris_read and is_replied flags synced from IMAP and exposed as filtersget_thread walks full email conversations via Message-ID / In-Reply-To headerstotal_count so agents can page through large result setsstdio for local tools (Claude Desktop), http (StreamableHTTP) or sse for remote and Docker deploymentslinux/amd64, linux/arm64) published to ghcr.io on every releasesync_interval_min: 15
database:
path: "data/mail.db" # path to the local SQLite shadow database
attachment_dir: "data/attachments" # base directory for downloaded attachments
# Optional: log file and level. Omit log_file to write to stderr (default).
# log_file: "logs/mail-shadow-mcp.log" # append mode; directory is created automatically
# log_level: info # debug | info (default) | warn | error
# log_format: text # text (default) | json
# MCP transport mode.
# stdio (default) — stdin/stdout, used by Claude Desktop and most local tools.
# http — StreamableHTTP, recommended for Docker and remote deployments.
# sse — legacy SSE transport (prefer http unless your client requires SSE).
# transport: stdio
# http_addr: ":8080" # bind address for http/sse (default: :8080)
# http_base_url: "http://localhost:8080" # sse only: externally reachable base URL
# http_bearer_token: "" # recommended: set a secret token to protect the HTTP endpoint
# generate one with: openssl rand -hex 32
# Optional: lightweight HTTP server for temporary attachment download links.
# 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: "work@example.com"
host: "imap.example.com"
port: 993
username: "work@example.com"
password: "$WORK_IMAP_PASS" # or plain text; prefix with $ to read from env var
tls_mode: tls # tls (default, implicit TLS, port 993)
# starttls (STARTTLS upgrade, port 143)
# none (no encryption — localhost/testing only)
tls_skip_verify: false # set true for self-signed certificates
folders: ["INBOX", "Archive"] # optional: omit to sync all folders
# idle_folders: ["INBOX"] # optional: folders watched via IMAP IDLE for instant new-mail notification
# trash_folder: "llm_delete" # optional: target folder for delete_mail (soft-delete via IMAP MOVE)
services:
mail-shadow-mcp:
image: ghcr.io/dryas/mail-shadow-mcp:latest
restart: unless-stopped
ports:
- "8080:8080"
volumes:
- ./config/config.yaml:/config/config.yaml:ro # your config — mount read-only
- ./data:/data # persistent DB + attachments
environment:
- WORK_IMAP_PASS=your_password_here # referenced as $WORK_IMAP_PASS in config
Passwords as environment variables: In
config.yamlyou can reference passwords as$ENV_VAR— the server resolves them at startup. Pass them viaenvironment:in docker-compose or via-ewithdocker run. This way no plaintext password ends up in the config file.
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.
When using http or sse transport, always set http_bearer_token — otherwise the MCP endpoint is reachable by anyone who can access the port.
Generate a cryptographically secure token:
# Linux / macOS / WSL
openssl rand -hex 32
# PowerShell
[System.Convert]::ToBase64String((1..32 | ForEach-Object { [byte](Get-Random -Max 256) }))
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: "work@example.com"
# ...
idle_folders: ["INBOX"] # IDLE runs on top of regular polling
idle_foldersEXISTS notification, a sync is triggered immediatelyThe 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 channels.
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
make build # current platform
make release # cross-compile for all platforms into dist/
Requires Go 1.25+.
Beyond running as an MCP server, mail-shadow-mcp exposes a few CLI commands that are useful for manual operations, scripting, or debugging — without needing an AI agent at all.
Trigger a one-shot sync (fetches new emails into the local database and exits):
./mail-shadow-mcp sync
Query the local database (output is newline-delimited JSON, suitable for jq pipelines):
# Search by subject and body keyword
./mail-shadow-mcp query --subject "invoice" --body "Q1"
# Full-text search with attachment filter
./mail-shadow-mcp query -q "budget" --attachments only
# Most recent emails, paginated
./mail-shadow-mcp query --recent --limit 10 --offset 10
Download attachments for a specific email by its ID (format account:folder:uid):
./mail-shadow-mcp attachments --id "work@example.com:INBOX:42"
Apache 2.0 — see LICENSE for details.
Copyright (c) 2026 Benjamin Kaiser.
MCP server integration for DaVinci Resolve Studio
mcp-language-server gives MCP enabled clients access semantic tools like get definition, references, rename, and diagnos
Run Claude Code as an MCP server so any agent can delegate coding tasks to it
Browser automation using accessibility snapshots instead of screenshots