Files
mcp-email/README.md
T

13 KiB
Raw Blame History

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:

  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" } ]'

  1. 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 <API_KEY>
    • 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).

Time and timezone:

  • TIMEZONE:
    • IANA timezone used by get_current_time (e.g. "America/New_York", "Europe/London", "UTC").
    • Default: "UTC".
    • Example: -e TIMEZONE="America/New_York"

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
  • get_current_time

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 models context.

Environment variables:

  • EXTERNAL_SUMMARY_MODEL_ENDPOINT:
  • 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.

get_current_time tool

Provides the LLM with a reliable reference time so it can interpret relative terms like "today", "yesterday", or "last week".

  • Name: get_current_time
  • Input: none
  • Output:
    • iso: current time in ISO 8601 format (e.g. 2025-06-28T10:15:00+01:00)
    • timezone: configured timezone (from TIMEZONE env variable)

Usage:

  • Call get_current_time when:

    • Filtering or sorting by relative dates
    • Checking if an email is recent or overdue
  • The tool uses the TIMEZONE environment variable.

  • Example Docker run:

    docker run -d
    -p 8000:8000
    -e TIMEZONE="America/New_York"
    mcp-email-server