mcp-email
by weiting-tw
README.md
# mcp-email — IMAP / SMTP 信箱 MCP server(本機 stdio + 遠端 HTTP / OAuth)
[](https://github.com/weiting-tw/mcp-email/actions/workflows/test.yml)
純 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](https://github.com/weiting-tw/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_sensitive`、`match_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](https://docs.astral.sh/uv/) 免手動建環境,首次執行自動抓套件:
```bash
uvx mcp-email # 直接跑(uv 會自動安裝到隔離環境)
# 或
pipx install mcp-email
pip install mcp-email # 裝進現有環境,提供 `mcp-email` 指令
```
MCP host 設定(Claude Desktop / Code / Cursor / Cline … 通用),帳密走 env:
```json
{
"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):
```bash
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` 或對應路徑),加上:
```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、
而設定檔裡完全不用寫密碼。設定改成:
```json
{
"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,適合內網共用)
```bash
# 已安裝(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 註冊(帳密只存在自己機器上):
```bash
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 網址:
```bash
# 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` 時輸入 `alice` → `alice@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 範例:
```ini
# /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 自動發佈):
```bash
# GHCR(免登入即可 pull)
docker pull ghcr.io/weiting-tw/mcp-email:latest
# Docker Hub(維護者設定 DOCKERHUB_* secret 後才會發佈)
docker pull weiting-tw/mcp-email:latest
```
或自己建:
```bash
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](https://myaccount.google.com/apppasswords) |
| 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` 報錯。要關掉驗證:
```jsonc
// 環境變數
"SMTP_VERIFY_CERT": "false",
"IMAP_VERIFY_CERT": "false"
```
或 runtime 用 `email_configure`:
```json
{ "smtp": { "verify_cert": false }, "imap": { "verify_cert": false } }
```
關掉後會喪失 MITM 防護,**只在自簽 / 測試環境用**,連 Gmail / Outlook 之類公網信箱絕對不要關。
**附件路徑白名單**:因為 `email_send` 的 `path` 附件受模型/host 控制,預設不設限時,
理論上可被誘導把任意本機檔案(如 `~/.ssh/id_rsa`)夾帶寄出。若要防護,設定允許目錄:
```jsonc
// 環境變數(os.pathsep 分隔,macOS/Linux 用 : )
"EMAIL_ATTACHMENT_DIRS": "/Users/me/Documents:/Users/me/Pictures"
```
或 runtime 用 `email_configure`:
```json
{ "attachment_allowed_dirs": ["/Users/me/Documents", "/Users/me/Pictures"] }
```
設定後,`path` 附件只能來自這些目錄底下(會解析 symlink 防繞過),名單外一律 `PermissionError`。
空白(預設)= 不限制。`content_base64` 附件不受此限(內容由呼叫端直接提供)。
**收件人網域白名單(防被誘導亂寄,開關,預設關)**:`email_send` / `email_reply` /
`email_forward` / `email_send_draft` 都是對外動作,若擔心模型被惡意信件內容誘導亂寄,
可限制只能寄到指定網域:
```jsonc
// 環境變數(逗號分隔)
"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
```json
{
"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
```json
{
"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
```json
{ "smtp": true, "imap": true }
```
回傳:
```json
{
"smtp": {"ok": true, "host": "smtp.gmail.com", "port": 587},
"imap": {"ok": true, "host": "imap.gmail.com", "port": 993}
}
```
### email_list_messages
```json
{
"folder": "INBOX",
"limit": 20,
"search": "UNSEEN"
}
```
IMAP search syntax 常用:
- `ALL`、`UNSEEN`、`SEEN`、`FLAGGED`、`UNFLAGGED`、`ANSWERED`、`UNANSWERED`、`DELETED`、`DRAFT`、`RECENT`
- `FROM "alice@example.com"`、`TO "..."`、`CC "..."`、`SUBJECT "週報"`
- `SINCE 1-Jan-2026`、`BEFORE 31-Dec-2026`、`ON 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
```json
{ "folder": "INBOX", "uid": "12345", "mark_read": false }
```
### email_mark / email_delete
```json
{"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 子行程啟動,
跑完整 `initialize` → `list_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 EXPUNGE**:`email_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](https://docs.pypi.org/trusted-publishers/),不需要 API token):
```bash
# 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。隨便用。
TDQS
A3.7/5.0
Scored across 11 tools
Disambiguation5/5
Each tool targets a distinct email operation (e.g., send, list, delete, apply rules) with no overlapping functionality. The descriptions clearly differentiate their purposes.
Naming Consistency5/5
All tools follow a consistent 'email_verb_noun' pattern (e.g., email_get_message, email_list_folders). The naming is predictable and easy to navigate.
Tool Count5/5
With 11 tools, the server covers core email workflows (send, receive, manage folders, rules) without being bloated or sparse. Each tool serves a clear purpose.
Completeness4/5
The tool set covers major email operations: send, list, get, delete, move, mark, manage folders, apply rules, configure, and test. Minor gaps like cross-folder search or attachment download are absent but not critical for typical usage.
Maintenance
ActivityStale
ResponsivenessNo issues