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, 无后门, 用户代理, 访问控制, 逆向工具, 邮件系统