内容
# gmail-mcp
<!-- mcp-name: io.github.cunicopia-dev/multi-account-gmail-mcp -->





**一个 [MCP](https://modelcontextprotocol.io) 服务器,可以跨越所有 Gmail 账户进行读取**
大多数 Gmail 集成 — 包括原生连接器 — 绑定一个账户到每个 OAuth 授权:连接第二个邮箱时,第一个邮箱就会断开。`gmail-mcp` 可以同时授权任意数量的账户。 一个 Google Cloud 客户端授权所有账户,每个账户作为一行存储在本地 SQLite 文件中,每个工具都接受一个 `account` 参数,该参数会路由到正确的邮箱。`search_all_accounts` 可以在一次查询中扫描所有账户。
> Python 3.12+ · MIT · stdio MCP 服务器 + 授权 CLI · 本地 SQLite token 存储
它的设计是 **完全自主的**:在 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...)
```
一次查询,所有邮箱,每个结果都标记了其账户并携带了消息 ID — 这样代理可以链接 `read_message(account, id)` 或 `create_draft(...)`。
---
## 设计笔记
**一个 OAuth 客户端,多个邮箱。** 一个 Google Cloud 项目和一个 `client_secret.json` 授权所有账户。添加第十个邮箱与添加第一个邮箱的流程相同,只需一个命令。
**简单的存储。** token 存储在一个 SQLite 文件中,位于 `~/.gmail-mcp/` 目录下。没有守护进程,没有 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` | 消息摘要(Gmail 搜索语法)及 ID。 |
| `read_message` | `account`、`message_id`、`format="full"`、`max_body_chars?` | 解码的头部、纯文本正文(如果需要,HTML 被剥离)、附件元数据。默认正文大小有限;传递 `max_body_chars=0` 获取完整正文。 |
| `read_thread` | `account`、`thread_id`、`max_body_chars?` | 线程中的每个消息,按顺序。每个正文大小有限;`max_body_chars=0` 获取完整正文。 |
| `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 天;不是永久删除)。拒绝空选择。 |
| `bulk_action` | `account`、`action`、`selection`(`message_id` \| `message_ids` \| `query`) | `modify_labels` 的友好动词层。`action` ∈ `archive`/`unarchive`/`mark_read`/`mark_unread`/`star`/`unstar`/`spam`/`unspam`/`trash`/`untrash`。批量处理 1000 次/调用;拒绝空选择。 |
| `read_messages` | `account`、`message_ids` \| `query`、`max_results=25` | 批量读取多个消息的完整内容,一次调用(而非 N 次 `read_message` 调用)。 |
| `count_messages` | `query`、`account?`、`all_accounts=false` | **不** 获取内容的情况下统计匹配结果 — 大规模操作前的辐射范围检查。`all_accounts` 提供每个账户的明细 + 总数。 |
| `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 删除过滤器(不影响已处理的邮件)。 |
---
## 架构
```mermaid
flowchart TD
subgraph client[您的机器]
Agent[MCP 客户端 / 代理]
CLI[gmail-mcp-auth CLI]
Server[gmail-mcp stdio 服务器]
Store[("SQLite<br/>~/.gmail-mcp/tokens.db")]
Secret["client_secret.json<br/>一个 OAuth 客户端"]
end
Google[Google OAuth + Gmail API]
CLI -->|"循环 OAuth,一次一个账户"| Google
CLI -->|"存储刷新令牌"| Store
Secret -.-> CLI
Agent -->|"工具调用(account=...)"| Server
Server -->|"查找 + 刷新凭证"| Store
Secret -.-> Server
Server -->|"读取 / 草拟 / 标记"| Google
Server --> Agent
```
授权通过 CLI 进行,每个账户一次(它需要一个浏览器)。之后,stdio 服务器直接从 SQLite 读取令牌,根据需要刷新访问令牌并持久化。
---
## 身份和授权模型
`gmail-mcp` 如何向 Gmail 验证,管理单个 OAuth 客户端下的多个账户,刷新令牌并在无头服务器上授权账户。
### OAuth 模型
`gmail-mcp` 使用 Google **"桌面应用程序"** OAuth 客户端(一种 *已安装的应用程序*,在 OAuth 2.0 术语中),由 `google-auth-oauthlib` 中的 `InstalledAppFlow` 助手驱动。
**为什么选择已安装的应用程序 / 桌面客户端。** 已安装的应用程序在终端用户控制的机器上运行,因此 OAuth 将它们视为 **公共客户端**:下载的 `client_secret.json` 中的 `client_secret` *不* 被视为机密。这对于本地 CLI/桌面工具来说是正确的信任模型;没有服务器端组件可以真正保密一个秘密,安全性依赖于用户控制重定向(环回地址)而不是秘密的机密性。这是 Google 为命令行和桌面工具推荐的客户端类型。
**环回重定向流程。** 在浏览器中批准同意后,Google 将授权码重定向到 `http://localhost:<port>/`,其中一个微小的临时 HTTP 服务器(由 `InstalledAppFlow.run_local_server` 启动)捕获它。`gmail-mcp` 将此固定到一个固定端口(默认 `8765`,覆盖 `GMAIL_MCP_OAUTH_PORT`)并在没有浏览器的机器上运行,因此它在没有浏览器的机器上也能正常工作。
**请求的范围。** 四个细粒度范围 — 而不是完整的 `https://mail.google.com/`:
| 范围 | 授予权限 |
|-------|----------------|
| `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` 也未被请求,因此没有过滤器可以将邮件转发到另一个地址。范围列表位于一个位置:`src/gmail_mcp/config.py` 中的 `SCOPES`。
> **将过滤器范围添加到现有安装:** 扩大 `SCOPES` 不会追授已经授权的账户。每个账户必须重新运行 `gmail-mcp-auth add` 以重新同意新的范围;直到它这样做,过滤器工具返回一个 `403 不足范围` 错误。
### 多账户模型
- **一个 OAuth 客户端授权多个账户。** 您创建一个 Google Cloud 项目和一个“桌面应用程序”OAuth 客户端,然后为每个 Gmail 账户运行一次同意流程,分别登录到您要添加的账户。一个 `client_secret.json` 可以授权任意数量的账户。
- **每个账户是 SQLite 中的一行。** 每个已授权的账户存储在 `accounts` 表(`~/.gmail-mcp/tokens.db`,可覆盖 `GMAIL_MCP_DB`)中,**按电子邮件地址索引**。该行存储长久的刷新令牌、最近的访问令牌 blob、授予的范围和时间戳。
- **工具调用通过 `account` 参数路由。** 除了 `list_accounts` 和 `search_all_accounts` 之外的每个工具都接受一个 `account` 参数。服务器查找该电子邮件地址,构建一个凭证,并作为该账户调用 Gmail API。未知账户返回一个明确的错误,列出已授权的账户。`search_all_accounts` 迭代每个存储的行。
```mermaid
flowchart LR
Client[MCP 客户端 / 代理] -->|"account=a@x.com"| Server[gmail_mcp.server]
Server --> Store[("accounts 表<br/>按电子邮件地址索引")]
Store -->|"行 a@x.com"| CredsA[凭证 a]
Store -->|"行 b@y.com"| CredsB[凭证 b]
CredsA --> InboxA["Gmail:a@x.com"]
CredsB --> InboxB["Gmail:b@y.com"]
Secret["client_secret.json<br/>一个 OAuth 客户端"] -.->|"所有行共享"| CredsA
Secret -.-> CredsB
```
### Token 生命周期
**初始授权**(每个账户一次,通过 CLI)。OAuth 流程需要一个浏览器,而 MCP 工具无法干净地驱动它,因此授权位于 `gmail-mcp-auth` CLI 中,而不是作为一个工具。
```mermaid
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: 运行 `gmail-mcp-auth add`
CLI->>CLI: 加载 client_secret.json,<br/>在 :8765 上启动环回服务器
CLI-->>User: 打印同意 URL(open_browser=False)
User->>Browser: 打开网址,登录到目标账户
Browser->>Google: 同意 + 批准范围
Google-->>Browser: 重定向授权码
Browser->>CLI: GET http://localhost:8765/?code=...
CLI->>Google: 交换代码获取令牌
Google-->>CLI: 访问令牌 + 刷新令牌
CLI->>Google: users.getProfile(发现电子邮件)
Google-->>CLI: emailAddress
CLI->>Store: upsert(email, refresh_token, token, scopes)
CLI-->>User: “已授权并存储:you@gmail.com”
```
- CLI 传递 `prompt="consent"` 以 **强制发出刷新令牌** — Google 仅在全新同意时返回它。 如果没有刷新令牌,CLI 会明确报错(在 <https://myaccount.google.com/permissions> 撤销应用程序并重新运行)。
- 账户的电子邮件地址被 **发现**,而不是输入的:令牌交换后,CLI 调用 `users.getProfile` 并按返回的地址索引存储的行。
**每次请求刷新**(每个工具调用)。访问令牌寿命短(≈1 小时)。在每次调用时,服务器为目标账户重建凭证,让 `google-auth` 根据需要刷新它,并将刷新的 blob 持久化。
```mermaid
sequenceDiagram
participant Client as MCP 客户端 / 代理
participant Server as gmail_mcp.server
participant Store as SQLite token store
participant Google as Google OAuth + Gmail API
Client->>Server: 工具调用(account=you@gmail.com)
Server->>Store: 获取(account)→ 刷新令牌 + 上一个令牌
Server->>Server: 构建凭证
alt 访问令牌仍然有效
Server->>Google: Gmail API 请求
else 访问令牌已过期
Server->>Google: 使用刷新令牌刷新
Google-->>Server: 新的访问令牌
Server->>Store: 更新令牌(account, new blob)
Server->>Google: Gmail API 请求
end
Google-->>Server: 响应
Server->>Store: 触摸(account)→ last_used_at
Server-->>Client: 结果(包装为非受信的电子邮件内容)
```
如果刷新失败(撤销授权,刷新令牌过期),服务器引发 `GmailAuthError` 并附上“重新运行 `gmail-mcp-auth add`”的消息,而不是崩溃。
**测试与发布 - 7 天的陷阱。** 这是通常的“它在几天后就停止工作了”的惊喜:
- 在 OAuth 同意屏幕处于 **测试** 模式时,只有列出的 **测试用户** 可以授权,发行给 **未经验证** 应用程序的刷新令牌 **7 天后过期** — 您需要每周重新运行 `gmail-mcp-auth add`。
- **发布** 应用程序(同意屏幕 → *发布应用程序*)使刷新令牌长期有效。Google 会警告它是“未经验证的” — 对于您不分发的个人工具来说,这是预期且可以的。对于长期使用,请发布。[SETUP.md](docs/SETUP.md) 有确切的点击。
### 无头授权路径
目标通常是无头服务器(无桌面,无浏览器),但 OAuth 同意必须在浏览器中发生。流程桥接了这一点:
- **`open_browser=False`** — CLI 打印同意 URL 而不是启动浏览器。您在自己的笔记本电脑上打开它,登录到您要添加的账户。
- **固定环回端口** — 批准后,Google 重定向到 `http://localhost:<port>/`。该“localhost”是服务器的环回端口,CLI 在此处监听。端口是固定的(默认 `8765`,`GMAIL_MCP_OAUTH_PORT`)因此您可以确定性地转发它。
- **SSH 端口转发** — 在您的笔记本电脑和服务器的环回地址之间建立桥梁:
```bash
ssh -L 8765:localhost:8765 you@your-server
```
现在,当重定向命中笔记本电脑上的 `localhost:8765` 时,SSH 将其隧道传输到服务器,CLI 捕获代码并完成交换。
## 安全模型
收件箱里充满了其他人撰写的文本,因此这里是 prompt 注入的天然之处。标准框架是 **致命三要素** —— 当代理拥有以下三要素时,注入是危险的:
```mermaid
flowchart LR
A[私有数据<br/>您的邮箱] --- C{注入<br/>风险}
B[不受信任的内容<br/>您收到的任何电子邮件] --- C
D[出口通道<br/>发送数据的方式] --- C
C -.->|仅限草稿移除明显的出口通道| D
style D stroke-dasharray: 5 5
```
邮件阅读器天生具有前两个要素。几个选择使得第三个要素的风险较低:
- **草稿代替发送。** `create_draft` 是发送的上限 —— 没有发送工具,也没有 `gmail.send` 范围。草稿位于您的草稿文件夹中,直到您自己发送,因此嵌入在电子邮件中的指令不能使代理将您的数据发送到任何地方。合理的默认值,如果需要发送,可以轻松更改。
- **电子邮件内容被标记为不受信任。** 工具返回的消息文本被单个助手(`gmail.py`中的 `wrap_untrusted`)用 `⟦UNTRUSTED EMAIL CONTENT⟧` 分隔符包裹,ID 保存在 **外部**,因此工具链仍然有效。读取工具还在其描述中注明内容是数据,而不是指令。多消息响应(搜索结果、线程、跨帐户扫描)在整个内容块周围发出一次隔离 **——** 而不是每个消息一次 —— 并将每个正文键入一个受信任的 `#N` ID 清单,该清单位于隔离之外。这样既减少了分隔符令牌开销,又将实际 ID 仅保存在受信任区域,因此攻击者无法将伪造的 ID 偷运到代理视为权威的地方。
**已知限制。** 这仅适用于 **此** 服务器的界面。如果同一个代理会话还具有可以访问开放互联网的工具(网络抓取、HTTP),那么这是一个单独的出口路径,`gmail-mcp` 无法解决 —— 将其与任意出口工具配对会在其他地方重新打开三要素。请有意选择哪些工具共享一个会话。
另外两点说明:没有实施审计日志(故意超出范围),也没有硬编码秘密 —— `client_id`/`client_secret` 来自您下载的 `client_secret.json`,令牌仅存储在您的本地 SQLite 存储中。
---
## 安装
需要 Python 3.12+。PyPI 发行版为 **`multi-account-gmail-mcp`** (bare `gmail-mcp` 名称被占用);它安装了 `gmail-mcp` 和 `gmail-mcp-auth` 命令。
```bash
# 从 PyPI
pip install multi-account-gmail-mcp
# 或者,为了全局获取命令:
uv tool install multi-account-gmail-mcp # 或者:pipx install multi-account-gmail-mcp
# 或者,不安装:
uvx multi-account-gmail-mcp
```
从源代码(用于开发):
```bash
git clone https://github.com/cunicopia-dev/gmail-mcp.git
cd gmail-mcp
python -m venv .venv && source .venv/bin/activate
pip install -e . # 添加 ".[dev]" 以获取 ruff + pytest
```
这安装了两个控制台脚本: **`gmail-mcp`** (stdio 服务器)和 **`gmail-mcp-auth`** (帐户授权 CLI)。
---
## 快速入门
您需要一个 Google "桌面应用程序" OAuth 客户端(`client_secret.json`)和每个帐户的授权。完整的点击步骤 —— 创建 Google Cloud 项目、启用 Gmail API、发布同意屏幕和无头 SSH 转发步骤 —— 在 **[docs/SETUP.md](docs/SETUP.md)** 中。简短版本:
```bash
# 1. 将您下载的 OAuth 客户端放在此处:
mkdir -p ~/.gmail-mcp && mv ~/Downloads/client_secret_*.json ~/.gmail-mcp/client_secret.json
# 2. 授权帐户(打印一个 URL 以在浏览器中打开;请重复每个帐户)。
# 在无头服务器上,请先使用 -L 8765:localhost:8765 进行 SSH 登录。
gmail-mcp-auth add
# 3. 确认已授权的内容。
gmail-mcp-auth list
# 4. 将您的 MCP 客户端指向 `gmail-mcp` 命令(见下文)。
```
稍后使用 `gmail-mcp-auth remove you@gmail.com` 删除帐户。
---
## 配置
所有配置项均为可选 —— 默认情况下 `~/.gmail-mcp/` 下的配置项是合理的。
| 变量 | 默认值 | 用途 |
| --- | --- | --- |
| `GMAIL_MCP_DB` | `~/.gmail-mcp/tokens.db` | SQLite 令牌存储路径。 |
| `GMAIL_MCP_CLIENT_SECRET` | `~/.gmail-mcp/client_secret.json` | 下载的 Google OAuth 客户端。 |
| `GMAIL_MCP_OAUTH_PORT` | `8765` | 授权流程的固定环回端口(在无头框上通过 SSH 转发此端口)。 |
| `GMAIL_MCP_MAX_BODY_CHARS` | `500` | `read_message`/`read_thread` 的默认每消息正文限制。默认限制较严格,以便默认情况下读取内容便宜;`0`(或负数)表示无限制,并且 `max_body_chars` 参数可覆盖此限制。 |
---
## 注册到 MCP 客户端
服务器使用 stdio。请将客户端的 `mcpServers` 配置指向 `gmail-mcp` 命令:
```json
{
"mcpServers": {
"gmail": {
"command": "/path/to/gmail-mcp/.venv/bin/gmail-mcp"
}
}
}
```
如果 `gmail-mcp` 在 `PATH` 中,则 `"command": "gmail-mcp"` 就足够了。如有需要,请显式覆盖路径(某些客户端不扩展 `~`):
```json
{
"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"
}
}
}
}
```
---
## 开发
```bash
pip install -e ".[dev]"
ruff check .
pytest # 48 个测试,无网络 —— Gmail 客户端被模拟
```
测试涵盖了纯层 —— MIME 解析/解码、标签名称→ID 解析、不受信任的内容包装、输出格式化和令牌存储 CRUD 操作,均针对临时 SQLite 数据库。
---
## 项目布局
```
src/gmail_mcp/
server.py MCP 工具定义 + 分派 + 每帐户路由
gmail.py Gmail 服务构建、令牌刷新/持久化、MIME 解析/格式化、
wrap_untrusted()、标签解析、MIME 消息构建
store.py TokenStore — sqlite3 帐户表 CRUD 操作
auth.py gmail-mcp-auth CLI:添加 / 列出 / 删除(环回 OAuth)
config.py SCOPES + 环境变量可覆盖的路径
docs/
SETUP.md 分步 Google Cloud + 帐户授权
tests/ 存储 / Gmail / 服务器,Gmail 客户端被模拟
```
---
## 许可证
MIT —— 请参阅 [LICENSE](LICENSE)。
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器