Gmail MCP Gateway
Gmail MCP 网关
一个 MCP 服务器,让 AI 代理能够完整读取和管理多个 Gmail 账户——并且无法发送、丢弃或删除邮件。
Gmail MCP Gateway
ALLOWED FORBIDDEN
─────── ─────────
Search Send
Read messages Send draft
Read threads Trash
Read attachments Delete
Create drafts Mark spam
Edit drafts Gmail settings
Archive Forwarding rules
Read / unread Arbitrary API calls
Labels该保证通过应用程序代码强制执行,而非通过向客户端下达指令实现。即使 MCP 客户端存在缺陷、被攻破或遭受提示注入,也无法通过此网关发送电子邮件,因为没有任何代码路径允许这样做。
目录
Related MCP server: Gmail MCP Server
快速开始
需要 Python 3.11+。五步,大约十分钟,大部分时间在 Google 控制台内。
1. 安装
git clone <this-repo> gmail-mcp-gateway
cd gmail-mcp-gateway
uv sync # or: python -m venv .venv && .venv/bin/pip install -e .
.venv/bin/gmail-mcp-gateway --version可选:将其放入 PATH 中,以便下面的示例更自然:
export PATH="$PWD/.venv/bin:$PATH"2. 创建 Google OAuth 客户端
一次性操作,免费,并且你之后添加的每个账户都可以共享。
在 https://console.cloud.google.com/ 创建一个项目。
APIs & Services → Library → 启用 Gmail API。
APIs & Services → OAuth consent screen → External,填写必填字段,在 Test users 下添加你自己的 Google 账户。
发布应用(如果你仍然是唯一用户,则无需验证审核)。跳过此步骤会使应用保持在“测试”状态,Google 会在 7 天 后过期刷新令牌,你将需要每周重新授权。
Credentials → Create credentials → OAuth client ID → Desktop app → 下载 JSON。
这里不需要选择作用域。网关在授权时仅请求所需权限,并拒绝请求 Gmail 之外的任何内容。
3. 安装 OAuth 客户端
install -Dm600 ~/Downloads/client_secret_*.json \
~/.local/share/gmail-mcp-gateway/secrets/oauth_client.json这是你唯一需要手动放置的文件。加密密钥会在首次运行时自动生成。
4. 授权账户
gmail-mcp-gateway accounts add personal浏览器会打开;批准请求的权限,勾选 所有 复选框(如果权限被拒绝,网关会大声报错而不是部分工作)。刷新令牌会加密存储,之后网关可以无人值守运行。
可以添加任意数量的账户——每个账户都有自己的授权、刷新令牌、加密密钥、速率限制桶和审计轨迹:
gmail-mcp-gateway accounts add work
gmail-mcp-gateway accounts add newsletters --read-only # Google itself refuses writes5. 验证
gmail-mcp-gateway health # exit 0 = ready, 2 = something is wrong[ok ] directories config=/home/you/.config/gmail-mcp-gateway ...
[ok ] database /home/you/.local/share/gmail-mcp-gateway/gateway.db
[ok ] master_key loaded
[ok ] oauth_client configured
[ok ] accounts 1/1 authorized
gmail-mcp-gateway 1.0.0: healthy然后,将你的 MCP 客户端指向它——参见 连接 MCP 客户端——或者先进行测试:
uv run python scripts/try-it.py --account personal环境变量
对于普通的本地安装,你不需要设置任何环境变量。 上面的快速开始没有设置任何环境变量。默认配置位于 ~/.config,数据和密钥位于 ~/.local/share,加密密钥会自动生成。
这些变量适用于容器、systemd 单元和密钥管理器等场景——在这些地方,磁盘文件不是合适的机制。
变量 | 必需? | 默认值 | 用途 |
| 否¹ | — | Google OAuth 客户端 ID |
| 否¹ | — | Google OAuth 客户端密钥 |
| 否² | 自动生成 | base64 编码的 32 字节凭据加密密钥 |
| 否 |
|
|
| 否 |
|
|
| 否 |
| 密钥、OAuth 客户端、凭据 |
| 否 |
| HTTP 绑定地址 |
| 否 |
| HTTP 绑定端口 |
| 否 |
| 通过配置启用 HTTP 传输 |
| 否 |
| 允许非回环绑定 |
| 否 |
|
|
¹ 替代 secrets/oauth_client.json。提供文件 或 这对变量。
² 未设置时,网关会在首次运行时创建 secrets/master.key(模式 0600)。
环境变量覆盖 config.toml,而 config.toml 覆盖默认值。
生成每个值
OAuth 客户端 ID 和密钥 —— 来自快速开始步骤 2 中下载的 JSON。如果要用环境变量而不是文件:
jq -r '.installed.client_id' ~/Downloads/client_secret_*.json
jq -r '.installed.client_secret' ~/Downloads/client_secret_*.json主密钥 —— 32 个随机字节,base64 编码:
openssl rand -base64 32
# or, without openssl:
python3 -c "import base64,secrets; print(base64.b64encode(secrets.token_bytes(32)).decode())"此密钥用于解密你存储的刷新令牌。如果在添加账户后更改密钥,凭据将变得不可读,每个账户都需要执行
accounts reauth。请将密钥备份到数据目录的备份位置。
网关承载令牌 —— 不是环境变量。这是 MCP 客户端通过 HTTP 传输发送的内容,与任何 Google 凭据无关。CLI 生成它,并仅存储 SHA-256 哈希:
gmail-mcp-gateway token create my-agent明文令牌只打印一次,并放入 客户端 的配置中。
使用 env 文件
网关不会自动读取 .env —— 安全工具不应悄无声息地从启动目录吸收密钥。复制 .env.example,其中记录了每个变量,并显式加载:
cp .env.example .env # already covered by .gitignore
$EDITOR .env
set -a && source .env && set +a
gmail-mcp-gateway healthsystemd 使用 EnvironmentFile=;Docker Compose 使用 env_file:。
运行方式
stdio —— 通常的选择
客户端将网关作为子进程启动,并通过管道进行通信。没有端口,没有令牌,没有网络暴露。Google 凭据始终保留在网关进程内;客户端仅看到工具调用。
gmail-mcp-gateway serve --transport stdio手动运行时,它看起来会挂起——这是正常的,因为它正在等待 stdin 上的 JSON-RPC。通常你的 MCP 客户端会为你启动它。
可流式 HTTP —— 独立服务
适用于长期运行的服务,或无法生成进程的客户端。
gmail-mcp-gateway token create my-agent # once; save the printed token
gmail-mcp-gateway serve --transport http --host 127.0.0.1 --port 8765端点绑定到回环地址,需要承载令牌,并启用了 DNS 重新绑定保护。GET /healthz 无需认证,仅报告存活状态。
绑定非回环地址需要 GMAIL_MCP_ALLOW_REMOTE_BIND=true,即便如此,互联网可路由地址也会被拒绝。对于远程客户端,请使用隧道:
ssh -L 8765:127.0.0.1:8765 gateway-hostsystemd
deploy/gmail-mcp-gateway.service 以专用系统用户身份运行,在强化沙箱中——ProtectSystem=strict,空的 CapabilityBoundingSet,seccomp 过滤器,NoExecPaths 覆盖数据目录。安装步骤位于单元文件的头部。在启动之前,以服务用户身份以交互方式一次性授权账户。
Docker
deploy/Dockerfile 和 deploy/docker-compose.yml 以非 root 和只读方式运行,放弃所有能力,仅将端口发布到回环地址。镜像中不包含凭据;它们位于 /secrets 卷中。
docker compose -f deploy/docker-compose.yml up -d一次性授权序列——放置 OAuth 客户端、运行授权流程(发布重定向端口)、生成令牌——在 compose 文件的头部注释中有说明。
连接 MCP 客户端
stdio
{
"mcpServers": {
"gmail": {
"command": "/absolute/path/to/gmail-mcp-gateway/.venv/bin/gmail-mcp-gateway",
"args": ["serve", "--transport", "stdio"]
}
}
}Claude Code:
claude mcp add gmail -- /absolute/path/to/.venv/bin/gmail-mcp-gateway serve --transport stdioHTTP
{
"mcpServers": {
"gmail": {
"type": "http",
"url": "http://127.0.0.1:8765/mcp",
"headers": { "Authorization": "Bearer <token from `token create`>" }
}
}
}客户端永远不会挂载或以其他方式访问 Google 凭据文件。在 stdio 模式下,客户端与管道通信;在 HTTP 模式下,它持有一个与任何 Google 凭据无关的网关令牌。
工具参考
每个工具都接受一个 account 别名——没有默认账户。可变操作工具可接受一个可选的 client_request_id 用于幂等性:使用相同的 ID 和参数重复调用将返回第一次的结果,而不是执行两次操作。
工具 | 作用 |
| 别名、地址、状态、授予的权限。不含凭据。 |
| 每个账户的实时授权检查,以及邮箱总数。 |
| Gmail 搜索语法; |
| 一条消息:发件人、收件人、抄送、密送、主题、时间戳、标签、已读状态、正文、附件清单。 |
| 按顺序的整个对话,包含参与者。 |
| 附件清单。不下载任何内容。 |
| 获取字节:小附件内联 base64,否则写入网关自己的目录。 |
| 所有标签及其计数,以及网关是否会修改每个标签。 |
| 按 ID 或名称应用标签。拒绝 |
| 按 ID 或名称移除标签。拒绝 |
| 移除 |
| 移除 |
| 添加 |
| 已保存的草稿,包含收件人、主题、片段。 |
| 完整的一封草稿。 |
| 新建纯文本草稿。保存,从不发送。 |
| 在现有线程中创建回复草稿,包含正确的 |
| 编辑草稿;省略的字段保留其值,线程信息保持不变。 |
变更操作适用于单个消息或线程,也适用于批处理(默认上限 100 个 ID)。标签可以以 ID(Label_7)或显示名称(Receipts)形式提供。
在关键地方,读写处理不对称:gmail_search 可以愉快地过滤 TRASH 或设置 include_spam_trash,因为检查已存在的内容是读取操作。而应用这些标签会被拒绝,因为那会将邮件移入垃圾桶或报告为垃圾邮件。
错误
失败以 MCP 工具错误形式返回,带有 isError: true 和结构化负载,同时出现在文本块和结构化内容中:
{"error": {
"code": "forbidden_label",
"message": "refusing to add label 'TRASH': moving messages to Trash is a forbidden capability of this gateway",
"retryable": false
}}错误码包括 invalid_input、unknown_account、not_found、too_large、batch_too_large、rate_limited、forbidden_operation、forbidden_label、account_read_only、needs_reauth、upstream_rate_limited、upstream_unavailable、network_error、timeout 和 internal_error。内部异常记录在服务器端,并报告为裸露的 internal_error——客户端永远不会收到回溯或内部路径。
管理
gmail-mcp-gateway accounts list
gmail-mcp-gateway accounts status # live Gmail check per account
gmail-mcp-gateway accounts auth <alias>
gmail-mcp-gateway accounts reauth <alias> # after a revoked or expired grant
gmail-mcp-gateway accounts remove <alias> --yes # revokes at Google, deletes locally
gmail-mcp-gateway token create <name>
gmail-mcp-gateway token list
gmail-mcp-gateway token revoke <name>
gmail-mcp-gateway audit --limit 50 # recent state-changing operations
gmail-mcp-gateway audit --account work --since-hours 24
gmail-mcp-gateway audit --outcome denied --json
gmail-mcp-gateway prune # expired audit rows, dedup keys, attachments
gmail-mcp-gateway health --json在无头主机上,通过转发重定向端口进行授权:
# on the server
gmail-mcp-gateway accounts add work --no-browser --port 8899
# on your laptop
ssh -L 8899:127.0.0.1:8899 server
# then open the printed URL locally审计日志记录账户、时间戳、操作、受影响ID、结果、错误码、持续时间和调用主体——包括成功、失败和拒绝。它绝不记录令牌、消息体、主题或附件内容。管理仅限CLI:被入侵的MCP客户端无法添加账户、触发同意流程、生成令牌或读取审计日志。
目录布局
配置、数据和秘密相互分离,且各自可覆盖,因此每个部分可以有不同的后端存储:
角色 | 变量 | 默认值 | 内容 |
配置 |
|
|
|
数据 |
|
|
|
秘密 |
|
|
|
config.toml 是可选的;所有键及其默认值请参见
deploy/config.example.toml —— 包括批量上限、页面大小、正文和附件预算、速率限制、重试策略以及幂等窗口。
边界如何强制执行
四个独立层。每一层单独即可阻止发送;只有全部四层都失效,消息才会离开。
1. 工具表面。 共有十八个工具。没有 gmail_send、没有 gmail_trash、没有 gmail_raw_request,也没有任何接受URL、路径、HTTP方法或端点名称的工具。通用Gmail代理不是客户端可以达到的,因为它根本不存在。
mcpsrv/server.py
2. 端点白名单。 每个对Gmail的HTTP请求必须指定一个十四个 Endpoint 常量之一。users.messages.send、users.drafts.send、users.messages.trash、users.messages.delete 以及 users.settings 下的所有内容均被排除。路径参数使用严格ID模式验证,并以空安全集进行百分号编码,因此任何值都无法引入/从而到达不同端点。在请求发出前,黑名单会再次检查解析后的方法和路径,独立于其构建方式。DELETE 和 PATCH 完全无法发出。
gmail/allowlist.py
3. 标签策略。 这关闭了白名单留下的后门。users.messages.modify 是允许的——它是归档和已读状态的工作方式——但Gmail将 TRASH 和 SPAM 视为普通标签,因此应用其中一个标签会删除邮件或将其报告为垃圾邮件。变异中的每个标签ID都会进行双向、不区分大小写的检查,并且在传输前还会再次检查组装后的请求体。
gmail/labels.py
4. OAuth 范围。 账户使用 gmail.modify 授权,别无其他。该范围无法永久删除邮件(messages.delete 需要 https://mail.google.com/),也无法触碰任何Gmail设置,因此转发规则、过滤器、POP/IMAP配置和永久删除在Google的授权层即被阻止,而不仅仅是在此处被拦截。Google没有发布任何允许创建草稿但不允许发送的范围,因此发送由第1-2层阻止。使用 --read-only 添加的账户获得 gmail.readonly,Google本身会拒绝所有写入操作。
安全模型
邮件内容不可信。 正文、主题、发件人名称和附件文件名由第三方编写,可能包含针对读取邮件的模型的指令。网关将每个读取结果标记为 content_is_untrusted: true,并且服务器指令告诉客户端将邮件视为数据而非指令。更有用的是,注入指令可能请求的能力并不存在。
HTML 从不执行,也从不作为标记返回。 <script>、<style>、<iframe> 及类似元素及其内容会被丢弃;所有其他标签被剥离。结果为纯文本。
不可见 Unicode 被剥离。 零宽字符、双向覆盖和Unicode标签字符可让攻击者向人类展示一个内容,而LLM读取另一个内容。这些字符被移除,移除数量记录为 removed_hidden_characters。
附件被存储,但从不打开。 网关不解析、渲染或执行附件内容。客户端可以建议文件名,但绝不能建议路径:目标始终为 <attachments_dir>/<account>/<message_id>/<sanitized-name>,经过解析并重新检查是否包含在内,以 O_NOFOLLOW 模式 0600 写入。
凭据永远不会到达客户端。 刷新令牌、访问令牌和OAuth客户端密钥仅存在于网关进程内部。每个账户的凭据使用 AES-256-GCM 在按账户派生的密钥(HKDF-SHA256(master, "…account:<id>"))下加密,账户ID作为关联数据——因此一个账户的密钥无法打开另一个账户的凭据,并且移动到不同账户的凭据文件将解密失败。文件权限为 0600,位于 0700 目录中;网关拒绝读取组或世界可读的密钥。
诚实的作用域: 静态加密保护备份、散落副本和磁盘镜像。它无法防御已经以网关用户身份执行代码的攻击者——该攻击者可以读取主密钥。文件系统权限仍然是主要边界。
日志不会泄露秘密。 每条日志记录都会经过一个过滤重写,任何看起来像Google访问或刷新令牌、客户端密钥、Bearer头、JWT或凭据名称字段的内容——在消息、参数和异常文本中——都会被重写。在 stdio 下,日志发送到 stderr,因为 stdout 是 MCP 线路。
输入经过验证。 草稿收件人必须是符合严格模式的裸地址;标头值中的任何 CR、LF 或 NUL 都被视为标头注入尝试而被拒绝。草稿从类型化字段组装——网关从不接受来自客户端的原始 RFC 5322。批量、页面大小、正文长度、附件大小和收件人数均有上限,并且每个账户的令牌桶在失败时快速返回带有 retry_after_seconds 提示,而不是排队。
这不能防范什么
能够以网关用户身份执行代码的操作员。
客户端合法使用允许的功能但使用不当——例如批量归档或编写误导性草稿。归档和标记是可逆且可审计的;草稿仍然需要人工发送。
Google 端被入侵或恶意的 OAuth 客户端配置。
如果在没有 TLS 的情况下暴露 HTTP 传输,则可能被流量拦截。请将其保留在回环接口上,或在其前面放置一个 TLS 终止代理。
可靠性
令牌刷新:自动,每个账户有锁,因此并发调用只会刷新一次。
401触发恰好一次刷新和重试。刷新失败:
invalid_grant将账户标记为needs_reauth,并返回结构化错误,指出修复该问题的 CLI 命令。速率限制和 5xx:指数退避,完全抖动,遵循
Retry-After,最多max_attempts次。网络故障和超时:重试,然后报告为
network_error或timeout,不包含内部细节。分页:
next_page_token返回给客户端,因此服务器中不保存游标状态。重复请求:
client_request_id在 24 小时内抑制重复请求。并发重复请求在进程内序列化;使用不同参数重用 ID 是错误,而不是静默返回错误答案。背压:Gmail 调用共享可配置的并发信号量,每个账户有独立的令牌桶。因此,大型搜索或繁忙的账户不会产生无限制的上游并发。
扩展和部署拓扑
对于给定的数据和秘密目录,运行一个网关进程。SQLite 状态、凭据文件、令牌刷新锁和进行中的幂等协调有意保持本地;将多个副本指向同一卷不会提供安全的主主操作。
对于更大的安装,将账户分片到独立的网关实例中,每个实例有自己的配置、数据、秘密、Bearer 令牌和回环端口。这使故障、速率限制、审计轨迹和凭据保持隔离,同时允许每个实例服务并发客户端。仅在观察 Gmail 配额使用和主机容量后增加 limits.max_concurrency;默认值 8 是保守的。如果客户端需要一个共享的网络地址,在其前面放置一个 TLS 认证的路由层,并将每个账户别名路由到其自己的实例。
同一账户的主主副本需要将 SQLite 和本地凭据/幂等状态替换为协调的外部存储。这超出了当前网关的安全模型;不要仅仅通过增加工作节点或共享卷来扩展它。
测试
uv sync --all-extras
uv run pytest -q # 334 tests, no Google account needed
uv run pytest tests/test_security_boundary.py -v # just the guaranteetest_security_boundary.py 通过模拟 Gmail 驱动每个支持的操作,如果请求了禁止的 URL 则失败,然后尝试通过每个可用路由进行删除、垃圾邮件和发送。
一旦账户授权,用真实邮箱进行测试。该脚本像 MCP 客户端一样通过 stdio 连接,执行只读遍历,然后确认禁止的操作被拒绝:
uv run python scripts/try-it.py --account personal
uv run python scripts/try-it.py --account personal --draft # also drafts a reply
uv run python scripts/try-it.py --account personal --archive # archive round trip除非传递了变异标志,否则为只读,并且它所做的每个变异都是可逆的。它创建的草稿必须由您删除——网关无法删除。
要交互式点击:
npx @modelcontextprotocol/inspector .venv/bin/gmail-mcp-gateway serve --transport stdio布局
src/gmail_mcp_gateway/
├── mcpsrv/server.py the tool surface — the complete client-facing API
├── mcpsrv/http.py Streamable HTTP transport, bearer auth, bind safety
├── service.py the supported operations, and nothing else
├── gmail/allowlist.py the endpoint allowlist ← security boundary
├── gmail/labels.py label policy (blocks TRASH/SPAM) ← security boundary
├── gmail/client.py the only code that talks to Gmail
├── gmail/parse.py MIME → structured data, sanitization
├── gmail/compose.py draft assembly from typed fields
├── security/ validation, rate limiting, path confinement
├── auth/oauth.py OAuth 2.0 + PKCE, refresh, revoke
├── accounts.py account registry
├── crypto.py envelope encryption for credentials
├── audit.py audit log
└── cli.py administration添加一个支持的操作意味着:allowlist.py 中的 Endpoint,service.py 中的方法,mcpsrv/server.py 中的工具,EXPOSED_TOOLS 中的条目,以及测试。EXPOSED_TOOLS 和 FORBIDDEN_TOOLS 根据运行中的服务器进行断言,因此添加一个未声明的工具——或添加一个禁止的工具——会导致测试失败。保持网关专注于 Gmail;不同的 Google 产品应属于单独的 MCP 服务,而不是在此处使用更宽的范围。
故障排除
no OAuth client configured —— 快速入门第 3 步。放置 secrets/oauth_client.json(模式 0600)或设置 GMAIL_MCP_OAUTH_CLIENT_ID 和 GMAIL_MCP_OAUTH_CLIENT_SECRET。
Google did not return a refresh token —— 您之前已授权此应用。在 https://myaccount.google.com/permissions 移除其访问权限,然后重新运行 accounts auth <alias>。
consent screen did not grant every required permission —— 权限框未勾选。重新运行授权并保持所有框勾选。网关在此处故意失败,而不是留下一个半功能账户。
账户每周进入 needs_reauth 状态 —— OAuth 应用仍处于“测试”状态,Google 会在 7 天后过期刷新令牌。请发布它(快速入门第 2.4 步)。
stored credential failed authentication —— GMAIL_MCP_MASTER_KEY 已更改,或密钥文件已被替换。恢复原始密钥,或对每个账户运行 accounts reauth <alias>。
<file> is accessible to other users —— 网关拒绝读取组或世界可读的秘密。对该文件执行 chmod 600。
refusing to bind …: it is not a loopback address —— 有意如此。绑定 127.0.0.1 并使用 SSH 隧道,或者如果确实是受信任的私有接口,则设置 GMAIL_MCP_ALLOW_REMOTE_BIND=true。互联网可路由地址无论何种情况都会被拒绝。
serve --transport stdio 看起来挂起 —— 正确;它在等待 stdin 上的 JSON-RPC。让您的 MCP 客户端启动它,或使用 scripts/try-it.py。
有人更改了邮箱,我想知道具体发生了什么——
gmail-mcp-gateway audit --limit 50。每一次状态变更都记录在那里,拒绝操作也包含在内。
许可证
MIT
This server cannot be deployed
Maintenance
Related MCP Connectors
Email infrastructure for AI agents — send, receive, search, and reply to email over MCP.
Email inboxes for AI agents: send, receive, reply, search, and manage threaded email over MCP.
A MCP server for Gmail that lets you search, read, and draft emails and replies.
Real email inboxes for AI agents: register, receive, read and send mail over MCP. Free to receive.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceRead-only MCP server for IMAP email access, enabling AI agents to read, search, and monitor email without sending or deleting messages.13 npmMIT
- FlicenseNot gradedqualityDmaintenanceEnables AI agents to interact with Gmail through a standardized MCP server interface, allowing for natural language email management and automation.-
- AlicenseAqualityDmaintenanceMCP server that gives AI agents the ability to send, read, and search email via Gmail.441 npmMIT
- AlicenseAqualityDmaintenanceMCP server that gives an AI assistant simultaneous access to multiple Gmail accounts with per-account read-only, draft, or send tiers, two-phase write confirmation, and safeguards for handling untrusted email content.109 npm1MIT