Skip to main content
Glama
weiting-tw

mcp-email

by weiting-tw

mcp-email — IMAP / SMTP 信箱 MCP server(本機 stdio + 遠端 HTTP / OAuth)

tests

純 Python 實作(mcp_email.py),把任意支援 IMAP / SMTP 的標準信箱(Gmail / Outlook / Yahoo / iCloud / Zoho / 公司 Exchange / 自架 mail server …)包成 MCP tools 給 Claude / Claude Code / Cowork / 任何 MCP host 用。

三種啟動模式(參考 m2k-calendar-tools 的部署架構):

模式

指令

適用場景

認證

stdio(預設)

mcp-email

本機單人(Claude Desktop / Code)

環境變數 / .env / email_configure(原有方式,完全不變)

HTTP

mcp-email --http

內網多人共用(Claude Code / Desktop 遠端連線)

每請求 Authorization: Basic,伺服器不存帳密

OAuth

mcp-email --oauth --issuer …

claude.ai Connectors(手機 app / 網頁版)

OAuth 2.1 + 無狀態加密 token,伺服器不存帳密

能做什麼(16 個 tools)

Tool

用途

email_configure

Runtime 動態切換 SMTP / IMAP 帳密、port、TLS、附件上限、收件人白名單

email_test_connection

一鍵測 SMTP + IMAP 是否能登入

email_send

寄信:HTML + 純文字、to/cc/bcc、檔案/Base64 附件、Reply-To、自訂 headers、retry

email_reply

回覆某封信:自動帶 Re: 主旨與 In-Reply-To/References 串接對話,可 reply_all、引用原文

email_forward

轉寄某封信:Fwd: 主旨、附上原信抬頭與內容,預設一併轉寄原附件

email_list_folders

IMAP 列出所有 mailbox 名稱(中文名稱自動解碼)

email_list_messages

列出 folder 內訊息 header(支援 IMAP search syntax;含 date_iso

email_get_message

抓單封信完整內容(body text/html、附件 metadata 含 index、date_iso、選擇是否標 SEEN)

email_get_attachment

下載單封信裡某個附件的實際內容(base64),以 filename 或 index 指定;有大小上限保護

email_mark

加/移除 IMAP flag(\Seen / \Flagged 等)

email_delete

標記 \Deleted 並 expunge(UID EXPUNGE)

email_create_folder

建立 folder(支援中文,自動 modified UTF-7;已存在不報錯)

email_move_messages

搬信:UID MOVE,server 不支援則 COPY + UID EXPUNGE fallback

email_apply_rules

規則整理:掃描後依條件 move/mark/delete,dry_run 預覽;單一目的地失敗不中斷整批;比對方式可調(match: substring/regex/exact、case_sensitivematch_mode: first/all)

email_save_draft

把一封信存成草稿(IMAP APPEND 到草稿匣,\Draft flag),不寄出,回草稿 uid

email_send_draft

把草稿匣裡的某封草稿寄出,成功後從草稿匣刪除

Prompts(在支援的 client 顯示成 slash command)

Prompt

用途

triage_inbox

分流信箱:列未讀 → 摘要 → 建議動作(參數:folder、limit)

weekly_cleanup

每週整理:引導 apply_rules 先 dry-run 再執行的安全流程(參數:folder)

draft_reply

讀取指定信件並草擬回覆、確認後才寄(參數:uid、folder、tone)

對應使用者需求:

  • 📤 寄信 HTML + 純文字 ✅(multipart/alternative,純文字 fallback 自動)

  • 👥 多收件人 to / cc / bcc ✅(bcc 不會 出現在 header)

  • 📎 附件 — 本機檔案路徑 + Base64 ✅(text/* 自動 charset=utf-8)

  • 🔧 Runtime 動態設定 ✅(email_configure tool,任一欄位可單獨更新)

  • 🔍 連線測試 ✅(SMTP NOOP + IMAP NOOP 各別測)

  • ⚡ 高效能 ✅(timeout 可調、SMTP send 失敗 exponential backoff 重試)

安裝

最快:uvx / pip(從 PyPI)

⚠️ 尚未發佈到 PyPI(首次發佈流程見文末〈發佈到 PyPI〉)。發佈後即可用 下列方式安裝;在那之前請用下一節「從原始碼」。

發佈後,用 uv 免手動建環境,首次執行自動抓套件:

uvx mcp-email          # 直接跑(uv 會自動安裝到隔離環境)
# 或
pipx install mcp-email
pip install mcp-email   # 裝進現有環境,提供 `mcp-email` 指令

MCP host 設定(Claude Desktop / Code / Cursor / Cline … 通用),帳密走 env:

{
  "mcpServers": {
    "email": {
      "command": "uvx",
      "args": ["mcp-email"],
      "env": {
        "IMAP_SERVER": "imap.gmail.com", "IMAP_PORT": "993",
        "SMTP_SERVER": "smtp.gmail.com", "SMTP_PORT": "465", "SMTP_USE_SSL": "true",
        "EMAIL_USER": "you@gmail.com", "EMAIL_PASS": "<app password>"
      }
    }
  }
}

Claude Code 使用者:repo 內附 .mcp.json,用 claude 開這個資料夾會自動提示啟用。

從原始碼(開發 / 自己改)

建議用獨立的 virtualenv,避免污染系統 Python(也不要把 .venv/ 提交到 git):

cd ~/Documents/workspace/mcp-email

# 建立並啟用虛擬環境(請用你實際的 Python,例如 Homebrew 的 python3)
python3 -m venv .venv
source .venv/bin/activate          # Windows: .venv\Scripts\activate

# 執行時依賴
pip install -r requirements.txt

# 開發 / 測試依賴(含 aiosmtpd 假 SMTP)
pip install -r requirements-dev.txt

# 跑端到端測試(in-process 假 SMTP + 假 IMAP)
python test_e2e.py
# 預期:=== 49/49 passed ===

# 遠端(--http/--oauth)模式測試
python -m pytest test_remote.py -q

⚠️ macOS 注意:別用 /usr/bin/python3(CommandLineTools 內建的 stub)建 venv, 它可能跳出 xcode-select 安裝提示。請改用 Homebrew 的 python3/opt/homebrew/bin/python3)或 pyenv。.venv/ 已列入 .gitignore, 不會、也不該進版控;clone 後請各自重建。

註冊到 Claude Desktop / Cowork / Code

打開 Claude 的 MCP 設定檔(通常在 ~/Library/Application Support/Claude/claude_desktop_config.json 或對應路徑),加上:

{
  "mcpServers": {
    "email": {
      "command": "python3",
      "args": ["/Users/weiting/Documents/workspace/mcp-email/mcp_email.py"],
      "env": {
        "SMTP_HOST": "smtp.gmail.com",
        "SMTP_PORT": "587",
        "SMTP_USER": "you@gmail.com",
        "SMTP_PASS": "<gmail app password>",
        "SMTP_USE_TLS": "true",
        "IMAP_HOST": "imap.gmail.com",
        "IMAP_PORT": "993",
        "IMAP_USER": "you@gmail.com",
        "IMAP_PASS": "<gmail app password>",
        "IMAP_USE_SSL": "true",
        "EMAIL_FROM": "Your Name <you@gmail.com>",
        "EMAIL_TIMEOUT_SEC": "30",
        "EMAIL_RETRY_MAX": "3"
      }
    }
  }
}

重啟 Claude 後就會看到 email_* 系列 tool 出現。

不設環境變數也可以,啟動後第一次用之前先呼叫 email_configure 設定帳密(runtime 設定不會落地,重啟會清空)。

用環境變數 + wrapper(推薦:設定檔零密碼)

如果你的帳密放在 ~/.secrets(會被 shell source 的機密檔),可以用附帶的 run_server.sh: 它會先 source ~/.secrets 再啟動 server,所以連 GUI 啟動的 Claude Desktop 也吃得到 env、 而設定檔裡完全不用寫密碼。設定改成:

{
  "mcpServers": {
    "email": { "command": "/絕對路徑/mcp-email/run_server.sh" }
  }
}

環境變數別名:server 同時接受兩種命名,~/.secrets 不必改名 —

標準名

也接受的別名

SMTP_HOST / IMAP_HOST

SMTP_SERVER / IMAP_SERVER

SMTP_USER / IMAP_USER

SMTP_USERNAME / IMAP_USERNAME

SMTP_PASS / IMAP_PASS

SMTP_PASSWORD / IMAP_PASSWORD

帳密只需設一組:SMTP 與 IMAP 通常是同一個信箱帳號,所以帳密不必重複設 —

  • 用共用的 EMAIL_USER / EMAIL_PASS(或 MAIL_*),SMTP / IMAP 兩邊都吃;

  • 或只設一邊(例如只有 IMAP_USERNAME / IMAP_PASSWORD),另一邊會自動沿用同一組帳密。

  • 只有 host / port 因協定不同需各自設(如 smtp.x.com:587 vs imap.x.com:993;自架常是同 host 不同 port)。

只設一個協定也可以:IMAP 與 SMTP 功能完全獨立 —— 只設 IMAP 就能用全部讀信工具 (list_folders / list_messages / get_message / mark / delete); 只設 SMTP 就能用 email_send。要兩種都用才需要兩邊的 host 都設。

遠端使用(本地以外)

以下兩種模式讓同一份 server 部署在內網主機 / NAS / 雲端 VM,多人共用。 共同原則:伺服器只設定主機資訊(IMAP_HOST / SMTP_HOST / port / TLS), 不設定任何人的帳密;每位使用者的帳密隨請求帶入、原樣 pass-through 給 IMAP/SMTP 登入,撤銷 = 使用者自己改密碼或撤掉應用程式專用密碼。

兩種模式都是明文帳密等級的傳輸,正式部署必須放在 HTTPS 反向代理後面 (nginx / Caddy / Synology 反向代理皆可)。放在反向代理後面時,記得設 EMAIL_ALLOWED_HOSTS=你的對外網域,否則 SDK 的 DNS-rebinding 防護會把 請求擋成 421 Invalid Host header

遠端模式與 stdio 的行為差異(多人共用的安全考量):

  • email_configure 停用並自 tool 列表隱藏(全域設定不容任一使用者改動)

  • path 附件預設停用(那是「伺服器」的檔案系統);請改用 content_base64, 或由管理員設 EMAIL_ATTACHMENT_DIRS 白名單開放

  • 預設 From = 該請求登入的帳號(不吃伺服器端 EMAIL_FROM

HTTP 模式(Basic pass-through,適合內網共用)

# 已安裝(pip / pipx)或直接 uvx,一行起服務:
IMAP_HOST=mail.example.com SMTP_HOST=mail.example.com \
uvx mcp-email --http --host 0.0.0.0 --port 8765

# 從原始碼跑也一樣:python3 mcp_email.py --http --host 0.0.0.0 --port 8765

主機設定也可以放在工作目錄的 .env 檔(KEY=VALUE 每行一組,環境變數優先), 不必每次打在指令前。

每位使用者在自己的 Claude Code 註冊(帳密只存在自己機器上):

claude mcp add --transport http email https://主機/mcp \
  --header "Authorization: Basic $(printf '%s' '帳號:密碼' | base64)"

伺服器不保存任何帳密、也絕不回退到環境變數憑證:沒帶 Authorization 標頭的工具呼叫一律被拒絕。

OAuth 模式(claude.ai Connectors:手機 app / 網頁版)

注意:OAuth 模式為單行程設計(進行中的授權交易存在記憶體), 不支援多副本負載平衡;token 本身無狀態,重啟不影響已發 token。

claude.ai 的連線來自 Anthropic 雲端,issuer 必須是公網可達的 HTTPS 網址:

# uvx(自動帶上 OAuth 模式的額外依賴 cryptography):
IMAP_HOST=mail.example.com SMTP_HOST=mail.example.com \
uvx --with cryptography mcp-email --oauth --issuer https://mail-mcp.example.com \
  --host 0.0.0.0 --port 8765

# 或既有環境:pip install "mcp-email[oauth]"(= pip install cryptography)

流程:標準 OAuth 2.1(動態註冊 + PKCE)。使用者第一次連接會被導到 /login 輸入信箱帳密(建議應用程式專用密碼),bridge 先以 IMAP 登入驗證, 通過後把憑證用伺服器金鑰 AES-GCM 加密封進 token —— 無狀態設計, 伺服器沒有憑證資料庫。access token 1 小時、refresh token 30 天自動輪替。

claude.ai 端:設定 → 連接器(Connectors)→ 新增自訂連接器 → 貼上 https://mail-mcp.example.com/mcp

相關環境變數(皆選配):

變數

用途

EMAIL_BRIDGE_KEY

token 加密金鑰(urlsafe base64 的 32 bytes);沒設就自動產生 .bridge-key 檔(chmod 600)。換金鑰=所有已發 token 立即失效

EMAIL_BRIDGE_KEY_FILE / EMAIL_OAUTH_CLIENTS

金鑰檔 / DCR client 註冊檔路徑(Docker 部署指向 /data

EMAIL_DOMAIN

設了之後,登入帳號沒打 @ 會自動補 @此網域(如 EMAIL_DOMAIN=example.com 時輸入 alicealice@example.com

EMAIL_AUTH_LOG

登入失敗日誌(固定格式,供 fail2ban 監看封鎖來源)

EMAIL_ALLOWED_HOSTS

反向代理後的對外網域白名單(逗號分隔)

⚠️ 金鑰是整個 OAuth 模式的單點要害:拿到 bridge 金鑰的人可以解開所有 已發 token 內的使用者憑證。金鑰檔 / EMAIL_BRIDGE_KEY 務必限縮讀取權限、 不進版控(repo 的 .gitignore 已排除 .bridge-key),有疑慮就換金鑰 (代價只是所有人重新授權一次)。

內建防護:登入頁同意畫面(顯示發起授權的應用程式名稱+授權碼送達的 網域,讓使用者辨識並攔截釣魚連結)、登入失敗節流(同 IP / 全域滑動視窗, 避免上游信箱服務封鎖 bridge 的 IP)、同授權交易密碼錯誤 5 次作廢、 登入頁安全標頭、auth log 自我輪替。搭配 fail2ban 的 filter 範例:

# /etc/fail2ban/filter.d/mcp-email.conf
[Definition]
failregex = mcp-email-login-fail ip=<HOST>
# jail:logpath 指向 EMAIL_AUTH_LOG 的路徑,maxretry/bantime 視需求

Docker 部署

現成多架構 image(linux/amd64 + linux/arm64,打 v* tag 由 GitHub Actions 自動發佈):

# GHCR(免登入即可 pull)
docker pull ghcr.io/weiting-tw/mcp-email:latest
# Docker Hub(維護者設定 DOCKERHUB_* secret 後才會發佈)
docker pull weiting-tw/mcp-email:latest

或自己建:

docker build -t mcp-email .

# HTTP 模式(把 mcp-email 換成上面的 image 名稱亦可)
docker run -d -p 8765:8765 \
  -e IMAP_HOST=mail.example.com -e SMTP_HOST=mail.example.com \
  mcp-email

# OAuth 模式(掛 /data volume 保留金鑰與 client 註冊)
docker run -d -p 8765:8765 -v mcp-email-data:/data \
  -e IMAP_HOST=mail.example.com -e SMTP_HOST=mail.example.com \
  mcp-email --oauth --issuer https://對外網址 --host 0.0.0.0 --port 8765

容器以非 root(uid 10001)執行。OAuth 模式為單行程設計(授權交易存在 記憶體),不支援多副本負載平衡。

HTTPS 反向代理範例(Caddy,兩行搞定自動憑證)

mail-mcp.example.com {
    reverse_proxy 127.0.0.1:8765
}

nginx 使用者:proxy_pass http://127.0.0.1:8765; 並保留 proxy_set_header Host $host;;SSE 緩衝不用另外關(server 回應已帶 X-Accel-Buffering: no)。設好代理後記得配 EMAIL_ALLOWED_HOSTS=mail-mcp.example.com

遠端部署上線前檢查清單

上線 --http / --oauth 前逐項確認,尤其打 ✅ 的三項是實務上最常害人踩雷的:

  • HTTPS 反向代理:帳密/token 是明文等級傳輸,一定要放在 TLS 後面,絕不裸跑對外。

  • EMAIL_ALLOWED_HOSTS:設成你的對外網域,否則 SDK 的 DNS-rebinding 防護會回 421 Invalid Host header

  • OAuth 金鑰要持久化--oauth 一定要掛 -v mcp-email-data:/data(或設固定的 EMAIL_BRIDGE_KEY)。沒掛 volume→容器每次重啟都重新產生金鑰→所有人 token 全失效、要重新授權,這是最常見的災情。

  • 金鑰檔權限.bridge-key(或 /data)只給執行帳號讀;拿到金鑰的人可解開所有已發 token 內的使用者憑證。已在 .gitignore 排除,切勿進版控。

  • 用應用程式專用密碼:宣導使用者在登入頁輸入 App Password,不要用信箱主密碼——外洩時只波及收發信、可單獨撤銷。

  • 同意畫面:登入頁會顯示「哪個應用程式要連你的信箱」+「授權碼送達的網域」;提醒使用者只在認得目的地時才繼續(防釣魚)。

  • 失敗節流 / fail2ban:設 EMAIL_AUTH_LOG 並掛 fail2ban(filter 範例見上),擋帳密暴力猜測。

  • path 附件:遠端模式預設停用;若確有需求才用 EMAIL_ATTACHMENT_DIRS 白名單開放,範圍越小越好。

  • token 生命週期已知:access 1 小時(自動續)、refresh 30 天(活躍使用者滾動不過期);使用者改密碼=所有舊 token 失效(需重新授權),這是刻意的撤銷機制。

常見信箱設定

服務

SMTP host

port

TLS

IMAP host

port

SSL

備註

Gmail

smtp.gmail.com

587

TLS

imap.gmail.com

993

SSL

App Password

Outlook / Microsoft 365

smtp.office365.com

587

TLS

outlook.office365.com

993

SSL

帳號需開 SMTP / IMAP 開關

Outlook.com / Hotmail

smtp-mail.outlook.com

587

TLS

outlook.office365.com

993

SSL

同上

Yahoo Mail

smtp.mail.yahoo.com

587

TLS

imap.mail.yahoo.com

993

SSL

需開 App Password

iCloud Mail

smtp.mail.me.com

587

TLS

imap.mail.me.com

993

SSL

需開 App-specific Password

Zoho Mail

smtp.zoho.com

587

TLS

imap.zoho.com

993

SSL

自架(Postfix + Dovecot)

視設定

587/465

TLS / SSL

視設定

143/993

STARTTLS / SSL

安全提醒:別把純密碼塞進 config 檔案明文。建議:

  • Gmail / Yahoo / iCloud / Outlook:用 App Password(兩階段驗證 → 應用程式密碼)

  • 公司 Exchange:問 IT 拿 SMTP/IMAP credential,或用 OAuth(這個版本暫未支援 OAuth flow)

  • 密碼存 macOS Keychain 並用 security find-generic-password -w 動態注入

TLS / SSL 憑證驗證:預設兩邊都 憑證(verify_cert: true)。自架信箱或公司內部用自簽憑證會在連線時 CERTIFICATE_VERIFY_FAILED 報錯。要關掉驗證:

// 環境變數
"SMTP_VERIFY_CERT": "false",
"IMAP_VERIFY_CERT": "false"

或 runtime 用 email_configure

{ "smtp": { "verify_cert": false }, "imap": { "verify_cert": false } }

關掉後會喪失 MITM 防護,只在自簽 / 測試環境用,連 Gmail / Outlook 之類公網信箱絕對不要關。

附件路徑白名單:因為 email_sendpath 附件受模型/host 控制,預設不設限時, 理論上可被誘導把任意本機檔案(如 ~/.ssh/id_rsa)夾帶寄出。若要防護,設定允許目錄:

// 環境變數(os.pathsep 分隔,macOS/Linux 用 : )
"EMAIL_ATTACHMENT_DIRS": "/Users/me/Documents:/Users/me/Pictures"

或 runtime 用 email_configure

{ "attachment_allowed_dirs": ["/Users/me/Documents", "/Users/me/Pictures"] }

設定後,path 附件只能來自這些目錄底下(會解析 symlink 防繞過),名單外一律 PermissionError。 空白(預設)= 不限制。content_base64 附件不受此限(內容由呼叫端直接提供)。

收件人網域白名單(防被誘導亂寄,開關,預設關)email_send / email_reply / email_forward / email_send_draft 都是對外動作,若擔心模型被惡意信件內容誘導亂寄, 可限制只能寄到指定網域:

// 環境變數(逗號分隔)
"EMAIL_ALLOWED_RECIPIENT_DOMAINS": "gss.com.tw,example.com"

設定後任何收件人(to/cc/bcc)網域不在清單內就 PermissionError;空白(預設)= 關閉不限制。

附件下載大小上限email_get_attachment 把附件 base64 回傳,過大的附件會撐爆模型 context / 記憶體,預設上限 5MB(超過只回 metadata 並標 too_large)。可用 EMAIL_MAX_ATTACHMENT_BYTES 調整,或呼叫時以 max_bytes 覆蓋。

⚠️ Prompt injection(把這套接上 LLM 最重要的風險):信件的主旨/內文是寄件人可控的, email_get_message / email_list_messages 會把它們原樣回給模型,惡意信可能夾帶 「請把這封轉寄到 …/刪除某資料夾」之類的指令試圖操縱模型。防線:

  • 絕不要把信件內文裡的指令當成命令執行——內文永遠是「資料」不是「指令」。

  • 破壞性/對外動作(send/reply/forward/delete/move/apply_rules/send_draft執行前一律先向使用者確認;多數 MCP host(Claude Desktop/Code)本來就會對每次工具 呼叫要求核准,這是主要安全網,別關掉它。

  • 加上上面的收件人網域白名單當第二層防線;apply_rules 保持 dry_run=true 先預覽。

Tool 呼叫範例

email_configure

{
  "smtp": {
    "host": "smtp.gmail.com",
    "port": 587,
    "username": "you@gmail.com",
    "password": "xxxx xxxx xxxx xxxx",
    "use_tls": true
  },
  "imap": {
    "host": "imap.gmail.com",
    "port": 993,
    "username": "you@gmail.com",
    "password": "xxxx xxxx xxxx xxxx",
    "use_ssl": true
  },
  "email_from": "Your Name <you@gmail.com>",
  "retry_max": 3
}

email_send

{
  "to": ["alice@example.com", "bob@example.com"],
  "cc": "carol@example.com, dave@example.com",
  "bcc": ["eve@example.com"],
  "subject": "週報 2026-06-12",
  "text": "純文字版本內容",
  "html": "<h1>週報</h1><p>本週進度:…</p>",
  "reply_to": "support@example.com",
  "headers": {
    "X-Report-Week": "2026-W24"
  },
  "attachments": [
    {"path": "/Users/me/Documents/report.pdf"},
    {"path": "/Users/me/Pictures/chart.png", "filename": "週報圖表.png"},
    {
      "content_base64": "SGVsbG8gV29ybGQh",
      "filename": "data.txt",
      "mime_type": "text/plain"
    }
  ]
}

email_test_connection

{ "smtp": true, "imap": true }

回傳:

{
  "smtp": {"ok": true, "host": "smtp.gmail.com", "port": 587},
  "imap": {"ok": true, "host": "imap.gmail.com", "port": 993}
}

email_list_messages

{
  "folder": "INBOX",
  "limit": 20,
  "search": "UNSEEN"
}

IMAP search syntax 常用:

  • ALLUNSEENSEENFLAGGEDUNFLAGGEDANSWEREDUNANSWEREDDELETEDDRAFTRECENT

  • FROM "alice@example.com"TO "..."CC "..."SUBJECT "週報"

  • SINCE 1-Jan-2026BEFORE 31-Dec-2026ON 12-Jun-2026

  • 邏輯:UNSEEN FROM "alice"OR FROM "alice" FROM "bob"NOT SEEN

中文關鍵字:支援。非 ASCII 關鍵字會自動改用 SEARCH CHARSET UTF-8 + literal (RFC 3501 標準做法);一次一個中文詞,且含 OR/NOT 時要放在最後一個條件。 若伺服器接受語法但比對不到(實測 Mail2000 不解 RFC2047 編碼的中文標頭),會自動 fallback 成「以剩餘 ASCII 條件縮小範圍 → 抓 header 在客戶端比對」(掃描上限 200 封, 回傳會附 note 說明)。email_apply_rules 的規則比對本來就在客戶端做,中文不受限。

email_get_message

{ "folder": "INBOX", "uid": "12345", "mark_read": false }

email_mark / email_delete

{"folder": "INBOX", "uids": ["12345", "12346"], "flag": "\\Seen", "add": true}
{"folder": "INBOX", "uids": ["12345"]}

驗證測試結果

=== 49/49 passed ===

另有 test_mcp_stdio.py:用真正的 MCP client 把 mcp_email.py 以 stdio 子行程啟動, 跑完整 initializelist_tools → 呼叫 tool 的 handshake,驗證能被任何 MCP host 載入。

以及 test_remote.py(40 tests):遠端模式的 Basic 標頭解析、每請求憑證覆蓋、 遠端限制(email_configure / path 附件停用、白名單 symlink 繞過)、 OAuth token 加解密與 provider 流程(含失敗節流、狀態清理、DCR 上限淘汰)、 in-process uvicorn 起真正 streamable-http server 的端對端測試——包括 「HTTP Basic 帳密穿透到 IMAP/SMTP 登入」與「DCR→PKCE→/login→token→Bearer 呼叫工具」的完整 OAuth 授權流程。

每次 push / PR 會由 GitHub Actions 在 Python 3.10–3.13 上自動跑兩支測試(見 .github/workflows/test.yml)。

涵蓋三類情境:

SMTP 寄信(in-process aiosmtpd 假 SMTP,真的把信送過去比對)

  • header(From / To / Cc / Reply-To / Subject)

  • bcc 不外漏(rcpt_tos 有 bcc 但 header 沒)

  • multipart:純文字 + HTML 兩個 part 都存在

  • 附件:檔案 + base64 兩種來源,filename 含中文(備註.txt)也對

  • retry:port 沒人 listen 時 SMTP 會 raise ConnectionRefusedError

  • 密碼不會出現在 email_configure 回傳

錯誤路徑 / 安全

  • 沒寄件人、沒收件人、附件不存在、壞 base64 各自 raise 對應 error

  • html-only 自動補純文字 fallback

  • 附件白名單:名單外檔案被 PermissionError 擋下、名單內正常附加

IMAP 讀信(FakeIMAP 取代真連線)

  • list_folders / list_messages(UID 正確解析)/ get_message(text+html+附件 metadata)

  • mark(flag 真的寫入)/ delete(UID EXPUNGE,及不支援 UIDPLUS 時 fallback)/ test_connection(IMAP NOOP)

  • parser 健壯性:FLAGS/UID 出現在 BODY literal「之後」的回應排序也能正確解析

設計選擇 / 已知限制

  • 無 OAuth flow:目前只支援帳密。Gmail / 365 OAuth2 比較複雜,要看後續是否真實需要再加。

  • 不維護長連線:每次 IMAP 呼叫都重開連線。簡單可靠,效能上對「偶爾讀信」場景夠用;如果你要做「常駐 polling」可能要改長連線。

  • IMAP search 直接傳給伺服器:自由度高但要懂 RFC 3501 syntax;含空格的字串記得加雙引號(如 FROM "alice@x.com")。

  • 刪信用 UID EXPUNGEemail_delete 在 server 支援 UIDPLUS(RFC 4315)時只清掉你指定的 uid,不會誤刪資料夾內其他已標 \Deleted 的信;不支援時才 fallback 到一般 EXPUNGE(回傳的 method 欄位會標明用了哪種)。

  • 附件大小:受 SMTP 伺服器限制(Gmail 25MB、Outlook 20MB、Exchange 視設定)。本工具不設上限,超過會直接 SMTP error。

  • 遠端模式的多帳號--http / --oauth 天生多人(每請求帶各自憑證、彼此隔離);stdio 模式仍是一個 instance 一個帳號,多帳號要靠 host 多開幾個 instance(每個給不同 env)。

  • OAuth 模式單行程:授權交易存在記憶體,不支援多副本負載平衡;token 本身無狀態,重啟不影響已發 token(金鑰不變的前提下)。

發佈到 PyPI(維護者用)

v* tag 會由 GitHub Actions 自動 build + 發佈(.github/workflows/publish.yml, 走 PyPI Trusted Publishing,不需要 API token):

# 1. bump pyproject.toml 的 version 並 commit
# 2. 上 tag 並推出去,Actions 就會發佈
git tag v0.3.0 && git push origin v0.3.0

一次性設定:PyPI 的 mcp-email 專案 → Settings → Publishing → 新增 GitHub publisher (owner weiting-tw、repository mcp-email、workflow publish.yml、environment pypi), 並在 GitHub repo 的 Settings → Environments 建立名為 pypi 的 environment。

License

MIT。隨便用。