scaratec/imap-mcp
GitHub: scaratec/imap-mcp
一个安全优先的 MCP 服务器,在严格可审计的细粒度策略下为 LLM agent 调解 IMAP 邮箱的读写访问权限。
Stars: 0 | Forks: 0
# imap-mcp
一个以安全为核心的 [Model Context Protocol][mcp] 服务器,它在严格且可审计的策略下,调解 LLM
对 IMAP 邮箱的访问。专为需要读取、分类、移动和归档邮件的
agent 而设计——但必须防止它们读取不该读取的内容、移动不该移动的内容,
或者在操作后不留任何痕迹。
## 状态
**V1 — 241 个 BDD 场景通过,0 个跳过。** 该服务器
可在 stdio 和 HTTP/SSE 传输协议上运行。支持 Gmail(通过 RFC 6154 进行本地化文件夹名称解析)和
标准 IMAP 提供商。
支持通过 Jaeger 进行可选的 OpenTelemetry 链路追踪。
## 快速开始
```
# 通过 pipx 安装(推荐 — 隔离环境,全局 CLI)
pipx install "sc-imap-mcp[tracing]"
# 或通过 pip
pip install sc-imap-mcp[tracing]
# 不带 tracing
pipx install sc-imap-mcp
# 从源码
cd server && pip install -e ".[tracing]"
# 升级
pipx upgrade sc-imap-mcp
```
### 配置
```
mkdir -p ~/.config/imap-mcp/{policies,secrets/accounts/my-account}
# 创建 accounts.yaml, callers.yaml, policies/*.yaml
# (参见下方的 Configuration 部分)
# 存储你的 IMAP 密码或 OAuth refresh token
echo -n 'your-password' > ~/.config/imap-mcp/secrets/accounts/my-account/password
```
### 在 stdio 上运行(适用于 Claude Desktop, Claude Code, Cline)
```
IMAP_MCP_CONFIG_DIR=~/.config/imap-mcp \
IMAP_MCP_CALLER_ID=my-agent \
imap-mcp --transport stdio
```
### 注册到 Claude Code
```
claude mcp add --scope user imap-mcp \
--env IMAP_MCP_CONFIG_DIR=$HOME/.config/imap-mcp \
--env IMAP_MCP_CALLER_ID=my-agent \
-- imap-mcp --transport stdio
```
### 在 HTTP 上运行(适用于多 agent 设置)
```
IMAP_MCP_CONFIG_DIR=~/.config/imap-mcp \
imap-mcp --transport http --host 127.0.0.1 --port 8080
```
## 架构
```
┌──────────────────────────┐
│ LLM agent (MCP client) │
└────────────┬─────────────┘
│ MCP (stdio or HTTP/SSE)
┌────────────▼─────────────────────────────────────────┐
│ imap-mcp │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Policy Decision Point — default-deny │ │
│ │ Redaction / Transparency Layer │ │
│ │ Transaction Manager (WAL + saga) │ │
│ │ IMAP Core · Batch Fetch · OAuth2 adapters │ │
│ │ Pluggable Secret Store │ │
│ │ Append-only Audit Log with hash chain │ │
│ │ OpenTelemetry Tracing (optional) │ │
│ └─────────────────────────────────────────────────┘ │
└─────────┬───────────────────┬────────────────────────┘
│ IMAP/IMAPS │ IMAP/IMAPS
┌─────────▼────┐ ┌──────────▼────┐
│ Account A │ │ Account B │
│ (e.g. Gmail) │ │ (e.g. Dovecot)│
└──────────────┘ └───────────────┘
```
## 配置
```
config/
├── accounts.yaml # IMAP accounts, secret store, audit, WAL
├── callers.yaml # MCP callers and their auth
└── policies/
├── invoice-agent.yaml
└── overview-bot.yaml
```
### accounts.yaml
定义服务器可以连接的每个 IMAP 账户,以及
secret store 后端、审计日志和 WAL 配置。
```
accounts:
- id: company-mail
provider: imap-standard # or "google" for Gmail
host: imap.example.com
port: 993
auth:
type: password # or "xoauth2"
secret_ref: secret://accounts/company-mail/password
token_cache: memory_only # or "persist_all" (OAuth only)
- id: gmail-work
provider: google
host: imap.gmail.com
port: 993
auth:
type: xoauth2
secret_ref: secret://accounts/gmail-work/refresh_token
oauth_scope: https://mail.google.com/
token_cache: persist_all
- id: accounting@company.com
host: imap.company.com
port: 993
user: mail2223 # explicit IMAP login (when != id)
auth:
type: password
secret_ref: secret://accounts/accounting@company.com/password
secret_store:
backend: file_dir # or "env_var" or "gpg_file"
path: /home/user/.config/imap-mcp/secrets
audit:
directory: /home/user/.config/imap-mcp/audit
hot_days: 90 # days before gzip compression
warm_days: 275 # additional days as .gz
delete_after_days: 365 # total age before deletion
wal:
path: /home/user/.config/imap-mcp/wal.db
```
#### 账户字段
| 字段 | 必需 | 默认值 | 描述 |
|---|---|---|---|
| `id` | 是 | — | 策略中引用的唯一标识符。对于使用 OAuth 的 Gmail,请使用电子邮件地址(例如 `user@company.com`) |
| `provider` | 否 | `imap-standard` | `imap-standard`、`google` 或 `google-mock` |
| `host` | 否 | `127.0.0.1` | IMAP 服务器主机名 |
| `port` | 否 | `143` | IMAP 端口。IMAPS 使用 `993`(Gmail 和大多数提供商) |
| `user` | 否 | 从 `id` 派生 | 显式的 IMAP 登录用户名。当 IMAP 用户与账户 ID 不同时使用(例如 `accounting@example.com` 使用 `mail2223`) |
| `auth.type` | 是 | — | `password` 或 `xoauth2` |
| `auth.secret_ref` | 是 | — | 对 secret store 的引用(例如 `secret://accounts/x/password`) |
| `auth.oauth_scope` | 否 | — | xoauth2 账户的 OAuth2 scope |
| `token_cache` | 否 | `memory_only` | `memory_only`(access token 仅存储在 RAM 中)或 `persist_all`(也会持久化存储) |
#### Secret store 后端
| 后端 | 描述 | 配置字段 |
|---|---|---|
| `file_dir` | 明文文件;机密性由周边系统保障(如 git-crypt, SOPS, LUKS) | `path` |
| `env_var` | 从环境变量只读取。 | — |
| `gpg_file` | 使用操作者的密钥进行逐文件 GPG 解密 | `path`, `recipient`, `gnupghome` |
Secret 引用使用 `secret://path/segments` 格式。对于 `file_dir`,
这会解析为 `{path}/path/segments`。
### callers.yaml
定义可以连接到服务器的每个 MCP caller(agent)。
```
callers:
- id: my-agent
policy: my-policy
auth:
type: stdio_trusted
- id: invoice-bot
policy: invoice-policy
auth:
type: shared_token
token_secret_ref: secret://callers/invoice-bot/token
```
#### Caller 认证类型
| 类型 | 传输协议 | 机制 |
|---|---|---|
| `stdio_trusted` | 仅限 stdio | Caller ID 由编排器通过 `IMAP_MCP_CALLER_ID` 环境变量设置。 |
| `shared_token` | stdio + HTTP | 使用常数时间比较算法验证的 Bearer token。在 HTTP 上:`Authorization: Bearer ` 请求头。 |
Caller 身份在会话期间是不可变的。不存在
身份伪造的原语。
### policies/\.yaml
每个策略文件定义了一个 caller 可以看到和执行的操作。
```
name: my-policy
accounts:
company-mail:
- path: INBOX
mode: blacklist
default: ENVELOPE
mark_seen: true
rules: []
gmail-work:
- path: INBOX
mode: whitelist
default: NONE
mark_seen: true
move_out: true
rules:
- match: { from_domain: hornbach.de }
grant: FULL
- match: { from_domain: amazon.de }
grant: FULL
- path: Archive
mode: whitelist
default: NONE
accept_incoming: true
rules: []
```
#### 文件夹策略字段
| 字段 | 必需 | 默认值 | 描述 |
|---|---|---|---|
| `path` | 是 | — | IMAP 文件夹路径(例如 `INBOX`, `INBOX/Invoices`, `[Gmail]/All Mail`) |
| `mode` | 是 | — | `whitelist`(默认为 NONE,规则授予访问权限)或 `blacklist`(默认 >NONE,规则限制访问权限) |
| `default` | 是 | — | 没有规则匹配时的默认可见性级别 |
| `rules` | 否 | `[]` | 针对特定发件人的覆盖规则(见下文) |
| `mark_seen` | 否 | `false` | 是否可以切换 `\Seen` 标志 |
| `mark_tagged` | 否 | `false` | 是否可以设置关键字和 `\Flagged` |
| `move_out` | 否 | `false` | 是否可以从该文件夹移除邮件 |
| `accept_incoming` | 否 | `false` | 是否可以接收移入/复制入的邮件 |
| `draft_append` | 否 | `false` | 是否可以追加新草稿 |
#### 可见性级别
每条规则仅授予此层级中的一个级别:
```
NONE < COUNT < METADATA < ENVELOPE < HEADERS < BODY < FULL
```
| 级别 | 暴露的内容 |
|---|---|
| `NONE` | 无(邮件不可见) |
| `COUNT` | 仅邮件数量(`folder_stats`) |
| `METADATA` | UID、大小、标志(`search`) |
| `ENVELOPE` | 发件人、收件人、主题、日期(`fetch_envelope`, `list_messages`) |
| `HEADERS` | 完整的 RFC 5322 标头块(`fetch_headers`) |
| `BODY` | 纯文本和 HTML 正文(`fetch_body`) |
| `FULL` | 所有内容,包括附件(`fetch_attachment`) |
#### 发件人规则语法
规则使用封闭的谓词集合。一个规则内的谓词
进行“与”组合;一个文件夹中的多个规则进行“或”组合。
| 谓词 | 类型 | 示例 |
|---|---|---|
| `from` | 精确电子邮件 | `alice@example.com` |
| `from_domain` | 域名(不区分大小写,容忍末尾的点) | `hornbach.de` |
| `to` | 精确电子邮件 | `billing@company.com` |
| `to_contains` | 子字符串 | `team` |
| `subject_contains` | 子字符串(不区分大小写,NFC 标准化) | `rechnung` |
| `has_attachment` | 布尔值 | `true` |
| `flagged` | 布尔值 | `true` |
| `newer_than` | 持续时间 | `30d` |
| `older_than` | 持续时间 | `90d` |
| `size_gt` | 字节 | `10000` |
| `size_lt` | 字节 | `1500` |
在 `whitelist` 模式下,规则使用 `grant: `。在 `blacklist` 模式下,
规则使用 `cap: `。在一个文件夹中混合使用两者会导致解析时
错误。
`flagged` 谓词匹配 IMAP 的 `\Flagged` 标志(在
Gmail 中表示加星标),允许像“授予对所有加星标邮件的完全访问权限”这样的规则:
```
- path: INBOX
mode: whitelist
default: NONE
rules:
- match: { flagged: true }
grant: FULL
```
## MCP 工具表面
共有十九个工具,每个工具都严格受限于一个可见性级别或一个
能力。
### 读取工具 (10)
| 工具 | 最低可见性 | 描述 |
|---|---|---|
| `list_accounts` | — | 列出可见账户 + `hidden_accounts_count` |
| `list_folders` | COUNT | 列出可见文件夹 + `hidden_folders_count` |
| `list_labels` | COUNT | 仅限 Gmail:列出带标志的标签 |
| `list_messages` | ENVELOPE | **读取邮件的主要工具。** 返回每封邮件的发件人、主题、日期。支持条件和分页。 |
| `folder_stats` | COUNT | 按可见性级别统计的邮件数量 |
| `search` | METADATA | 搜索 UID,并返回 `matched_total` / `matched_visible` / `filtered_out` |
| `fetch_envelope` | ENVELOPE | 根据 UID 获取单个邮件的发件人、收件人、主题、日期 |
| `fetch_headers` | HEADERS | 完整的 RFC 5322 标头 |
| `fetch_body` | BODY | 纯文本和 HTML 正文 |
| `fetch_attachment` | FULL | MIME 附件字节 |
### 写入工具 (6)
| 工具 | 所需能力 | 描述 |
|---|---|---|
| `mark_seen` | `mark_seen` | 切换单个邮件的 `\Seen` 标志 |
| `bulk_mark_seen` | `mark_seen` | 切换所有匹配条件邮件的 `\Seen` 标志 |
| `mark_tagged` | `mark_tagged` | 添加/删除关键字 |
| `move` | `move_out` + `accept_incoming` | 移动邮件(账户内:原生 MOVE;跨账户:saga) |
| `copy` | `accept_incoming` | 将邮件复制到目标文件夹 |
| `create_draft` | `draft_append` | 追加 RFC 5322 草稿 |
### 元工具 (3)
| 工具 | 描述 |
|---|---|
| `describe_policy` | Caller 自身的策略配置(账户、文件夹、能力、隐藏计数)。从不暴露规则模式或其他 caller。 |
| `get_caller_identity` | 当前会话已解析的 `caller_id` |
| `get_transaction_status` | 跨账户移动 saga 的 WAL 状态 |
### 刻意移除的功能
`delete`, `expunge`, `raw_imap_command`, `fetch_raw_rfc822`, 跨账户
搜索,MCP 资源订阅,文件夹 CRUD,通过 MCP 重新加载策略。
见 [ADR 0018](docs/adr/0018-non-goal-tool-surface.md)。
## 环境变量
| 变量 | 必需 | 描述 |
|---|---|---|
| `IMAP_MCP_CONFIG_DIR` | 是 | 配置目录的路径 |
| `IMAP_MCP_CALLER_ID` | 仅限 stdio_trusted | stdio 传输协议的 Caller 身份 |
| `IMAP_MCP_OAUTH_CLIENT_ID` | xoauth2 账户 | 来自 GCP 控制台的 OAuth2 client ID |
| `IMAP_MCP_OAUTH_CLIENT_SECRET` | xoauth2 账户 | 来自 GCP 控制台的 OAuth2 client secret |
| `IMAP_MCP_HTTP_HOST` | 否 | HTTP 的绑定地址(默认:`127.0.0.1`) |
| `IMAP_MCP_HTTP_PORT` | 否 | HTTP 的端口(默认:`0` = 临时端口) |
| `IMAP_MCP_APPEND_TIMEOUT` | 否 | IMAP APPEND 的超时时间(秒) |
| `OTEL_EXPORTER_OTLP_ENDPOINT` | 否 | 用于链路追踪的 OTLP endpoint(例如 `http://localhost:4317`) |
## OAuth2 引导
对于 `auth.type: xoauth2`(例如 Gmail)的账户,每个账户运行一次
交互式引导以获取 refresh token:
```
IMAP_MCP_CONFIG_DIR=~/.config/imap-mcp \
IMAP_MCP_OAUTH_CLIENT_ID=your-client-id.apps.googleusercontent.com \
IMAP_MCP_OAUTH_CLIENT_SECRET=your-client-secret \
imap-mcp-oauth-bootstrap --account user@company.com
```
这将打印一个 URL。在浏览器中打开它,完成 Google 授权
流程,然后将重定向的 URL 粘贴回终端。成功后,
refresh token 将存储在 secret store 中。服务器随后
会自动将其换取 access token。
### 设置 GCP OAuth client
1. 前往 https://console.cloud.google.com/apis/credentials
2. 创建凭据 > OAuth client ID > 桌面应用
3. 复制 Client ID 和 Client Secret
4. 将它们用作 `IMAP_MCP_OAUTH_CLIENT_ID` 和 `IMAP_MCP_OAUTH_CLIENT_SECRET`
## 链路追踪(可选)
```
pip install sc-imap-mcp[tracing]
cd ops/tracing && docker compose up -d
# 添加到你的 MCP server config:
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
```
打开 http://localhost:16686 访问 Jaeger UI。每次 MCP 工具调用
都会创建一个链路追踪,其中包含嵌套的 IMAP 连接和身份验证 span。
## 策略重载
向正在运行的服务器进程发送 `SIGHUP`。服务器会重新解析
整个配置目录,进行验证,并原子地替换内存中的状态。
解析或验证错误将保留之前的策略,
并写入包含该错误的审计记录。
```
kill -HUP $(pidof imap-mcp)
```
## 审计日志
仅追加的 JSONL 格式,带有 SHA-256 哈希链,每个 UTC 日对应一个文件。
严格的防内容泄露规则:在 DENY 记录中
不包含邮件正文、主题、附件
文件名、OAuth token 或明文发件人地址。
## 技术实现栈
- **语言:** Python 3.11+
**IMAP:** `aioimaplib` (IMAP4 和 IMAP4_SSL)
- **MCP:** 官方 `mcp` SDK (stdio + HTTP/SSE)
- **存储:** 用于 WAL 的 `aiosqlite`;用于配置的 YAML
- **验证:** `pydantic` v2(严格模式)
- **OAuth:** `httpx` + 第一方流程实现
- **链路追踪:** OpenTelemetry(可选,通过 `[tracing]` 扩展)
- **测试:** `pytest`(属性测试)+ `behave`(BDD,241 个场景)
## 测试
```
# BDD suite(IMAP fixtures 需要 Docker)
cd bdd && docker compose -f docker/docker-compose.yml up -d
.venv/bin/behave --no-color --format=progress features/
# Server property 测试
cd server && .venv/bin/pytest tests/policy/ -q
```
## 文档
设计记录在 [`docs/adr/`](docs/adr/) 下的 23 个 ADR 中。
限制记录在 [`docs/limitations/`](docs/limitations/) 下。
错误路径分析位于
[`docs/error_path_analysis.md`](docs/error_path_analysis.md)。
## 许可证
GPL-3.0-or-later。见 [`LICENSE`](LICENSE)。
标签:AI中间件, Blue Team, IMAP协议, LLM代理, MCP服务器, Python, Streamlit, 无后门, 用户代理, 访问控制, 逆向工具, 邮件系统