399 lines
12 KiB
Markdown
399 lines
12 KiB
Markdown
# 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://<your-server>: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 <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).
|
||
|
||
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='<div style="font-size:12px;">Best regards,<br>Jane Doe<br>Associate</div>'
|
||
- 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": "<p>Hello,</p><p>...</p>",
|
||
"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.
|