mailwarden
mailwarden
一个可靠的、原生的 Gmail MCP 服务器——为 AI 助手提供完整的邮箱分类功能,并具备其他 Gmail MCP 服务器所没有的特性:邮箱端延迟处理。
亮点
延迟处理——唯一一个在 Gmail MCP 服务器中实现邮箱端延迟处理的功能。 现在归档一个会话,让它 在指定日期重新出现在收件箱中。基于带日期的标签和扫描操作构建,因此可以从任何客户端使用, 在 Gmail 本身中可见,并且重启后仍然有效。(其他服务器提供的“延迟处理”只是一个本地提醒列表—— 邮件从未离开或重新进入收件箱。)
你可以信赖的搜索。 Gmail 的
threads.list——任何线程搜索都会调用的接口——可能 从过时的线程级读取状态回答is:unread:在一个真实的邮箱中测量,它返回的线程中有 86% 根本没有未读消息;在第二个邮箱中,没有任何偏差。 不查看的话你无法知道自己处于哪个邮箱,因此search会针对每个命中的结果,根据其 实时标签重新验证。通过pageToken/nextPageToken进行分页。可扩展的批量操作。
bulk_modify以每次 API 请求 1000 条消息的速度归档/标记匹配查询的所有内容—— 支持逐块的部分成功报告,而不是全有或全无。延迟处理扫描也使用相同的批量路径。结构化输出。 每个工具都声明了一个
outputSchema,并返回经过验证的structuredContent以及带围栏的 JSON 文本——客户端无需猜测解析。攻击面小。 没有发送工具(提示注入的邮件没有外泄路径), 可选的只读模式,没有遥测,默认没有开放端口,符号链接安全的下载防护, 注入防护的输出。一个刻意的例外:
unsubscribe/bulk_unsubscribe(管理 层级)会联系邮件自身头部中命名的退订端点——这是 mailwarden 唯一会访问的非 Google 主机, 而read层级的部署根本不会发出任何出站请求。详情见 安全与隐私 和 退订。对真实世界的邮件处理正确。 RFC 2047 头部已解码(
=?UTF-8?B?…?=→ 可读文本), 正文按其声明的字符集解码(ISO-8859-1/Shift_JIS 邮件不会出现乱码), 429/5xx 错误使用指数退避重试。
Related MCP server: Gmail MCP
为什么
同步或缓存邮箱的连接器可能滞后于邮箱本身——甚至 Gmail 自己的搜索索引有时也不准确(见下文)。mailwarden 直接与实时 Gmail API 通信(没有缓存快照),并重新验证索引返回的内容,因此你看到的就是实际存在的。它是一个通用的 Gmail 能力层——将你自己的规则/逻辑保留在 AI 客户端中,而不是服务器中。
search 比原始 API 更进一步:Gmail 的 threads.list 索引可能从该状态的过时副本回答读取状态运算符,因此 is:unread 会返回你几周前就已经读完的线程——在一个被测量的邮箱中,返回结果中的绝大多数都是如此。由于每个命中结果都是实时获取的,search 会针对每个线程的真实标签重新检查明确的谓词(is:unread/is:read/is:starred/in:inbox/category:…,支持否定),并丢弃索引的误报。
与其他 Gmail MCP 服务器比较
大多数 Gmail MCP 服务器覆盖相同的读取/标记/发送功能。两个能力仍然是 mailwarden 独有的(邮箱端延迟处理、搜索重新验证),而一个刻意的省略是安全特性,而非功能缺口。Google 自己的服务器也比看起来更窄:仅草稿,没有垃圾箱、过滤器或退订功能。
能力 | mailwarden | ||||
邮箱端延迟处理 — 立即归档,在指定日期/时间或预设时间重新出现在收件箱中 | ✅ | — | — | — | — |
搜索结果重新验证 — 根据实时标签丢弃线程索引的误报 | ✅ | — | — | — | — |
基于查询的批量操作 — 对搜索返回的每个线程执行一个操作 | ✅ 1000/请求,部分成功 | — | ⚠️ 按显式 ID 批量 | — | ⚠️ 按显式 ID 批量 |
退订 — 按发件人概览 + RFC 8058 一键退订,无需发送权限 | ✅ | — | ⚠️ 仅显示头部,无操作 | — | — |
收件箱分类概览 — 一次调用即可了解待处理邮件的分类情况 | ✅ 发件人/标签/年龄 + 头部信号 | — | — | ✅ 启发式标志 + 统计信息 | — |
服务器端过滤器 — 无需助手参与即可持续分类的规则 | ✅ 绝不转发 | — | ✅ | — | ✅ |
无发送工具——设计如此 — 提示注入的邮件没有外泄路径 | ✅ 完全没有撰写功能 | ⚠️ 仅草稿 | ❌ 可发送 | ❌ 可发送 | ❌ 可发送 |
最小权限工具层级 — 根据你启用的工具派生 OAuth 范围 | ✅ | ⚠️ 范围拆分 | — | — | ⚠️ 反向:工具由已授予的范围控制 |
令牌静态加密(可选) | ✅ AES-256-GCM | 不适用(托管) | ✅ | — | — |
无供应商云——你自行运行服务器 | ✅ | ❌ Google 托管 | ✅ | ✅ | ✅ |
结构化输出 — 每个工具都声明一个 | ✅ | — | — | — | — |
截至 2026 年 8 月 16 日的快照,来自每个项目的公开文档和源代码;— = 未提供/未记录。各列是读者最可能接触到的服务器——Google 的第一方服务器,加上两个最大的社区服务器——以及 klodr,它最接近 mailwarden 自身的最小权限设计。发送能力被列为安全属性:mailwarden 缺乏此能力是故意的(见 安全与隐私)。最后一行问的是谁运营服务器,而不是它碰巧在哪里运行:自托管在这里是共同点,此表上的每个社区服务器都提供某种远程部署,除了 klodr(仅 stdio)——mailwarden 通过 --http,taylorwilsdon 通过可流式 HTTP 配合 OAuth 2.1,a-bonus 在 Cloud Run 上。在你自己的主机上运行它们之一不是云副本;在供应商的主机上运行它才是。
护城河不在于任何单一行——而是延迟处理 + 实时重新验证的结合:一个真正的收件箱工作流层,作用于邮箱的当前状态,而不是缓存快照。在其他方面迎头赶上的地方,上面已如实注明:静态加密(taylorwilsdon)、范围驱动的工具门控(klodr)、更丰富的每消息分类启发式方法(a-bonus),以及跨邮箱的批量整理(托管的 mcpemails.com,它也没有延迟处理功能)。它们都没有做到的是:基于一个查询执行操作,并在操作前检查邮箱的答案。
为什么重新验证很重要——一个具体案例
让助手*“归档那些已经跳过我的收件箱的未读促销邮件”*,它会使用明显的查询 category:updates is:unread -in:inbox。一个信任 Gmail 索引的服务器现在会归档你已经读过的线程——你从未打算触碰的邮件,在一次你难以轻易撤销的批量操作中消失。
经过测量,而非断言。 一个真实的邮箱(约 70,000 条消息),2026 年 8 月 15 日,只读:
查询 ( | 返回线程数 | 含未读消息 | 陈旧率 |
| 131 | 17 | 87% |
| 128 | 14 | 89% |
| 235 | 99 | 58% |
索引并没有忽略该谓词——同样的查询不带 is:unread 返回了 800+ 个线程,所以它确实被应用了。但它应用在一个尚未同步的线程级读取状态上:那些每条消息都已读的线程,在这里仍被计为未读。返回的一个线程仅带有一个标签 SENT。这并非花哨运算符组合的怪癖:三个查询中最简单的一个也显示了同样的问题——最低占比 (58%) 但绝对数量上最多的错误线程 (136 个)。
正是线程索引本身的问题。 同样的查询、同样的邮箱、同一分钟,通过 messages.list 而不是 threads.list 发出:19 条消息,无一陈旧。 所以这不是"Gmail 搜索不可靠"——而是线程的读取状态视图滞后,而单条消息的视图则没有。search 走的是 threads.list,这正是它为什么需要重新验证。
第二个邮箱,在同一天以相同方式测量,完全没有偏移——is:unread 的原始索引命中数为零,尽管它每天通过 API 被标记读取多次。所以这是某个邮箱的属性,而非所有 Gmail。它们的区别尚不明确:两者在邮件量(相差约三个数量级)和存续时间上不同,且第二个邮箱缺少一个更基本的条件——其中从未有线程在未读状态下被归档,而这是陈旧读取状态唯一可能出现的形式。所以它并非针对某种特定原因的反例;它只是一个不存在候选条件的邮箱。
这正是关键所在:服务器无法知道它面对的是哪种邮箱。 在没有偏移的地方,重新验证不花任何代价;而在有偏移的地方,它能救你——在上面的测量中,search 丢弃的每一个线程都是真正已读的,并且它没有丢弃任何真正未读的邮件。
而在批量工具中,重新验证并非免费。 search 重新验证是因为它本来就获取每个命中结果;bulk_modify(以及 create_filter 的 applyToExisting 扫描)处理的是数千条消息的量级,每个命中结果都去获取一次的成本截然不同。这些工具直接基于索引返回的结果操作——因此它们现在会报告 unverifiedPredicates,即你的查询中那些依赖索引断言的条件(+UNREAD、-INBOX 等)。空值意味着没有需要怀疑的内容。非空且结果需要精确的读取状态?先用 search 解析出集合,再对这些线程 ID 进行操作。dryRun 不能弥补这个差距:它只是重新读取同一个索引,所以它只能确认集合有多大,而无法确认它是否正确。
mailwarden 无论如何都会实时获取每个命中结果,因此 search 会针对每个线程的真实标签重新检查无歧义的谓词(is:unread、is:read、in:inbox、category:…,包括取反),并在任何工具看到它们之前丢弃索引的误报。然后批量操作仅在你要求的集合上运行。这就是基于 Gmail 索引 采取行动与基于邮箱当前实际状态采取行动之间的区别——这也是为什么 snooze/sweep 可以安全地交给助手处理:sweep 只会重新显示那些真正到期的暂停线程,并在运行时根据实时标签进行验证。
亲自看看——无需 Gmail 账户。 从仓库克隆(演示是一个仅限仓库的 验证脚本,不属于 npm 包):
git clone https://github.com/csitte/mailwarden && cd mailwarden
npm install && npm run build
node scripts/demo-reverify.mjs旁边还有一个脚本 node scripts/probe-reverify.mjs,它会在你的邮箱中(而非模拟邮箱中)测量同样的事情——只读,仅元数据(不获取主题、发件人或正文),打印计数和标签名称。上面的数字就是这样产生的,你也可以用它来检查你的邮箱是否有偏移。
演示驱动真实的 search() 针对一个故意让索引陈旧的模拟 Gmail API(其索引对 is:unread 查询返回一个已读线程,完全像 Gmail 一样)进行,并展示 mailwarden 丢弃误报。它断言了结果,因此如果行为回归,它会以非零退出。同样的案例由单元测试锁定在 test/gmail.test.ts("通过实时标签重新验证丢弃索引误报")。
工具 | 功能说明 |
| Gmail 查询语法 → 邮件线程摘要(发件人/主题/日期/标签/片段);已读/未读状态及类别谓词会对照每条结果的实时标签重新验证;通过 |
| 完整线程:标头、纯文本 + HTML 正文、附件元数据 |
| 所有标签(系统 + 用户) |
| 已连接账户的地址 + 总消息/线程数——在操作前确认哪个邮箱已接入 |
| 邮箱分片的结构化概览,用于决策:主要发件人(每个发件人附带其线程携带的信号)、标签和时效性分组、未读 + 附件计数,以及新闻通讯/自动化/日历邀请/回复地址不匹配的线程数量——而非原始线程列表 |
| 线程提供的退订选项( |
| 按发件人分组的邮箱分片:线程/未读计数、每个发件人出现的日期范围及其退订选项——每个发件人获取一次标头,不联系任何人。 |
| 创建用户标签(幂等;通过 |
| 按名称或 id 添加/移除标签—— |
| 对匹配查询的每条消息进行批量标签更改——每次 API 请求处理 1000 条消息,按块报告部分成功(线程 id 列表上限为 500, |
| 便捷封装 |
| 移至/恢复自垃圾箱 |
| 将附件保存到本地路径(从不覆盖——冲突时添加数字后缀) |
| 使用消息自身标头中的端点进行一键退订(RFC 8058)——唯一会联系非 Google 主机的工具(详情) |
| 对多个线程执行相同操作,顺序执行且每个发件人最多一次请求;按线程报告部分成功。 |
| 立即归档,在指定日期( |
| 取消暂停,立即返回收件箱 |
| 所有暂停的线程及其到期日期 |
| 唤醒暂停到期的线程(按需运行、通过 cron 或守护进程运行);批量处理,报告部分失败。 |
| 所有 Gmail 过滤器(条件 + 标签操作);显示现有过滤器上的任何 |
| 创建服务器端自动分类规则(仅限条件 → 标签操作;不支持转发——见下文)。可选择 |
| 按 id 删除过滤器 |
所有工具都声明了一个 outputSchema 并返回结构化内容(经过验证、机器可读)以及作为围栏文本的相同 JSON——客户端永远不需要解析散文。
暂停功能的工作原理(不存在 Gmail API 暂停功能——我们构建它)
snooze 移除 INBOX 并应用一个带日期的标签 MCP/Snoozed/<key>,其中 key 是 YYYY-MM-DD(全天到期)或 YYYY-MM-DDTHHMM(在该本地分钟到期)。until 参数接受一个显式日期、日期+时间(2026-06-20 9am、…T17:00),或一个在服务器端解析的预设——today、tomorrow、weekend(下周六)、next week(下周一)、一个工作日名称(monday–sunday,下一次出现)、in N days 或 in N hours——并且日期预设可以附带一个尾随时间(tomorrow 9am、monday 8:30),因此调用者永远不需要自己计算时刻。sweep_snoozed 查找到期的标签并将这些线程返回收件箱(标记为未读);定时暂停会在其分钟后的第一次扫描中唤醒,因此唤醒延迟等于您的扫描间隔。运行扫描:
按需(
sweep_snoozed工具),通过 cron:
mailwarden --sweep,或自动:设置
MAILWARDEN_AUTO_SWEEP=1(服务器运行时每小时扫描一次)。
过滤器(持久自动分类规则)
create_filter 设置一个 Gmail 服务器端规则:匹配条件的邮件会自动获得给定的标签操作——邮箱在没有助手介入的情况下持续自我分类。
条件:
from、to、subject、query(完整的 Gmail 搜索语法)、negatedQuery、hasAttachment、excludeChats以及size+sizeComparison(smaller/larger,一起给出)。至少需要一个。操作(仅标签):
addLabels/removeLabels,按名称或 ID(addLabels中的未知名称会自动创建,通过/嵌套)。常见配方:跳过收件箱 →removeLabels: ["INBOX"];自动标记为已读 →removeLabels: ["UNREAD"];自动移至垃圾箱 →addLabels: ["TRASH"];加星标 →addLabels: ["STARRED"];永不标记为垃圾邮件 →removeLabels: ["SPAM"];归档到标签下 →addLabels: ["Receipts"]。现有邮件: 过滤器仅对创建之后到达的邮件生效。传递
applyToExisting: true也会将相同的操作一次性应用于邮箱中已有的邮件——mailwarden 从条件构建一个 Gmail 搜索并执行批量修改(最多maxMessages,默认 1000;与bulk_modify相同的未验证索引注意事项,并且一次性过程排除垃圾邮件/垃圾箱)。这需要至少一个正面条件(from/to/subject/query/hasAttachment:true/size):仅排除规则(negatedQuery或hasAttachment:false)会被拒绝用于applyToExisting,因为它会匹配几乎整个邮箱——创建此类过滤器时不带该标志。结果在applied下返回(使用的query、matchedMessages/modifiedMessages/modifiedThreadCount计数、当匹配集达到maxMessages时的capped、每个块的failed,以及如果整个过程失败时的error字符串);当未设置applyToExisting时,它为null。过滤器首先被创建,因此部分或失败的后台过程在applied中报告,但不会引发异常——规则仍然有效。无转发——请参阅安全与隐私。
需要
gmail.settings.basic范围;如果您授权了旧版本,请重新运行--auth一次。在只读模式下不可用。
退订——唯一的外发请求
list_unsubscribe(只读层级)报告发送者提供的内容,不联系任何人。它读取最新确实携带 List-Unsubscribe 标头的邮件——一个回复到新闻邮件的线程位于末尾并且不显示任何内容,否则会读作“此列表无退订选项”。list_subscriptions(只读层级)在整片邮件上执行相同操作,按发送者分组,这样您可以看到谁在持续写信以及其中哪些实际上可以离开——每个发送者只获取一次标头,而不是每个线程。unsubscribe 和 bulk_unsubscribe(管理层级)对其执行操作——这是 mailwarden 唯一一次与非 Google 主机通信,因此规则很严格:
没有 URL 参数。 端点来自邮件本身的标头,而不是其他任何地方。URL 参数会让提示注入的邮件将工具变成信息泄露通道(查询字符串中的邮箱内容);标头无法携带模型选择的数据。
仅执行 RFC 8058 一键退订——发送者必须通过
List-Unsubscribe-Post选择加入。纯https:链接是为浏览器中的人类准备的,会被返回,而不是被获取。从不执行
mailto:退订。 它们需要发送邮件,而 mailwarden 无法做到。地址会被报告,以便您自己采取行动。固定请求,丢弃响应。 POST 主体始终是
List-Unsubscribe=One-Click,并且从不从任何内容派生;响应主体被取消读取。返回给模型的是状态码和实际调用的 URL——没有来自端点的内容,因此它无法用指令回复。(301/302/303 重定向作为 GET 被跟随,即根本没有主体。)每个发送者一个请求,顺序进行,在一个预算内。
bulk_unsubscribe接受线程 ID(从不查询——查询驱动的批量会在任何人查看之前为每个匹配的发送者发出一个请求)。来自已发送请求的发送者的线程会被报告为duplicateOf,并且不会产生第二个请求:来自一个列表的两个线程共享一个退订,重复调用只会两次确认您的地址。只有当请求实际到达端点时,发送者才会被记录,因此拒绝或连接断开仍为下一个线程留下自己的尝试——并且如果跳过的线程广告了不同的端点,原因会说明,因为一个发送者可以运行多个列表。每次调用限制为 25 个线程和 60 秒;预算未覆盖的部分会作为skippedOutOfTime返回,而不是静默地未完成。所有这些都无法撤销,这就是为什么存在这三个限制。SSRF 防护。 仅限 https,仅限默认端口,URL 中无凭据,并且每一跳——包括最多跟随 3 次的重定向——必须仅解析为全局可达的地址。检查将每个地址解析为字节并与 IANA 特殊用途注册表匹配,因此同一地址的每种拼写都得到相同的裁决(
::1和0:0:0:0:0:0:0:1一样);无法解析的地址会被拒绝。DNS 解析和所有跳共享一个 10 秒的预算。不防绑定劫持(fetch在连接时再次解析)——请参阅 SECURITY.md;跨越该间隙的是其响应从未被读取的盲目 POST。
在信任它之前,请用您自己的邮件检查它。 从仓库克隆(仅仓库,不在 npm 包中),在 npm run build 和 mailwarden --auth 之后:
node scripts/probe-unsubscribe.mjs --vet # category:promotions, 25 threads
node scripts/probe-unsubscribe.mjs "from:substack.com" --max 50 --vet它会打印每个真实的 List-Unsubscribe 标头以及解析器对其的理解,而 --vet 还通过 URL 审查和地址防护运行端点——因此您可以看到解析器是否理解标头以及防护是否允许该退订通过。严格只读:从不向发送者发出请求,邮箱中的内容也不会改变。
它无法撤销的:请求会告诉发送者您的地址是活跃的。忽略自身退订的发送者超出了任何客户端的能力范围——对于这些情况,将 unsubscribe 与 create_filter 或 trash 配对使用。不提供可自动化选项的情况会被报告为 unsubscribed:false,并附带替代方案,而不是作为错误。read-only 部署会获得 list_unsubscribe 和 list_subscriptions,并且从不发出请求。
安全与隐私
有关完整的威胁模型——信任边界、每个威胁的缓解措施、明确的非目标以及如何报告漏洞——请参阅 SECURITY.md。要点:
无遥测。 不向任何地方发送数据——无分析、无崩溃报告、无追踪。
默认无开放端口。 仅使用 stdio。可选的
--http监听器绑定到127.0.0.1(非局域网),并且在没有MAILWARDEN_TOKEN持有者令牌的情况下拒绝启动——设置MAILWARDEN_ALLOW_NO_TOKEN=1可在受信任的隔离网络上覆盖此行为。在回环绑定上,它还会验证Host头(DNS 重新绑定防御)。对于远程托管,请设置MAILWARDEN_HOST并在其前面加上 TLS。无发送工具——设计如此。 mailwarden 无法撰写、回复或转发。电子邮件中通过提示注入的指令无法通过此服务器泄露。
create_filter遵循相同的规则:它可以标记、归档、删除、加星或标记邮件,但绝不创建转发过滤器(这将是一条泄露路径)。list_filters仍然会显示账户上已有的任何转发过滤器,以便您发现。这是因为不存在这样的工具,且运行时无法注册任何工具;对于更强的变体,即 Google 拒绝发送而非 mailwarden 拒绝,请参见下面的只读模式。一个出站主机,无模型选择的 URL。
unsubscribe工具是唯一联系非 Google 主机的代码路径。其端点从消息的List-Unsubscribe头中读取——绝不从工具参数中读取——请求体是固定的,响应体被丢弃,因此它无法成为数据通道。仅限 https/默认端口,重定向会重新验证,任何解析到私有、回环、链路本地或元数据地址的跳转都会被拒绝。请参见取消订阅。工具层级(渐进式披露 + 最小范围)。
MAILWARDEN_TOOLS仅公布您指定的层级——read(读取工具)、manage(邮箱变更、暂停、下载)、filters(服务器端过滤器 CRUD,唯一需要gmail.settings.basic的层级)。默认是所有三个;例如read,manage提供完整的分诊表面而不包含过滤器管理。在--auth时请求的 OAuth 范围源自启用的层级——read部署仅请求gmail.readonly,而gmail.settings.basic仅在filters层级开启时才请求。并且当存储的令牌不携带gmail.settings.basic时(例如在启用该层级之前授权的令牌),过滤器工具会自动隐藏——重新运行--auth以授予该范围。没有记录范围的旧令牌会像以前一样公布,并回退到运行时范围不足的消息。只读模式。 设置
MAILWARDEN_READONLY=1(MAILWARDEN_TOOLS=read的简写),仅注册读取工具(search、get_thread、list_labels、list_snoozed、get_profile、triage_digest、list_unsubscribe、list_subscriptions)——任何可以更改邮箱或写入文件的内容都不会向客户端公布(过滤器工具也需要更广泛的gmail.settings.basic范围,也被排除)。推荐用于仅进行分诊的共享/HTTP 部署。这也是 Google 强制执行其无发送属性的唯一层级:它持有一个gmail.readonly令牌,Gmail 的发送端点会直接拒绝该令牌。manage需要gmail.modify,而 Gmail 确实接受该范围用于发送——mailwarden 只是没有暴露任何会发送的工具。因此,即使替换了此二进制文件,read部署也无法发送;manage部署无法发送是因为没有可调用的工具。(没有无发送的写入范围可供切换——请参见 SECURITY.md,威胁 1。)围栏下载。 设置
MAILWARDEN_DOWNLOAD_DIR后,附件写入被限制在该目录内(通过 realpath 规范化,支持符号链接),并且绝不会覆盖现有文件。不可信内容围栏。 每个工具结果都包裹在
<untrusted-tool-output>标记中,并剥离不可见/BiDi 覆盖字符,以便客户端区分引用的邮件内容与指令。实时 API,无副本。 任何地方都不存储邮箱镜像或搜索索引。唯一的本地状态是
~/.mailwarden/中的 OAuth 令牌。可选的静态令牌加密。
token.json包含一个刷新令牌;在磁盘上仅受mode 0o600保护(在 Windows 上无效)。设置MAILWARDEN_TOKEN_PASSPHRASE为密码短语后,令牌将以 AES-256-GCM 加密存储(scrypt 派生密钥),因此文件的副本——备份、同步文件夹、另一台机器——在没有密码短语的情况下毫无用处。设置后重新运行mailwarden --auth以加密现有令牌。注意边界:这防御的是文件盗窃,而非以您的用户身份运行的恶意软件(它也可以从环境中读取密码短语)。
快速开始
claude mcp add mailwarden -- npx -y mailwarden这就是整个安装过程——npx 获取并运行已发布的包,无需克隆或构建步骤。您只需要一次 Google OAuth 凭据(如下)。
设置
首次设置 Google OAuth 应用?请遵循 分步设置指南——它逐步引导您完成 Google Cloud Console,提供精确的点击路径,解释“未验证的应用”屏幕,并涵盖导致令牌在 7 天后失效的陷阱。简要版本:
Google Cloud: 创建项目 → 启用 Gmail API → 配置 OAuth 同意屏幕并发布到生产环境(在测试状态下,Google 会在 7 天后过期刷新令牌)→ 创建类型为 桌面应用 的 OAuth 客户端 ID → 下载为
credentials.json。将
credentials.json放入~/.mailwarden/(或设置MAILWARDEN_CREDENTIALS=/path/to/credentials.json)。授权一次——打开浏览器,将刷新令牌存储在
~/.mailwarden/token.json中:npx -y mailwarden --auth请求的范围:
gmail.modify(读取 + 标签/归档/删除)和gmail.settings.basic(仅过滤器管理)。如果您在过滤器存在之前授权了某个版本,请重新运行--auth一次以授予添加的范围。要持有一个 Gmail 本身拒绝发送的令牌,请使用MAILWARDEN_TOOLS=read授权——请参见上面的只读模式。随时使用内置诊断工具验证设置:
npx -y mailwarden --check它检查
credentials.json、令牌是否存在(以及是否加密)、授予的范围是否覆盖您启用的层级,并进行一次实时的 Gmail 调用以证明令牌仍然有效——针对任何错误打印具体的修复方法,并在出错时以非零退出(方便用于 CI/健康检查)。诊断常见的陷阱:没有/错误的凭据文件、从未授权、加密令牌但没有MAILWARDEN_TOKEN_PASSPHRASE、缺少范围,或 7 天“测试”同意令牌过期。
连接
Claude Code(本地 stdio):
claude mcp add mailwarden -- npx -y mailwardenClaude Code 插件——相同的服务器加上一个 /mailwarden:setup 技能,引导您完成 OAuth 设置并诊断损坏的配置。仓库根目录是插件(.claude-plugin/plugin.json),因此从克隆中:
claude --plugin-dir /path/to/mailwarden它已提交到 Anthropic 的社区市场;一旦列出,/plugin marketplace add anthropics/claude-plugins-community 然后 /plugin install mailwarden@claude-community 无需克隆即可完成相同操作。该插件运行完整的工具表面——对于更窄的层级(MAILWARDEN_TOOLS=read)或第二个账户,请使用 claude mcp add 并带上您想要的环境变量(请参见配置(环境变量)和多账户)。
Claude Desktop——添加到 claude_desktop_config.json:
{
"mcpServers": {
"mailwarden": { "command": "npx", "args": ["-y", "mailwarden"] }
}
}或者安装 MCPB 包(mailwarden-<version>.mcpb,从 0.10.0 版本起附加在 GitHub 发布页上)作为 Desktop 扩展——设置 → 扩展 → 安装扩展…——相同的服务器,运行时自包含(无需 npx;Claude Desktop 自带 Node 运行时),工具层级作为设置。该包是从打包的 npm 包构建的(与发布的文件集相同;npm run mcpb,在 CI 中验证:验证、解包并启动),也是 Smithery 分发的相同文件集。一次性 npx -y mailwarden --auth 仍然适用(为此需要一次 Node)——该包读取相同的 ~/.mailwarden/ 令牌。
Smithery——列为 csitte/mailwarden,它提供该包:
npx -y @smithery/cli install csitte/mailwarden --client claude # local stdio entry in the client's config注意您选择 Smithery 的两条路径中的哪一条。上面的安装写入一个普通的本地服务器条目:进程、您的令牌和您的邮件保留在您的机器上,与 npx 完全相同。相反,将其添加到 Smithery 的工具箱(smithery mcp add)也会在本地运行该包,但通过 Smithery 的网关中继工具流量,以便远程客户端可以访问它——这些响应中的邮箱内容随后会经过第三方。这是网关的属性,而非 mailwarden 的属性;如果您想要无第三方保证,请使用本地安装、npm 包或发布页中的 .mcpb。
远程(Streamable HTTP)——用于 VPS / claude.ai 自定义连接器:
# Loopback + token required by default. For real hosting, bind outward and keep the token:
MAILWARDEN_TOKEN=<secret> MAILWARDEN_HOST=0.0.0.0 npx -y mailwarden --http # :8787/mcp然后在 claude.ai 中:设置 → 连接器 → 添加自定义连接器 → 您的 https://your-host/mcp URL。在 Claude Code 中:claude mcp add --transport http mailwarden https://your-host/mcp。
多账户
一个 OAuth 应用(一个 credentials.json)可以授权多个 Gmail 账户。每个账户将其自己的刷新令牌保存在单独的文件中,由 MAILWARDEN_ACCOUNT 选择:
mailwarden --auth --account work # stores token.work.json
mailwarden --auth --account personal # stores token.personal.json通过每个账户注册一次服务器来并行运行它们,每个服务器使用自己的 MAILWARDEN_ACCOUNT。每个实例完全隔离——自己的令牌、自己的授予范围、自己的工具表面——因此没有任何东西可以作用于错误的邮箱:
{
"mcpServers": {
"gmail-work": { "command": "npx", "args": ["-y", "mailwarden"], "env": { "MAILWARDEN_ACCOUNT": "work" } },
"gmail-personal": { "command": "npx", "args": ["-y", "mailwarden"], "env": { "MAILWARDEN_ACCOUNT": "personal" } }
}
}账户名称不区分大小写——它们会成为文件名,因此 Work 和 work 在 Windows/macOS 上会是同一个文件。mailwarden 将它们转换为小写(--account Work → token.work.json),因此一个名称总是映射到恰好一个邮箱。
--auth 写入哪个文件仅取决于 --account / MAILWARDEN_ACCOUNT——绝不取决于您在浏览器中选择的账户。 因此,在没有 --account 的情况下授权第二个邮箱会直接指向第一个邮箱的令牌文件,所以 --auth 会先检查并拒绝,而不是替换另一个邮箱的令牌;--force 可以有意覆盖。这两个旋钮不可互换:MAILWARDEN_ACCOUNT 用于从一个配置目录中管理多个邮箱(它选择 token.<name>.json),而 MAILWARDEN_DIR 移动整个目录——有助于完全分离设置,但它不会在内部提供第二个账户。从仓库克隆中运行 npm run auth 不传递任何一个,即它始终服务于默认账户。
mailwarden --check 显示活动账户并列出它找到的其他账户。如果没有设置 MAILWARDEN_ACCOUNT,所有内容都像以前一样使用默认的 token.json——这完全向后兼容。
从源码构建
git clone https://github.com/csitte/mailwarden && cd mailwarden
npm install && npm run build
node dist/index.js --auth配置(环境变量)
变量 | 含义 |
| 配置目录(默认 |
|
|
| 选择一个命名账户(其令牌为 |
| 口令 → 加密静态存储的 |
|
|
| 将 |
|
|
| 逗号分隔的工具层级以公布: |
|
|
| HTTP 端口(默认 8787) |
| HTTP 绑定地址(默认 |
| HTTP 端点的 Bearer 令牌——除非被覆盖,否则 |
|
|
| 额外的逗号分隔 |
状态
正在使用中,用于日常邮箱自动化。核心 Gmail 工具 + 基于 googleapis 实现的清理功能,由 vitest 测试套件覆盖(789 个测试——npm run coverage)。当前版本:见上方 npm 徽章、更新日志 或 发布页面。欢迎提交 PR。
许可证
MIT © C.Sitte Softwaretechnik
Maintenance
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceAn MCP server that enables Gmail integration, allowing users to manage emails (send, receive, read, trash, mark as read) directly through MCP clients like Claude Desktop.1MIT
- AlicenseBqualityDmaintenanceManage your emails effortlessly with a standardized interface for drafting, sending, retrieving, and organizing messages. Streamline your email workflow with complete Gmail API coverage, including label and thread management.641,39856MIT
- AlicenseNot gradedqualityAmaintenanceGmail MCP server — scope-gated tools (readonly / send / modify), path jails for attachments + downloads, hardened OAuth credentials, Sigstore-signed releases.20711MIT
- AlicenseAqualityFmaintenanceA Gmail MCP server with native multi-account support, enabling management of multiple Gmail accounts from a single server instance.75MIT
Related MCP Connectors
Read, search, send, organize, draft and schedule email across your inboxes from any MCP client.
Shipmail MCP server for AI agent custom-domain email inboxes with REST API and webhooks.
Fully-managed email as MCP tools - register domains, real mailboxes, send and receive mail.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/csitte/mailwarden'
If you have feedback or need assistance with the MCP directory API, please join our Discord server