cunicopia-dev/gmail-mcp
GitHub: cunicopia-dev/gmail-mcp
一个通过单一 OAuth 客户端同时连接并管理多个 Gmail 账户的 MCP 服务器,支持跨账户搜索、读取、起草和管理标签。
Stars: 0 | Forks: 0
# gmail-mcp





**一个通过单一连接读取_所有_ Gmail 账户的 [MCP](https://modelcontextprotocol.io) 服务器。**
大多数 Gmail 集成(包括原生连接器)在每次 OAuth 授权时只能绑定一个账户:连接第二个收件箱就会断开第一个。`gmail-mcp` 可以同时保持任意数量的账户处于授权状态。一个 Google Cloud 客户端即可授权所有账户,每个账户都会在本地 SQLite 文件中作为一行落地,并且每个工具都接受一个 `account` 参数,用于路由到正确的邮箱。`search_all_accounts` 通过单个查询即可扫视所有账户。
它的设计旨在**完全被掌控**:通过 stdio 在进程内运行,将 token 存储在一个您可以检查、复制或删除的 SQLite 文件中,仅与 Google 和您的 MCP 客户端通信,并且不硬编码任何机密。
它可以读取、搜索、起草和打标签。它不会发送邮件 —— `create_draft` 只会为您留下一个草稿,由您自己发送。这是一个经过深思熟虑的默认设置(原因详见[安全模型](#security-model)),并非强硬立场;如果您希望实现自动发送,只需稍加修改或使用其他服务器即可实现。
## 目录
- [30秒了解核心理念](#the-idea-in-30-seconds)
- [设计说明](#design-notes)
- [工具](#tools)
- [架构](#architecture)
- [身份与认证模型](#identity--auth-model)
- [OAuth 模型](#the-oauth-model)
- [多账户模型](#the-multi-account-model)
- [Token 生命周期](#token-lifecycle)
- [无头认证路径](#the-headless-auth-path)
- [安全模型](#security-model)
- [安装](#install)
- [快速开始](#quickstart)
- [配置](#configuration)
- [注册到 MCP 客户端](#register-with-an-mcp-client)
- [开发](#development)
- [项目结构](#project-layout)
- [许可协议](#license)
## 30秒了解核心理念
通过 CLI 一次性授权 N 个账户。然后每个工具都接受一个 `account` 参数,并且 `search_all_accounts` 会一次性命中所有账户:
```
search_all_accounts(query="invoice newer_than:30d")
── personal@gmail.com ───────────────────────────────
from: billing@acme.com subject: Invoice #4821 (id 18f...)
── work@company.com ─────────────────────────────────
from: ap@vendor.io subject: March invoice (id 19a...)
```
一次查询,覆盖所有收件箱,每个结果都标记有对应的账户并携带 message id —— 因此 agent 接下来可以链式调用 `read_message(account, id)` 或 `create_draft(...)`。
## 设计说明
**一个 OAuth 客户端,多个收件箱。** 一个单一的 Google Cloud 项目和一个 `client_secret.json` 即可授权所有账户。添加第十个收件箱与添加第一个的流程一样,只需一条命令。
**朴素的存储方式。** Token 存放在 `~/.gmail-mcp/` 下的一个 SQLite 文件中。没有 daemon,不依赖 keyring,也没有云端。通过复制它来进行备份;通过删除一行来撤销某个账户;使用任何 SQLite 工具对其进行检查。
**最小权限。** 四个粒度范围 —— `gmail.readonly`、`gmail.compose`、`gmail.modify`、`gmail.settings.basic` —— 绝不使用全邮箱权限的 `https://mail.google.com/`。它可以读取、起草、打标签和管理过滤器;它从不发送邮件,并且它创建的过滤器无法将邮件转发至账户之外。
**对无头环境友好。** 认证流程假定服务器可能没有浏览器:它会打印一个授权 URL,绑定一个固定端口,然后您通过 SSH 转发重定向。在桌面环境下也运行良好。
## 工具
除了 `list_accounts` 和 `search_all_accounts` 之外,每个工具都接受一个 `account` 参数(电子邮件地址)。未知账户将返回错误,并列出已授权的账户。
| 工具 | 参数 | 返回值 |
| --- | --- | --- |
| `list_accounts` | — | 已授权的账户 + 上次使用时间。用于发现有效的 `account` 值。 |
| `search_messages` | `account`, `query`, `max_results=20` | 包含 id 的邮件摘要(Gmail 搜索语法)。 |
| `read_message` | `account`, `message_id`, `format="full"` | 解码后的邮件头,纯文本正文(如有需要会去除 HTML),附件元数据。 |
| `read_thread` | `account`, `thread_id` | 会话中的所有邮件,按顺序排列。 |
| `search_all_accounts` | `query`, `max_results_per_account=10` | 跨**所有**账户进行单次搜索,每个结果均由对应账户标记。 |
| `create_draft` | `account`, `to`, `subject`, `body`, `cc?`, `bcc?`, `html=false` | 生成草稿(未发送)。返回草稿 id。 |
| `list_drafts` | `account`, `max_results=20` | 该账户内的草稿 id。 |
| `list_labels` | `account` | 该账户的标签(名称 + id)。 |
| `modify_labels` | `account`, selection (`message_id` \| `message_ids` \| `query`), `add?`, `remove?` | 对**选定范围**(一个 id、一个列表,或查询匹配的所有内容)添加/移除标签,按每批 1000 次调用。通用修改器:归档 = 移除 INBOX,标记已读 = 移除 UNREAD,加星标 = 添加 STARRED。 |
| `trash` | `account`, selection (`message_id` \| `message_ids` \| `query`) | 将选定内容移至废纸篓(30 天内可恢复;非永久删除)。拒绝空选定范围。 |
| `list_filters` | `account` | 该账户的过滤器:id、条件、操作(标签 id 显示为名称)。 |
| `create_filter` | `account`, `from_address`/`to_address`/`subject`/`query`/`has_attachment` 之一,加上一个操作(`archive`/`mark_read`/`delete`/`star` 或 `add_labels`/`remove_labels`) | 应用于**接收**邮件的服务端规则。无法将其转发至账户之外。 |
| `delete_filter` | `account`, `filter_id` | 根据 id 移除过滤器(不影响已应用过该规则的邮件)。 |
## 架构
```
flowchart TD
subgraph client[Your machine]
Agent[MCP client / agent]
CLI[gmail-mcp-auth CLI]
Server[gmail-mcp stdio server]
Store[("SQLite
~/.gmail-mcp/tokens.db")] Secret["client_secret.json
one OAuth client"] end Google[Google OAuth + Gmail API] CLI -->|"loopback OAuth, once per account"| Google CLI -->|"store refresh token"| Store Secret -.-> CLI Agent -->|"tool call (account=...)"| Server Server -->|"look up + refresh creds"| Store Secret -.-> Server Server -->|"read / draft / label"| Google Server --> Agent ``` 每个账户通过 CLI 进行一次授权(需要一个浏览器)。此后,stdio 服务器直接从 SQLite 读取 token,按需刷新 access token 并将其持久化保存。本节其余部分是对“为什么它会这样运作”的详细解释。 ## 身份与认证模型 介绍 `gmail-mcp` 如何向 Gmail 进行身份验证、如何在单个 OAuth 客户端下处理多个账户、如何随时间推移刷新 token,以及如何在无头服务器上授权账户。如果您只是想快速运行,请跳转至[快速开始](#quickstart)。 ### OAuth 模型 `gmail-mcp` 使用 Google **"Desktop app"** OAuth 客户端(在 OAuth 2.0 术语中称为*已安装应用*)进行身份验证,由 `google-auth-oauthlib` 中的 `InstalledAppFlow` 辅助程序驱动。 **为什么使用已安装应用/桌面客户端。** 已安装应用运行在最终用户控制的机器上,因此 OAuth 将它们视为**公共客户端**:下载的 `client_secret.json` 中的 `client_secret` *不被*视为机密。对于本地 CLI/桌面工具来说,这是正确的信任模型 —— 没有任何服务器端组件可以真正保证机密的安全性,其安全性依赖于用户对重定向(回环地址)的控制,而不是对机密的保密。这是 Google 为命令行和桌面工具推荐的客户端类型。 **回环重定向流程。** 您在浏览器中批准授权后,Google 会将授权码重定向到 `http://localhost:/`,此时一个微型的临时 HTTP 服务器(由 `InstalledAppFlow.run_local_server` 启动)会将其捕获。`gmail-mcp` 将此端口固定(默认为 `8765`,可通过 `GMAIL_MCP_OAUTH_PORT` 覆盖),并以 `open_browser=False` 运行,使其在没有浏览器的机器上也能工作 —— 参见[无头认证路径](#the-headless-auth-path)。
**请求的 Scopes。** 四个粒度范围 —— 绝不使用全邮箱权限的 `https://mail.google.com/`:
| Scope | 授予的权限 |
|-------|----------------|
| `gmail.readonly` | 读取邮件和元数据:搜索邮件/会话,读取正文,列出标签和草稿。只读 —— 无法修改任何内容。 |
| `gmail.compose` | 创建、更新和管理草稿。仅供 `create_draft` 使用。 |
| `gmail.modify` | 为邮件添加/移除标签。供 `modify_labels` 使用。 |
| `gmail.settings.basic` | 列出、创建和删除过滤器。供 `list_filters`/`create_filter`/`delete_filter` 使用。**不**授予更改转发地址的权限(这属于 `gmail.settings.sharing`,未请求)。 |
未请求 `gmail.send`。如果没有它,凭证就没有发送邮件的 Gmail API 路径 —— 这种仅起草草稿的行为是授权的固有属性,而不仅仅是因为省略了某个工具。同样也未请求 `gmail.settings.sharing`,因此没有过滤器可以将邮件转发到其他地址。Scope 列表仅存于一处:`src/gmail_mcp/config.py` 中的 `SCOPES`。
### 多账户模型
- **一个 OAuth 客户端授权多个账户。** 您只需创建一个 Google Cloud 项目和一个 "Desktop app" OAuth 客户端,然后为每个 Gmail 账户运行一次授权流程,每次登录您要添加的账户。一个单独的 `client_secret.json` 可以授权任意数量的账户。
- **每个账户在 SQLite 中占一行。** 每个授权的账户都存储在 `accounts` 表(`~/.gmail-mcp/tokens.db`,可通过 `GMAIL_MCP_DB` 覆盖)中,**以邮箱作为键**。该行保存了长期有效的 refresh token、最近的 access-token blob、已授予的 scope 以及时间戳。
- **工具调用根据 `account` 参数进行路由。** 除了 `list_accounts` 和 `search_all_accounts` 之外,每个工具都接受一个 `account` 参数。服务器会查找该邮箱,为其构建凭证,并以该账户身份调用 Gmail API。未知账户将返回明确的错误,并列出已获授权的账户。`search_all_accounts` 会遍历存储的每一行数据。
```
flowchart LR
Client[MCP client / agent] -->|"account=a@x.com"| Server[gmail_mcp.server]
Server --> Store[("accounts table
keyed by email")] Store -->|"row a@x.com"| CredsA[Credentials a] Store -->|"row b@y.com"| CredsB[Credentials b] CredsA --> InboxA["Gmail: a@x.com"] CredsB --> InboxB["Gmail: b@y.com"] Secret["client_secret.json
one OAuth client"] -.->|"shared by all rows"| CredsA Secret -.-> CredsB ``` ### Token 生命周期 **初始授权**(一次性,每个账户,通过 CLI 进行)。OAuth 流程需要一个浏览器,而 MCP 工具无法顺畅地驱动浏览器,因此授权操作被放在了 `gmail-mcp-auth` CLI 中,而不是作为一个工具。 ``` sequenceDiagram actor User participant CLI as gmail-mcp-auth add participant Browser participant Google as Google OAuth + Gmail API participant Store as SQLite token store User->>CLI: run `gmail-mcp-auth add` CLI->>CLI: load client_secret.json,
start loopback server on :8765 CLI-->>User: print consent URL (open_browser=False) User->>Browser: open URL, sign into target account Browser->>Google: consent + approve scopes Google-->>Browser: redirect with authorization code Browser->>CLI: GET http://localhost:8765/?code=... CLI->>Google: exchange code for tokens Google-->>CLI: access token + refresh token CLI->>Google: users.getProfile (discover email) Google-->>CLI: emailAddress CLI->>Store: upsert(email, refresh_token, token, scopes) CLI-->>User: "Authorized and stored: you@gmail.com" ``` - CLI 传递 `prompt="consent"` 以**强制签发 refresh token** —— Google 仅在新的授权同意下才会返回它。如果没有返回 refresh token,CLI 会明确报错(请在 撤销应用授权并重新运行)。
- 账户的邮箱是**自动发现的**,无需手动输入:在 token 交换之后,CLI 会调用 `users.getProfile` 并以返回的地址作为键存储该行数据。
**按请求刷新**(每次工具调用)。Access token 的有效期很短(约 1 小时)。在每次调用时,服务器都会为目标账户重新构建凭证,让 `google-auth` 按需进行刷新,并将刷新后的 blob 持久化保存回去。
```
sequenceDiagram
participant Client as MCP client / agent
participant Server as gmail_mcp.server
participant Store as SQLite token store
participant Google as Google OAuth + Gmail API
Client->>Server: tool call (account=you@gmail.com)
Server->>Store: get(account) → refresh_token + last token
Server->>Server: build Credentials
alt access token still valid
Server->>Google: Gmail API request
else access token expired
Server->>Google: refresh using refresh_token
Google-->>Server: new access token
Server->>Store: update_token(account, new blob)
Server->>Google: Gmail API request
end
Google-->>Server: response
Server->>Store: touch(account) → last_used_at
Server-->>Client: result (email content wrapped as untrusted)
```
如果刷新失败(授权被撤销、refresh token 过期),服务器将引发 `GmailAuthError` 并提示“重新运行 `gmail-mcp-auth add`”,而不会导致崩溃。
**测试状态与已发布状态 —— 7 天陷阱。** 这就是常见的“一周后失效了”的意外情况:
- 当 OAuth 同意屏幕处于 **Testing** 模式时,只有列出的**测试用户**才能授权,并且签发给**未经验证**的应用的 refresh token **会在 7 天后过期** —— 您需要每周重新运行 `gmail-mcp-auth add`。
- **发布**应用(同意屏幕 → *Publish app*)会使 refresh token 变为长期有效。Google 会警告它“未经验证” —— 这对于您不分发的、自行托管个人工具来说是预料之中且完全没问题的。要长期使用,请发布它。[SET.md](docs/SETUP.md) 中提供了具体的点击步骤。
### 无头认证路径
典型的目标通常是一个无头服务器(没有桌面,没有浏览器),但 OAuth 同意必须在浏览器中进行。该流程通过以下方式弥合了这一鸿沟:
- **`open_browser=False`** —— CLI 会打印授权 URL,而不是启动浏览器。您可以在自己的笔记本上打开它,并登录您要添加的账户。
- **固定的回环端口** —— 批准后,Google 会重定向到 `http://localhost:/`。这个“localhost”是*服务器*的回环地址,CLI 将在那里进行监听。该端口是固定的(默认为 `8765`,`GMAIL_MCP_OAUTH_PORT`),以便您能确定性地对其进行转发。
- **SSH 端口转发** —— 将您笔记本的浏览器桥接到服务器的回环地址:
ssh -L 8765:localhost:8765 you@your-server
现在,当重定向到达您笔记本的 `localhost:8765` 时,SSH 会将其隧道传输到服务器,CLI 就在那里获取授权码并完成交换。
## 安全模型
收件箱里装满了其他人编写的文本,因此它是进行 prompt injection 的天然场所。标准的框架是**致命三角** —— 当一个 agent 同时具备以下三点时,注入是非常危险的:
```
flowchart LR
A[Private data
your mailboxes] --- C{Injection
risk} B[Untrusted content
any email you receive] --- C D[Egress channel
a way to send data out] --- C C -.->|drafts-only removes the obvious one| D style D stroke-dasharray: 5 5 ``` 邮件阅读器本质上就具备前两项。一些特定的选择使得第三项保持低风险状态: - **起草而不是发送。** `create_draft` 是外发功能的上限 —— 没有发送工具,也没有 `gmail.send` scope。草稿会一直留在您的草稿文件夹中,直到*您*亲自发送,因此埋藏在电子邮件中的指令无法让 agent 将您的数据发送到任何地方。这是一个合理的默认设置,如果您想发送也很容易更改。 - **电子邮件内容被标记为不受信任。** 工具返回的邮件文本会被单个辅助程序(`gmail.py` 中的 `wrap_untrusted`)包裹在 `⟦UNTRUSTED EMAIL CONTENT⟧` 分隔符中,并将 id 保留在**外部**,以便工具链仍然可以正常工作。读取工具还会在其描述中指出,内容是数据,而不是指令。 **已知限制。** 这仅约束*此*服务器的攻击面。如果同一个 agent 会话还拥有可以连接到开放互联网(Web 获取、HTTP)的工具,那将是一个单独的出口路径,`gmail-mcp` 对此无能为力 —— 将它与任意出口工具配对会在其他地方重新开启致命三角。在决定哪些工具共享会话时请务必慎重。 另外还有两点说明:未实现审计日志(有意将其排除在范围之外),并且未硬编码任何机密 —— `client_id`/`client_secret` 来自您下载的 `client_secret.json`,而 token 仅存在于您本地的 SQLite 存储中。 ## 安装 需要 Python 3.12+。 ``` git clone https://github.com/cunicopia-dev/gmail-mcp.git cd gmail-mcp python -m venv .venv && source .venv/bin/activate pip install -e . # add ".[dev]" for ruff + pytest ``` 这将安装两个控制台脚本:**`gmail-mcp`**(stdio 服务器)和 **`gmail-mcp-auth`**(账户授权 CLI)。 ## 快速开始 您需要一个 Google "Desktop app" OAuth 客户端(`client_secret.json`),并且每个账户需要进行一次授权。完整的逐步操作指南 —— 包括创建 Google Cloud 项目、启用 Gmail API、发布同意屏幕以及无头 SSH 转发步骤 —— 在 **[docs/SETUP.md](docs/SETUP.md)** 中。简短版本如下: ``` # 1. 将您下载的 OAuth client 放置在此处: mkdir -p ~/.gmail-mcp && mv ~/Downloads/client_secret_*.json ~/.gmail-mcp/client_secret.json # 2. 授权一个账号(会打印一个 URL 以在浏览器中打开;每个账号重复此操作)。 # 在 headless 服务器上,请先使用 -L 8765:localhost:8765 进行 SSH 连接。 gmail-mcp-auth add # 3. 确认已授权的内容。 gmail-mcp-auth list # 4. 将您的 MCP client 指向 `gmail-mcp` 命令(见下文)。 ``` 之后可使用 `gmail-mcp-auth remove you@gmail.com` 移除账户。 ## 配置 均为可选 —— 在 `~/.gmail-mcp/` 下具有合理的默认值。 | 变量 | 默认值 | 用途 | | --- | --- | --- | | `GMAIL_MCP_DB` | `~/.gmail-mcp/tokens.db` | SQLite token 存储路径。 | | `GMAIL_MCP_CLIENT_SECRET` | `~/.gmail-mcp/client_secret.json` | 下载的 Google OAuth 客户端。 | | `GMAIL_MCP_OAUTH_PORT` | `8765` | 认证流程的固定回环端口(在无头机器上通过 SSH 转发此端口)。 | ## 注册到 MCP 客户端 该服务器使用 stdio 通信。将您客户端的 `mcpServers` 配置指向 `gmail-mcp` 命令: ``` { "mcpServers": { "gmail": { "command": "/path/to/gmail-mcp/.venv/bin/gmail-mcp" } } } ``` 如果 `gmail-mcp` 位于 `PATH` 中,则使用 `"command": "gmail-mcp"` 就足够了。如果需要,请显式覆盖路径(某些客户端不会展开 `~`): ``` { "mcpServers": { "gmail": { "command": "/path/to/gmail-mcp/.venv/bin/gmail-mcp", "env": { "GMAIL_MCP_DB": "/home/you/.gmail-mcp/tokens.db", "GMAIL_MCP_CLIENT_SECRET": "/home/you/.gmail-mcp/client_secret.json" } } } } ``` ## 开发 ``` pip install -e ".[dev]" ruff check . pytest # 48 tests, no network — the Gmail client is mocked ``` 测试覆盖了纯逻辑层 —— MIME 解析/解码、标签名→id 解析、不受信任内容包装器、输出格式化,以及针对临时 SQLite db 的 token 存储 CRUD 操作。 ## 项目结构 ``` src/gmail_mcp/ server.py MCP tool definitions + dispatch + per-account routing gmail.py Gmail service build, token refresh/persist, MIME parse/format, wrap_untrusted(), label resolution, MIME message build store.py TokenStore — sqlite3 accounts table CRUD auth.py gmail-mcp-auth CLI: add / list / remove (loopback OAuth) config.py SCOPES + env-overridable paths docs/ SETUP.md step-by-step Google Cloud + account authorization tests/ store / gmail / server, Gmail client mocked ``` ## 许可协议 MIT —— 详情见 [LICENSE](LICENSE)。
~/.gmail-mcp/tokens.db")] Secret["client_secret.json
one OAuth client"] end Google[Google OAuth + Gmail API] CLI -->|"loopback OAuth, once per account"| Google CLI -->|"store refresh token"| Store Secret -.-> CLI Agent -->|"tool call (account=...)"| Server Server -->|"look up + refresh creds"| Store Secret -.-> Server Server -->|"read / draft / label"| Google Server --> Agent ``` 每个账户通过 CLI 进行一次授权(需要一个浏览器)。此后,stdio 服务器直接从 SQLite 读取 token,按需刷新 access token 并将其持久化保存。本节其余部分是对“为什么它会这样运作”的详细解释。 ## 身份与认证模型 介绍 `gmail-mcp` 如何向 Gmail 进行身份验证、如何在单个 OAuth 客户端下处理多个账户、如何随时间推移刷新 token,以及如何在无头服务器上授权账户。如果您只是想快速运行,请跳转至[快速开始](#quickstart)。 ### OAuth 模型 `gmail-mcp` 使用 Google **"Desktop app"** OAuth 客户端(在 OAuth 2.0 术语中称为*已安装应用*)进行身份验证,由 `google-auth-oauthlib` 中的 `InstalledAppFlow` 辅助程序驱动。 **为什么使用已安装应用/桌面客户端。** 已安装应用运行在最终用户控制的机器上,因此 OAuth 将它们视为**公共客户端**:下载的 `client_secret.json` 中的 `client_secret` *不被*视为机密。对于本地 CLI/桌面工具来说,这是正确的信任模型 —— 没有任何服务器端组件可以真正保证机密的安全性,其安全性依赖于用户对重定向(回环地址)的控制,而不是对机密的保密。这是 Google 为命令行和桌面工具推荐的客户端类型。 **回环重定向流程。** 您在浏览器中批准授权后,Google 会将授权码重定向到 `http://localhost:
keyed by email")] Store -->|"row a@x.com"| CredsA[Credentials a] Store -->|"row b@y.com"| CredsB[Credentials b] CredsA --> InboxA["Gmail: a@x.com"] CredsB --> InboxB["Gmail: b@y.com"] Secret["client_secret.json
one OAuth client"] -.->|"shared by all rows"| CredsA Secret -.-> CredsB ``` ### Token 生命周期 **初始授权**(一次性,每个账户,通过 CLI 进行)。OAuth 流程需要一个浏览器,而 MCP 工具无法顺畅地驱动浏览器,因此授权操作被放在了 `gmail-mcp-auth` CLI 中,而不是作为一个工具。 ``` sequenceDiagram actor User participant CLI as gmail-mcp-auth add participant Browser participant Google as Google OAuth + Gmail API participant Store as SQLite token store User->>CLI: run `gmail-mcp-auth add` CLI->>CLI: load client_secret.json,
start loopback server on :8765 CLI-->>User: print consent URL (open_browser=False) User->>Browser: open URL, sign into target account Browser->>Google: consent + approve scopes Google-->>Browser: redirect with authorization code Browser->>CLI: GET http://localhost:8765/?code=... CLI->>Google: exchange code for tokens Google-->>CLI: access token + refresh token CLI->>Google: users.getProfile (discover email) Google-->>CLI: emailAddress CLI->>Store: upsert(email, refresh_token, token, scopes) CLI-->>User: "Authorized and stored: you@gmail.com" ``` - CLI 传递 `prompt="consent"` 以**强制签发 refresh token** —— Google 仅在新的授权同意下才会返回它。如果没有返回 refresh token,CLI 会明确报错(请在
your mailboxes] --- C{Injection
risk} B[Untrusted content
any email you receive] --- C D[Egress channel
a way to send data out] --- C C -.->|drafts-only removes the obvious one| D style D stroke-dasharray: 5 5 ``` 邮件阅读器本质上就具备前两项。一些特定的选择使得第三项保持低风险状态: - **起草而不是发送。** `create_draft` 是外发功能的上限 —— 没有发送工具,也没有 `gmail.send` scope。草稿会一直留在您的草稿文件夹中,直到*您*亲自发送,因此埋藏在电子邮件中的指令无法让 agent 将您的数据发送到任何地方。这是一个合理的默认设置,如果您想发送也很容易更改。 - **电子邮件内容被标记为不受信任。** 工具返回的邮件文本会被单个辅助程序(`gmail.py` 中的 `wrap_untrusted`)包裹在 `⟦UNTRUSTED EMAIL CONTENT⟧` 分隔符中,并将 id 保留在**外部**,以便工具链仍然可以正常工作。读取工具还会在其描述中指出,内容是数据,而不是指令。 **已知限制。** 这仅约束*此*服务器的攻击面。如果同一个 agent 会话还拥有可以连接到开放互联网(Web 获取、HTTP)的工具,那将是一个单独的出口路径,`gmail-mcp` 对此无能为力 —— 将它与任意出口工具配对会在其他地方重新开启致命三角。在决定哪些工具共享会话时请务必慎重。 另外还有两点说明:未实现审计日志(有意将其排除在范围之外),并且未硬编码任何机密 —— `client_id`/`client_secret` 来自您下载的 `client_secret.json`,而 token 仅存在于您本地的 SQLite 存储中。 ## 安装 需要 Python 3.12+。 ``` git clone https://github.com/cunicopia-dev/gmail-mcp.git cd gmail-mcp python -m venv .venv && source .venv/bin/activate pip install -e . # add ".[dev]" for ruff + pytest ``` 这将安装两个控制台脚本:**`gmail-mcp`**(stdio 服务器)和 **`gmail-mcp-auth`**(账户授权 CLI)。 ## 快速开始 您需要一个 Google "Desktop app" OAuth 客户端(`client_secret.json`),并且每个账户需要进行一次授权。完整的逐步操作指南 —— 包括创建 Google Cloud 项目、启用 Gmail API、发布同意屏幕以及无头 SSH 转发步骤 —— 在 **[docs/SETUP.md](docs/SETUP.md)** 中。简短版本如下: ``` # 1. 将您下载的 OAuth client 放置在此处: mkdir -p ~/.gmail-mcp && mv ~/Downloads/client_secret_*.json ~/.gmail-mcp/client_secret.json # 2. 授权一个账号(会打印一个 URL 以在浏览器中打开;每个账号重复此操作)。 # 在 headless 服务器上,请先使用 -L 8765:localhost:8765 进行 SSH 连接。 gmail-mcp-auth add # 3. 确认已授权的内容。 gmail-mcp-auth list # 4. 将您的 MCP client 指向 `gmail-mcp` 命令(见下文)。 ``` 之后可使用 `gmail-mcp-auth remove you@gmail.com` 移除账户。 ## 配置 均为可选 —— 在 `~/.gmail-mcp/` 下具有合理的默认值。 | 变量 | 默认值 | 用途 | | --- | --- | --- | | `GMAIL_MCP_DB` | `~/.gmail-mcp/tokens.db` | SQLite token 存储路径。 | | `GMAIL_MCP_CLIENT_SECRET` | `~/.gmail-mcp/client_secret.json` | 下载的 Google OAuth 客户端。 | | `GMAIL_MCP_OAUTH_PORT` | `8765` | 认证流程的固定回环端口(在无头机器上通过 SSH 转发此端口)。 | ## 注册到 MCP 客户端 该服务器使用 stdio 通信。将您客户端的 `mcpServers` 配置指向 `gmail-mcp` 命令: ``` { "mcpServers": { "gmail": { "command": "/path/to/gmail-mcp/.venv/bin/gmail-mcp" } } } ``` 如果 `gmail-mcp` 位于 `PATH` 中,则使用 `"command": "gmail-mcp"` 就足够了。如果需要,请显式覆盖路径(某些客户端不会展开 `~`): ``` { "mcpServers": { "gmail": { "command": "/path/to/gmail-mcp/.venv/bin/gmail-mcp", "env": { "GMAIL_MCP_DB": "/home/you/.gmail-mcp/tokens.db", "GMAIL_MCP_CLIENT_SECRET": "/home/you/.gmail-mcp/client_secret.json" } } } } ``` ## 开发 ``` pip install -e ".[dev]" ruff check . pytest # 48 tests, no network — the Gmail client is mocked ``` 测试覆盖了纯逻辑层 —— MIME 解析/解码、标签名→id 解析、不受信任内容包装器、输出格式化,以及针对临时 SQLite db 的 token 存储 CRUD 操作。 ## 项目结构 ``` src/gmail_mcp/ server.py MCP tool definitions + dispatch + per-account routing gmail.py Gmail service build, token refresh/persist, MIME parse/format, wrap_untrusted(), label resolution, MIME message build store.py TokenStore — sqlite3 accounts table CRUD auth.py gmail-mcp-auth CLI: add / list / remove (loopback OAuth) config.py SCOPES + env-overridable paths docs/ SETUP.md step-by-step Google Cloud + account authorization tests/ store / gmail / server, Gmail client mocked ``` ## 许可协议 MIT —— 详情见 [LICENSE](LICENSE)。
标签:AI辅助工具, Gmail集成, LLM上下文, MCP服务, Python, SQLite, 多账户管理, 无后门, 逆向工具