# MCP Email Server A Dockerized MCP (Model Context Protocol) email assistant for law firms and legal teams. Integrates: - Multiple IMAP/SMTP email accounts - Shared/public folder support - New-message tracking and flagging - Conflict check search - Triage guidance using personnel/chain-of-command Designed to be consumed by LLMs via MCP (Streamable HTTP) in Open WebUI. ## Quick start (single account) 1. Build: docker build -t mcp-email-server /home/user/wall-o/mcp-email-server 2. Run (example): docker run -d \ -p 8000:8000 \ -e API_KEY="your_api_key_here" \ -e LOG_LEVEL="DEBUG" \ -e IMAP_HOST=imap.example.com \ -e IMAP_PORT=993 \ -e IMAP_USE_SSL=true \ -e IMAP_USERNAME="user@example.com" \ -e IMAP_PASSWORD="secret" \ -e SMTP_HOST=smtp.example.com \ -e SMTP_PORT=587 \ -e SMTP_USE_TLS=true \ -e SMTP_USE_SSL=false \ -e SMTP_USERNAME="user@example.com" \ -e SMTP_PASSWORD="secret" \ -e SMTP_FROM="user@example.com" \ -e DEFAULT_CC="supervisor@example.com" \ -e BUSINESS_DESCRIPTION="A law firm handling corporate and litigation matters." \ -e PERSONNEL_JSON='[{"name":"Jane Doe","role":"Managing Partner","email":"jane@example.com","escalates_to":null},{"name":"John Smith","role":"Associate","email":"john@example.com","escalates_to":"Jane Doe"}]' \ mcp-email-server 3. Health check: http://localhost:8000/health 4. MCP endpoint: - POST http://localhost:8000/mcp - Uses JSON-RPC 2.0 (methods: initialize, tools/list, tools/call) 5. Integrate with Open WebUI (MCP Streamable HTTP): - Admin Settings → External Tools → Add Server - Type: MCP (Streamable HTTP) - Server URL: http://:8000/mcp - Auth: Bearer - Key: your_api_key_here - Save, then enable tools in a chat. ## Multi-account configuration The server can manage multiple IMAP/SMTP accounts. Tools accept an optional "account" parameter; if omitted, the default account is used. You have two main options: 1) Inline JSON (ACCOUNTS_JSON) Set ACCOUNTS_JSON with a JSON array of accounts. Copy-paste template (single line, for docker run): -e ACCOUNTS_JSON='[{"id":"account1","name":"Primary Firm Inbox","imap_host":"imap.primary.com","imap_port":993,"imap_use_ssl":true,"imap_username":"user@primary.com","imap_password":"CHANGE_ME_1","smtp_host":"smtp.primary.com","smtp_port":587,"smtp_use_tls":true,"smtp_use_ssl":false,"smtp_username":"user@primary.com","smtp_password":"CHANGE_ME_1","smtp_from":"user@primary.com"},{"id":"account2","name":"Secondary Inbox","imap_host":"imap.secondary.com","imap_port":993,"imap_use_ssl":true,"imap_username":"user@secondary.com","imap_password":"CHANGE_ME_2","smtp_host":"smtp.secondary.com","smtp_port":587,"smtp_use_tls":true,"smtp_use_ssl":false,"smtp_username":"user@secondary.com","smtp_password":"CHANGE_ME_2","smtp_from":"user@secondary.com"}]' Human-readable equivalent (for reference): ACCOUNTS_JSON='[ { "id": "account1", "name": "Primary Firm Inbox", "imap_host": "imap.primary.com", "imap_port": 993, "imap_use_ssl": true, "imap_username": "user@primary.com", "imap_password": "CHANGE_ME_1", "smtp_host": "smtp.primary.com", "smtp_port": 587, "smtp_use_tls": true, "smtp_use_ssl": false, "smtp_username": "user@primary.com", "smtp_password": "CHANGE_ME_1", "smtp_from": "user@primary.com" }, { "id": "account2", "name": "Secondary Inbox", "imap_host": "imap.secondary.com", "imap_port": 993, "imap_use_ssl": true, "imap_username": "user@secondary.com", "imap_password": "CHANGE_ME_2", "smtp_host": "smtp.secondary.com", "smtp_port": 587, "smtp_use_tls": true, "smtp_use_ssl": false, "smtp_username": "user@secondary.com", "smtp_password": "CHANGE_ME_2", "smtp_from": "user@secondary.com" } ]' 2) JSON file (ACCOUNTS_CONFIG_PATH) Mount a JSON file and set ACCOUNTS_CONFIG_PATH. Example: docker run -d \ -p 8000:8000 \ -v /path/to/accounts.json:/etc/mcp-email/accounts.json:ro \ -e API_KEY="your_api_key_here" \ -e ACCOUNTS_CONFIG_PATH="/etc/mcp-email/accounts.json" \ mcp-email-server Use the same structure as ACCOUNTS_JSON. Default account: - Use DEFAULT_ACCOUNT="firm_main" to set the default. - If not set: - If only one account exists, it is default. - Otherwise, an account with id "default" is preferred, or the first one is used. Tool usage with multiple accounts: - Most tools accept an "account" field. Examples: - list_folders: { "account": "firm_main" } - search_messages: { "account": "secondary", "folder": "INBOX", "query": "conflict" } - send_email: { "account": "firm_main", "to": ["client@example.com"], "subject": "...", "body": "..." } - If "account" is omitted, the default account is used. A new tool is available: - list_accounts: - Lists all configured accounts and shows which is default. ## Environment variables See env.example for a full list. Core: - API_KEY: - If set, all /mcp requests must include: Authorization: Bearer - Leave empty to disable auth (not recommended in production). - LOG_LEVEL: - e.g., INFO, DEBUG, ERROR (default: INFO) Single-account IMAP (only if not using ACCOUNTS_JSON/ACCOUNTS_CONFIG_PATH): - IMAP_HOST - IMAP_PORT - IMAP_USE_SSL - IMAP_USERNAME - IMAP_PASSWORD Single-account SMTP: - SMTP_HOST - SMTP_PORT - SMTP_USE_TLS - SMTP_USE_SSL - SMTP_USERNAME - SMTP_PASSWORD - SMTP_FROM Optional email: - DEFAULT_CC: Comma-separated CC addresses. - DEFAULT_BCC: Comma-separated BCC addresses. - BUSINESS_DESCRIPTION: Context for the LLM (e.g., firm description). - PERSONNEL_JSON: JSON array of personnel/chain-of-command: - Example: [ { "name": "Jane Doe", "role": "Managing Partner", "email": "jane@example.com", "escalates_to": null }, { "name": "John Smith", "role": "Associate", "email": "john@example.com", "escalates_to": "Jane Doe" } ] - SCHEDULED_SEND_INTERVAL: Seconds between checks for scheduled emails (default: 10). HTML, signature, and LLM identity: - Emails (send_email, reply_to_message, forward_message, schedule_send) default to HTML (html=true) unless explicitly set to false. - EMAIL_HTML_SIGNATURE: Optional HTML signature appended to HTML emails. - Inline HTML example: -e EMAIL_HTML_SIGNATURE='
Best regards,
Jane Doe
Associate
' - Template file example: - Mount the file: -v /path/to/signature.html:/etc/mcp-email/signature.html:ro - Set env: -e EMAIL_HTML_SIGNATURE='/etc/mcp-email/signature.html' If the value is an existing file path, its contents are used as the signature. - Signature template variables: - The signature can include placeholders: - {{NAME}} - {{TITLE}} - {{PHONE}} - {{ADDRESS}} - {{EMAIL}} - These are filled in using: - Per-call LLM identity (signature_vars), or - Fallback environment variables: LLM_NAME, LLM_TITLE, LLM_PHONE, LLM_ADDRESS, LLM_EMAIL - Example env-based defaults: -e LLM_NAME='Adam P. Strömbergsson-DeNora' -e LLM_TITLE='Barrister & Solicitor' -e LLM_PHONE='1 613 699 2127' -e LLM_ADDRESS='7th floor - 1 Rideau St. Ottawa, ON. K1N 8S7' -e LLM_EMAIL='adam@apstrom.ca' - LLM identity via MCP (signature_vars): - When sending, replying, forwarding, or scheduling an email, the LLM should include its identity so the signature is populated correctly. - Example (send_email): { "name": "send_email", "arguments": { "to": ["client@example.com"], "subject": "Your subject", "body": "

Hello,

...

", "html": true, "signature_vars": { "NAME": "Adam P. Strömbergsson-DeNora", "TITLE": "Barrister & Solicitor", "PHONE": "1 613 699 2127", "ADDRESS": "7th floor - 1 Rideau St. Ottawa, ON. K1N 8S7", "EMAIL": "adam@apstrom.ca" } } } - The LLM must provide accurate identity details; do not leave these fields blank or generic. ## MCP tools All tools are exposed via MCP (Streamable HTTP) at POST /mcp. General: - list_accounts Email (listing, reading, and triage): - list_folders - search_messages - get_new_messages - sync_seen - get_unread_summary - list_unread_emails - get_email - summarize_email - get_thread Email (actions): - mark_as_read - flag_message - move_message - copy_message - apply_label - remove_label - list_labels Email (send/reply/forward): - send_email - reply_to_message - forward_message - save_draft - schedule_send Attachments and threads: - list_attachments - download_attachment - search_attachments - export_conversation Legal-specific: - conflict_check_search - get_triage_config Notes: - IMAP operations use UIDs for stability. - Conflict check search scans subject lines across specified folders for given terms. - New-message tracking is in-memory per container instance. - For production: - Always set API_KEY. - Use HTTPS and secure IMAP/SMTP settings. ## New tools for efficient email triage These tools are designed to help LLMs triage many emails without overloading the context window. - list_unread_emails - Purpose: List unread emails with minimal content (metadata + short snippet). - Use this first when scanning a large inbox. - Parameters: - account: optional (uses default if omitted) - folder: IMAP folder (default: INBOX) - max_results: max emails to return (default: 50) - include_body: if true, include full body (default: false) - since: optional SINCE date, e.g. "01-Jan-2024" - Returns: - For each email: uid, subject, from, to, date, flags, snippet, has_attachments - Plus total_unread and returned counts - get_email - Purpose: Fetch the full content of a single email by UID. - Use this after scanning with list_unread_emails or get_unread_summary. - Parameters: - folder: IMAP folder (default: INBOX) - uid: message UID - include_html: if true, include HTML body (default: false) - Returns: - subject, from, to, cc, date, body_plain, attachments - body_html only if include_html=true - summarize_email - Purpose: Get a concise summary of an email via an external model (if configured), instead of loading the full text into the main LLM context. - Use this when you need to understand an email quickly without reading its full content. - Parameters: - folder: IMAP folder (default: INBOX) - uid: message UID - max_length: max summary length in words (default: 150) - Behavior: - If external summary model is configured, it sends the email to that model and returns its summary. - If not configured or the call fails, it falls back to the first N words of the email. - Returns: - uid, subject, from, date, summary (including attachment note if present) - get_thread - Purpose: Get a concise view of the conversation thread for a given email. - Use this to understand context without loading all message bodies. - Parameters: - folder: IMAP folder (default: INBOX) - uid: message UID to use as the anchor - max_messages: max thread messages to return (default: 10) - Returns: - subject, participants, message_count - For each message: uid, from, date, subject, snippet (short) Suggested LLM workflow: 1) Call list_unread_emails(max_results=100) to scan metadata. 2) For interesting emails: - Use summarize_email(uid) for a quick summary, or - Use get_email(uid) if you need the full content. 3) For email threads: - Use get_thread(uid) to see the conversation outline. - Then selectively call get_email on key messages. ## External summary model configuration Optional: configure an external LLM to summarize emails via summarize_email. This keeps long emails out of the main model’s context. Environment variables: - EXTERNAL_SUMMARY_MODEL_ENDPOINT: - OpenAI-compatible /v1/chat/completions endpoint. - Example: http://localhost:8080/v1 - EXTERNAL_SUMMARY_MODEL_API_KEY: - API key for the external model. - EXTERNAL_SUMMARY_MODEL_NAME: - Model name (default: gpt-4o-mini). - EXTERNAL_SUMMARY_MAX_TOKENS: - Max tokens for each summary (default: 400). Example (docker run): -e EXTERNAL_SUMMARY_MODEL_ENDPOINT='http://localhost:8080/v1' \ -e EXTERNAL_SUMMARY_MODEL_API_KEY='your_summary_model_key' \ -e EXTERNAL_SUMMARY_MODEL_NAME='gpt-4o-mini' \ -e EXTERNAL_SUMMARY_MAX_TOKENS='400' If these are not set: - The summarize_email tool will still work, but will fall back to returning the first N words of the email instead of using an external model.