关于 Google Workspace Admin Security-Audit MCP
Google Workspace Admin Security-Audit MCP 是面向 Workspace 管理员的审计服务,用于调查账户风险和租户变更。它结合审计、目录、Gmail 与 Drive 数据,并提供跨域每日摘要;用户退出登录是需显式启用的状态变更例外。
主要功能
- 01审计登录、账户活动和管理员操作
- 02检查已暂停账户和 OAuth 授权
- 03追踪 Drive 分享及文档变更
- 04结合活动和邮箱设置调查账户失陷
- 05汇总多个域的安全信号
- 06可选地将用户退出登录
适用场景
- 01复核可疑登录和账户保护设置变更
- 02检查外部文件分享及共享云端硬盘成员变动
- 03追踪失陷账户的活动和持久化设置
- 04结合租户审计数据评估 Workspace 事件
MCP 相关
向 MCP 客户端提供 Google Workspace 安全审计工具,使用 Admin SDK Reports API 和 Directory API,并在指定检查中调用 Gmail 等 Workspace API。多数工具为只读;只有显式启用后才会注册用户退出登录工具。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
16收录的工具列表 · 更新于 3 天前
此工具无需参数。
详细介绍
gwsadm-mcp
English | 日本語
Google Workspace 安全审计 MCP(Model Context Protocol)服务—— 通过 Admin SDK Reports API(审计活动),只读查看账号锁定、可疑登录和外部文件共享情况。
名称取自管理控制台视角(gwsadm = Google Workspace 管理员),
是 boxadm-mcp 的同系列项目。这不是通用型 Workspace MCP:它用于发现风险,不会更改任何内容;只有一个需选择启用的例外——sign_out_user,仅当服务以 GWSADM_ENABLE_SIGN_OUT=1 启动时才会提供。
功能
| 工具 | 描述 |
|---|---|
health_check | 服务版本、配置路径和每个网域的身份验证探测——在会话开始时或超时后调用 |
login_audit | 报告 API login——被 Google 自动停用的账号(account_disabled_*:密码泄露、账号被劫持、发送垃圾邮件)、可疑登录和失败次数排名前 N 的账号 |
gmail_usage_report | 报告 API customerUsageReports——按网域统计 Gmail 每日发送/接收数量,一次查询一天,截止到昨天(API 自身采用的 UTC-8:00/PST 日期基准)。需要单独的 admin.reports.usage.readonly DWD 范围(见下文“身份验证模型”)——它与 admin.reports.audit.readonly 是不同的授权,尽管两者都属于 Reports API |
suspended_accounts | Directory API——当前 已暂停账号的快照(isSuspended=true);与下游 IdP(例如 KeyCloak)交叉比对,以找出已暂停但仍处于启用状态的账号 |
get_user | Directory API users().get——一个指定账号的当前状态:suspended(包括原因和时间)、archived、last_login、是否已注册/强制启用两步验证(2SV)、组织单位、创建时间、是否待更改密码。这是“此人为什么无法登录”的查询:对你已知的地址发出一次请求,无需分页——不同于 suspended_accounts,后者只列出确实处于暂停状态的账号,因此永远无法确认某个地址未被暂停(而且当列表超过分页上限后,未出现在列表中也不再能作为任何一方的证据)。所需范围不超过 suspended_accounts 已使用的范围 |
user_oauth_tokens | Directory API tokens().list——一个用户获授的第三方 OAuth 应用权限;这是 login_audit 无法察觉的一种入侵途径,因为已获授的令牌无需再次登录。根据用户名后缀解析网域;对于别名/辅助网域地址,可选用 domain 覆盖 |
sign_out_user | 会更改状态,需主动启用。 Directory API users().signOut——将一个账号从所有网页和设备会话中注销,并重置其登录 Cookie;执行前会通过 users().get 确认账号存在。只有服务启动时设置 GWSADM_ENABLE_SIGN_OUT=1 才会注册;请在调用前加入审批步骤。不会撤销 OAuth 授权或应用专用密码,也不会移除已注册的第二因素或更改密码(这些事项列在其 not_covered 中)。需要两项授权:账号检查需要 admin.directory.user.readonly(与 get_user 相同),注销操作本身需要 admin.directory.user.security(与 user_oauth_tokens 相同);被模拟的管理员还必须获准管理用户安全。每次都会返回相同的一组键:signed_out 为 true(Google 已确认)、false(未更改任何内容)或 null(结果未知:请求已发送,但未及时收到答复);并会在 GWSADM_SIGN_OUT_DEADLINE_SECONDS 指定的时间内结束(默认 45 秒)。必须配置该地址所属的网域;如果提供了 domain,则必须与其匹配。 |
user_activity | Reports API activities().list,使用 userKey=<username>——某个时间范围内一个账号的所有操作,按最新优先合并多个应用(默认包括 login、token、user_accounts、drive、groups、calendar、chat、meet、saml)。flags 会显示改变账号安全状态的事件,无论事件数量多少——注册/停用 2SV、更改恢复邮箱/电话、更改密码、外发邮件转发、Google 的 risky_sensitive_action_*/suspicious_login 判定,以及每次包含客户端和范围的 OAuth authorize——因为攻击者注册的第二因素会在密码重置后继续生效;ips 会列出不同的来源地址及其首次/最后出现时间。Google 自身的 account_disabled_*/suspicious_login 判定不会通过该账号的 userKey 提供(其操作者是 Google),因此会像 login_audit 一样获取,并归入合成应用 login_verdicts。这是 login_audit 无法回答的入侵后调查问题:它只能查看 Google 自身提出的事件,且范围涵盖整个网域 |
ip_activity | Reports API activities().list,使用 actorIpAddress=<ip>——一个来源地址在各个网域和应用中代表哪些账号执行过操作,包含相同的 flags。当 user_activity 找到攻击者的地址后,可进一步调查:同一来源是否还访问了其他账号?攻击成功之前,密码喷洒会表现为 login_failure 计数 |
compromise_sweep | 确认一个账号遭入侵后,一次调用即可完成 Google 侧的后续调查:activity(user_activity,优先显示标记);state/grants(get_user;突出显示被标记为 risky 的 OAuth 授权,包括邮件/Drive/联系人/管理员范围);ips(针对攻击者的地址——由用户提供,或从该账号自身的标记中获取——在所有网域运行 ip_activity,并排除该账号本身);以及 gmail:攻击者留下的持续读取邮件设置(自动转发、转发地址、会转发/删除/归档邮件的过滤器、委托人、代发地址和回复地址),以及该邮箱在指定时间范围内发送的邮件数量和收件人网域——绝不会读取主题或正文。gmail.kind 表示能否读取相关信息:ok/partial(某个设置部分或已发送邮件摘要读取失败;其余信息仍有效)/api_disabled(服务账号自身的 Cloud 项目中 Gmail API 已关闭)/no_grant/error——读取失败会明确报告,绝不会当作“未发现问题”。highlights 列出需采取行动的事实,checklist 列出仍需手动完成的事项。与 gmail_message_trace 共用 gmail.readonly DWD 范围(已通过实时验证,包括 delegates.list 在内的所有设置读取均由该范围提供) |
drive_external_sharing | Reports API drive——向外部地址或网域授予的 ACL 权限(撤销操作单独报告),以及转变为链接/公开访问的可见性变更 |
drive_doc_activity | Reports API drive,使用服务器端 doc_id 过滤器——一个文档的所有者、ACL 变更和生命周期事件。可与 drive_external_sharing 配合进行调查:所有者(个人还是共享云端硬盘的名称)可以区分共享云端硬盘导致的误报;在共享云端硬盘中创建的文件会继承成员 ACL,因此看起来像是大规模外部共享 |
shared_drive_membership_changes | Reports API drive(shared_drive_membership_change)——谁在何时添加/移除共享云端硬盘成员或更改其角色,并标明受影响成员是否为外部成员;支持客户端按云端硬盘名称过滤 |
admin_actions | Reports API admin——管理员(个人,或以管理员身份操作的同步程序或 API 客户端)在 Admin 控制台中的操作:针对一个用户或群组(在目标网域上使用服务器端 USER_EMAIL/GROUP_EMAIL 过滤器)或整个租户。可回答“谁暂停了此账号/重置了密码/将其移出群组?”,而 user_activity 无法回答:管理员事件的操作者是管理员,而不是被操作的账号 |
workspace_status | Google Workspace 状态信息中心(公开 Feed,无需凭据)——当前未解决的事件,以及在指定时间范围内结束的事件;可选定一个产品(Gmail、Drive 等)。在进行租户侧调查前,先回答“问题出在 Google 吗?” |
gmail_message_trace | Gmail API——已知的 Message-ID 是否送达特定邮箱,以及邮件所在位置(收件箱/垃圾邮件/废纸篓/已归档)?对于每个收件人,工具都会通过 DWD 模拟该用户并搜索其邮箱。需要单独的 gmail.readonly DWD 范围(见下文“身份验证模型”);缺少此授权的网域会按收件人报告错误,绝不会错误地声称“未找到” |
dmarc_rua_summary | Gmail API——按网域汇总 DMARC 聚合(RUA)报告的通过/失败结果,以及最可能被拒绝的来源 IP 地址。工具会模拟网域配置的 dmarc_rua_mailbox(一个真实用户;默认为 postmaster@<domain>),搜索发送到 dmarc_rua_recipient(已发布的 rua= 地址,例如 postmaster+rua@;默认为邮箱本身)的邮件,并读取这些邮件所附的压缩报告。与 gmail_message_trace 共用 gmail.readonly DWD 范围,但不同于该工具,此工具会读取附件内容(报告 XML),而不只是元数据——见下文“身份验证模型” |
group_delivery_policy | Groups Settings API——Google 群组自身的发帖/投递政策(who_can_post、allow_external_members、审核级别)。群组的访问控制位于 Gmail 投递之前:仅允许网域内成员发帖的政策会在外部发件人的邮件生成任何 Gmail 投递事件之前将其静默丢弃;如果不直接读取政策,就无法将此情况与投递失败区分开。需要单独的 apps.groups.settings DWD 范围(见下文“身份验证模型”) |
list_group_members | Directory API——Google 群组的基本元数据和成员列表,直接解析,而不是根据某封特定邮件的收件人推断。需要单独的 admin.directory.group.readonly 和 admin.directory.group.member.readonly DWD 范围(见下文“身份验证模型”) |
daily_brief | 一次调用即可汇总所有已配置网域的信息,包括账号保护变更(两步验证、通行密钥、高级保护、密码、恢复地址/电话、转发到网域外的邮件);如果变更来自你的常驻国家/地区之外(GWSADM_IP2ASN、GWSADM_HOME_COUNTRIES)、来自无法定位的来源,或发生在可疑登录之后,则会特别标记 |
daily_brief_start / daily_brief_result | 与 daily_brief 相同,但在后台运行:start 会立即返回 job_id,随后轮询 result(job_id),直到状态为 done。适用于大型租户,因为同步调用可能会触发客户端约 60 秒的工具调用超时 |
计划支持:dlp_events(报告 rules;需要启用 DLP 的 Workspace 版本), | |
token_events。 |
身份验证模型
使用服务账号,通过 域范围委派(DWD) 冒充具备审计权限的管理员。完全无需交互——无需浏览器,也无需轮换令牌刷新,因此服务器可无人值守运行(cron、MCP 网关、CI)。
在同一次设置中,预先为同一个服务账号客户端 ID 授予以下 所有 DWD 范围。随着每个工具的开发逐个添加范围,很容易漏掉某个范围,直到依赖它的工具开始降级——集中在一处一次性完成,可避免这个问题:
| 范围 | 所需工具 | 缺少时的影响 |
|---|---|---|
https://www.googleapis.com/auth/admin.reports.audit.readonly | login_audit、user_activity、ip_activity、admin_actions、drive_external_sharing、drive_doc_activity、shared_drive_membership_changes、daily_brief* | 这些工具会降级为按域报告错误 |
https://www.googleapis.com/auth/admin.directory.user.readonly | suspended_accounts、get_user、sign_out_user(选择启用;用于其账号检查) | 这些工具会降级为错误(suspended_accounts 按域报告);其他所有功能仍可正常工作 |
https://www.googleapis.com/auth/admin.directory.user.security | user_oauth_tokens、sign_out_user(选择启用) | 这些工具会降级为按域报告错误;其他所有功能仍可正常工作 |
health_check 无需任何范围即可响应:如果可能缺少授权,应调用此工具——它会逐个探测各个域,并在结构化的按域结果中报告身份验证失败,而不是自身报错。
gmail_usage_report 也需要单独的范围,尽管它与上面的基础授权步骤一样,都使用 Admin SDK Reports API——“Usage report”系列(customerUsageReports)和“Audit”活动流(activities().list,基础授权步骤中的其他所有内容)受两个不同的范围控制,拥有其中一个并不意味着拥有另一个:
| 范围 | 所需工具 | 缺少时的影响 |
|---|---|---|
https://www.googleapis.com/auth/admin.reports.usage.readonly | gmail_usage_report | 该工具会降级为按域报告错误;其他所有功能仍可正常工作 |
gmail_message_trace 和 dmarc_rua_summary 还需要另一个范围,须作为 单独 步骤授予——特意不将它包含在上述授权步骤中:
| 范围 | 所需工具 | 缺少时的影响 |
|---|---|---|
https://www.googleapis.com/auth/gmail.readonly | gmail_message_trace、dmarc_rua_summary、compromise_sweep(其 gmail 部分) | 这些工具会按收件人/域报告错误;其他所有功能仍可正常工作 |
与上述三个范围相比,这项授权范围实质上更广:它允许读取服务账号所冒充的任何用户的邮件内容,而不仅仅是元数据。gmail_message_trace 始终只请求 format="metadata"——从不读取邮件正文——但 dmarc_rua_summary 确实会读取内容:它会获取每封 RUA 邮件所附的压缩 DMARC 报告附件(format="full" 加上 attachments().get()),并进行解析。无论哪种情况,两者都不会超出授权范围;但只有 gmail_message_trace 始终遵循更严格的“仅元数据”做法。曾考虑为这两个工具使用范围更窄的 gmail.metadata,但最终否决,因为它不支持 q= 搜索参数,而 rfc822msgid:/RUA 邮箱查找需要此参数。在同一个服务账号客户端 ID 上授予该范围(Admin 控制台 → 安全性 → API 控件 → 域范围委派 → 找到现有客户端 ID → 将此范围添加到其列表),并在为某个域启用之前,权衡这种更广泛的访问权限与实际对邮件追踪/DMARC 报告的需求。
group_delivery_policy 和 list_group_members 也各自需要单独的范围——除了基础授权步骤外,还需额外授予三个范围;这些范围彼此独立,也不会与上述 gmail.readonly 合并:
| 范围 | 所需工具 | 缺少时的情况 |
|---|---|---|
https://www.googleapis.com/auth/apps.groups.settings | group_delivery_policy | 该工具会降级为报错;其他功能仍可正常使用 |
https://www.googleapis.com/auth/admin.directory.group.readonly | list_group_members(群组元数据部分) | 该部分会报告自身错误;如果授予下方所需的范围,成员名册部分仍可独立正常工作 |
https://www.googleapis.com/auth/admin.directory.group.member.readonly | list_group_members(成员名册部分) | 同上,与上方的元数据部分相互独立——两次调用不会互相阻断 |
Groups Settings API 是与 Directory API 不同的产品,因此需要单独的范围;它没有仅供只读的变体,但此服务只会调用 groups().get(),不会调用任何会修改数据的方法。
suspended_accounts、get_user 和 user_oauth_tokens 都按配置的网域运行(Directory 的 domain=/userKey=),不同于面向整个客户的 Reports 工具——因此,你希望涵盖的每个网域(例如单独的学生网域)都需要自己的 [domain.*] 配置段。请注意,失败情形有所不同:suspended_accounts 会在结果中静默省略未配置的网域,而 get_user 和 user_oauth_tokens 会因未知网域而明确报错(这两个工具都接受 domain 覆盖值,可用于后缀没有对应配置段的别名或辅助地址)。
设置
# uv
uv pip install gwsadm-mcp
# pip
pip install gwsadm-mcp
或者从源代码安装:
git clone https://github.com/shigechika/gwsadm-mcp.git
cd gwsadm-mcp
# uv
uv sync
# pip
pip install -e .
配置
将 GWSADM_CONFIG 指向 INI 文件(默认路径为 ~/.config/gwsadm-mcp/config.ini,并保持权限为 0600):
[gwsadm]
# optional; defaults to all [domain.*] section names
internal_domains = example.edu, mail.example.edu
[domain.example.edu]
service_account_file = /path/to/service-account.json
subject = [email protected]
customer_id = C0xxxxxxx
dmarc_rua_mailbox = [email protected] # optional, default: postmaster@<domain>; "none" opts out
dmarc_rua_recipient = [email protected] # optional, default: same as dmarc_rua_mailbox
每个要审计的 Workspace 网域都需要一个 [domain.*] 配置段。internal_domains 是用于将共享目标分类为内部或外部的允许列表。dmarc_rua_mailbox 是 dmarc_rua_summary 模拟身份以读取 DMARC 汇总报告的真实用户;网域范围的委派只能使用真实用户的身份,不能使用群组或别名。dmarc_rua_recipient 是接收报告的地址(即发布在网域 _dmarc 记录中的 rua=mailto: 值),仅用于缩小 Gmail 搜索范围(to:<recipient>);默认值为邮箱地址。当已发布的地址是 Gmail 加号子地址(例如 postmaster+rua@)时,可设置此项(按此地址搜索也可将发送到 postmaster+ruf@ 的 ruf= 失败报告排除在汇总解析之外);如果该地址是转发到模拟身份收件箱的群组,也可设置此项。设置 dmarc_rua_mailbox = none 可让某个网域停用 DMARC 读取——例如,当其 rua= 指向其他网域的邮箱,且另一个 [domain.*] 配置段已经读取该邮箱时;报告会按每份报告中指定的策略网域分组,因此仍会显示在另一个配置段下。
环境变量(除注明外均为可选):
| 变量 | 默认值 | 含义 |
|---|---|---|
GWSADM_CONFIG | ~/.config/gwsadm-mcp/config.ini | 上述 INI 文件 |
GWSADM_MAX_WORKERS | 8 | 扇出请求中并发获取 Reports API 数据的数量(限制在 1–32;非整数值会回退为 8)。如果租户触发了限流,请调低此值;如果大型租户的扫描太慢,请调高此值 |
GWSADM_SITES_INI | — | 你自己的地址范围,用于标注账户保护变更的来源(参阅设置文档) |
GWSADM_IP2ASN / GWSADM_HOME_COUNTRIES | — | iptoasn 表和本国列表,用于标记来自境外的变更(参阅设置文档) |
GWSADM_ENABLE_SIGN_OUT | 未设置 | 设为 1 可注册会更改状态的 sign_out_user |
GWSADM_SIGN_OUT_DEADLINE_SECONDS | 45 | 单次 sign_out_user 调用的时间预算(限制在 10–300 秒) |
使用
Claude Code(插件)
此仓库同时也是一个单插件市场,因此 Claude Code 可以为你安装服务:
/plugin marketplace add shigechika/gwsadm-mcp
/plugin install gwsadm-mcp@gwsadm-mcp
该插件会启动 uvx gwsadm-mcp,并读取 GWSADM_CONFIG(若未设置,则回退到 ~/.config/gwsadm-mcp/config.ini),这与配置中描述的变量相同。/plugin install 只会配置服务进程——它无法创建配置 INI 文件或其指向的 Google Cloud 服务账号 JSON 密钥;在任何工具调用成功之前,这两者都必须已存在于运行插件的机器上。
运行 Claude Code 的进程必须能在 PATH 中找到 uvx——登录 shell 通常可以,但通过 GUI 启动的应用可能不行;如果插件无法启动,请在系统范围内安装 uv。
Claude Code(手动配置)
添加到 .mcp.json(配置位于默认路径时无需设置 env;仅当使用非默认位置时才添加 "env": { "GWSADM_CONFIG": "..." }):
{
"mcpServers": {
"gwsadm-mcp": {
"type": "stdio",
"command": "gwsadm-mcp"
}
}
}
Claude Desktop
将相同条目添加到 claude_desktop_config.json。
Codex
codex mcp add gwsadm-mcp -- gwsadm-mcp
仅当配置 INI 不在默认路径(~/.config/gwsadm-mcp/config.ini)时,才添加 --env GWSADM_CONFIG=...(放在 -- 之前)。
Gemini CLI
添加到 ~/.gemini/settings.json(或项目的 .gemini/settings.json):
{
"mcpServers": {
"gwsadm-mcp": {
"command": "gwsadm-mcp"
}
}
}
与 Codex 一样,仅当配置位于非默认位置时,才添加 "env": { "GWSADM_CONFIG": "..." }。若只想开放部分工具,请在 includeTools 中列出它们。
任何其他会启动 stdio 服务的 MCP 客户端也采用相同方式:将 gwsadm-mcp 指定为命令。
直接执行
gwsadm-mcp
CLI 选项
gwsadm-mcp --version # Print version and exit
gwsadm-mcp --check # Config + auth + API smoke for every domain, then exit
gwsadm-mcp # Start MCP server (STDIO, default)
--check 退出码:0 表示成功;配置或身份验证失败时返回非零值。
归档 DMARC 报告(批量)
dmarc_rua_summary 会汇总巡检的滚动时间窗口。若要保留逐日历史记录,可以通过定时任务导出报告本身:
gwsadm-mcp dmarc-reports --domain example.edu --since 2026-09-20 --until 2026-09-23 > reports.json
- 使用与
dmarc_rua_summary相同的 RUA 邮箱(需要相同的gmail.readonly权限范围),读取在[--since, --until)内收到的邮件(UTC 日期,使用 Gmailafter:/before:);处理每个附件和每个 ZIP 条目(每个附件最多处理 20 个条目和 50 MB 解压数据;超出限制的条目,以及加密或损坏的条目,都会计入non_report_attachments)。 - 向 stdout 输出一个 JSON 文档:包含每份报告的完整内容——
org_name、report_id、begin/end(报告时段的 epoch 秒数)、发布的policy(p、sp、pct、adkim、aspf)及其包含reason和auth_results的记录。不会进行汇总或去重:有些报告者会将一份报告拆分到多封邮件中,且使用相同的report_id,因此请按内容去重,而不要按 ID 去重。 - 只有在没有任何内容达到上限,且没有跳过任何邮件、附件或记录(
message_errors、non_report_attachments、dropped_records)时,fetch_complete才为 true。 - 参数或配置错误时退出码为
2;API 或身份验证失败(例如权限范围已被撤销)时退出码为1;这两种情况下都不会向 stdout 写入内容。 - 输出包含发件人 IP 和邮件头域名。请勿将其放入任何公开发布的内容中。
注意事项
- 当某个结果区段超出分页预算,或探测的提取操作直接出错(见
event_errors)时,都会报告capped: true——不会把部分覆盖呈现为“没有发现”。Drive 扫描还会报告capped_events(哪些 eventNames 被截断)。缩短hours或提高max_pages,以获得完整覆盖——在大型租户中,学期期间的工作日每天可能产生数千个change_user_access事件。 - Google 的
visibility=shared_externally是相对于文件所有者的域而言的,因此,当存在多个internal_domains时,跨内部域的授权(例如从学生域授权到教职员工域)也会带有此标记。因此,是否为外部对象,要根据授权目标并参照internal_domains判断:具名授权使用target_user,域范围授权使用target_domain(例如“partner.edu 中的任何人”;字面域名"all"表示“任何拥有链接的人”,应改为根据可见性判断)。risky_visibility_events只统计变为people_with_link/public_on_the_web的状态转换(不包括从公开收窄为仅链接可访问)。untargeted_external_transitions是一个剩余类别,用于统计变为shared_externally、但没有可供分类的目标地址或域名的状态转换——它不是用于核对其他位置是否遗漏授权的指标,因为域范围授权已在上文统计。external_samples/exposure_samples/untargeted_samples分别保存各类别的示例。 - Drive 事件按每次一个与审计相关的 eventName 查询,因此分页预算不会被查看/编辑等无关事件耗尽;API 拒绝的事件名称会记入
event_errors,而不会导致工具失败。change_document_visibility和change_document_access_scope在此 API 中会将同一状态转换报告为同时发生的关联事件——只有后者会用于分类(前者仅用于统计其acl_events数量),因此域范围授权或链接/公开暴露不会在两者之间重复计数。这也意味着,如果后者自身的提取失败,前者不再能作为补充:event_errors中出现change_document_access_scope条目时,该域会设为capped: true;尽管change_document_visibility(以及acl_events)可能有数据,该时间窗口的分类计数仍只是下限。 - 一个域发生故障,只会影响该域的区段(
{"error": ...})。 - 如果某个收件人的邮箱中,存在多封具有相同 Message-ID 的邮件(邮件列表副本加上直接抄送、隔离邮件释放后的重复邮件等),
gmail_message_trace会为该收件人设置ambiguous: true(并附带match_count)——该收件人的其余字段只描述第一封匹配邮件,不会合并多封邮件的信息。如果邮箱中的匹配项足够多,导致match_count是下限而非精确值,则还会同时设置match_count_capped(搜索不会翻页)。 get_user会区分“该地址不对应任何账号”和“查询失败”:普通 HTTP 404 会返回found: false,且不包含状态字段。这是诊断结果,表示地址拼写错误或账号已删除,绝不会是error。缺少 DWD 范围或发生暂时性故障时,会返回{"error": ...},且不包含found键,因此两种情况绝不会混淆。Google 未返回的字段会保留为null,而不会被强行转换:如果缺少suspended,就不能据此判断“账号状态正常”。group_delivery_policy会将 Groups Settings API 的"true"/"false"字符串字段(这是该 API 的特性,并非 JSON 布尔值)规范化为输出中的真实布尔值;Google 响应中缺失的字段仍为null,绝不会被强行转换为false。list_group_members会独立执行群组元数据和成员名册查询——即使租户只有这两者中的一个 DWD 范围,相应区段仍会正常返回,另一项则会在其位置报告为{"error": ...}。当成员名册超出分页预算(默认 20 页 × 每页 200 个成员),或成员查询直接失败(见members_error)时,都会报告capped: true——无论哪种情况,名册都不完整;当capped为true时,绝不能将空的members列表视为已确认该群组为空。 两个群组工具都会区分“该地址不是群组”(普通 HTTP 404,已针对底层三个 API 调用在生产环境中验证)和真实故障:group_delivery_policy会设置found: false;list_group_members也会在以下任一情况下设置该字段:两个独立查询均确认不存在,且两侧都没有错误;或一侧确认不存在、另一侧独立查询失败(此时会附带group_lookup_error/members_lookup_error,而不是隐藏该错误)——已确认不存在这一事实优先于另一范围上的无关错误。只有真正的混合状态(一侧确认不存在,另一侧实际查到数据)才会采用常规的分区段结构。- 设计为只读,但有一个可选择启用的例外。唯一发出的读取调用为:Reports API 中的
activities().list()和customerUsageReports().get();Directory API 中的users().list()、users().get()、tokens().list()、groups().get()、members().list();Groups Settings API 中的groups().get();Gmail API 中,gmail_message_trace和compromise_sweep使用的messages().list()/messages().get(format="metadata")(后者只读取 To/Cc/Bcc/Date 标头),以及dmarc_rua_summary使用的messages().list()/messages().get(format="full")/messages().attachments().get()(它会读取报告附件,而不只是元数据);compromise_sweep使用的设置读取调用users().settings().getAutoForwarding()/forwardingAddresses().list()/filters().list()/delegates().list()/sendAs().list();以及 Gmailhealth_check探测使用的users().getProfile()。 唯一的写入操作是 Directory API 的users().signOut(),仅由sign_out_user发出——只有在服务器以GWSADM_ENABLE_SIGN_OUT=1启动时才会提供该工具。workspace_status也会在不使用凭据的情况下 GET Google 的公开状态信息源。 - 输出包含账号地址(这正是审计工具的用途):请限制授权安全人员访问。
gmail_message_trace还会返回匹配邮件的内容摘要和标头(From/To/Cc/Subject/Date)——应像对待其来源邮箱内容一样谨慎处理该工具的输出。
开发
git clone https://github.com/shigechika/gwsadm-mcp.git
cd gwsadm-mcp
# uv
uv sync --dev
uv run pytest -v
uv run ruff check .
# pip
python3 -m venv .venv
.venv/bin/pip install -e . && .venv/bin/pip install pytest ruff
.venv/bin/pytest -v
.venv/bin/ruff check .
实时冒烟测试
单元测试套件从不连接 Google,这让测试运行得很快,也意味着它无法发现工具是否已停止返回真实数据。
scripts/smoke_test.py 会使用配置的租户测试每个已注册的工具,并在回答为空、格式错误或出错时失败:
# uses the same config file as the server (GWSADM_CONFIG)
uv run python scripts/smoke_test.py
uv run python scripts/smoke_test.py --only oauth --traceback
- 只读。 这里测试的每个工具都会读取审计日志或目录快照;不会更改 Workspace 中的任何内容(会跳过
sign_out_user)。daily_brief_start会在进程内创建一个任务,该任务会自行过期。 - 报告中不包含负载数据。 报告中只有工具名称、状态和行数;也会对服务端编写的错误文本进行脱敏,因为这些工具处理的内容始终涉及账户地址和文档标题。
- 有界。 工具提供的每个边界参数都会显式传入——默认值(5 页、180 天、200 个事件)是按人类单次提问的规模设置的,并由一项会从源代码中查找这些参数的测试强制执行。
- 规范中不包含特定租户的信息。 需要用户和文档的工具会在运行时发现相应账户和文档;如果租户没有可用对象,则跳过这些工具。两项测试确保这一点:一项禁止将这些参数写成字面值,另一项禁止文件中出现任何类似地址的内容,因为此仓库是公开的。
- 空回答也算通过:没有外部共享内容且没有锁定账户才是预期状态。实际断言的是响应封装;如果回答按域名分类,还会断言域名映射非空,因为如果配置解析出的域名数为零,所有工具都可能被报告为正常运行,实际上却没有审计任何内容。
- CI 会强制执行成本较低的部分:注册工具时若未提供探测规范,构建就会失败(
tests/test_smoke_probes.py),因此每次添加工具都必须考虑“如何确认它正常工作?” scripts/smoke_harness.py是执行引擎,不包含任何 Workspace 相关知识:它在共用该引擎的服务之间保持一致,因此修复引擎错误后,只需同步此文件,而不是单独修改此副本。
发布
使用 release-please 自动发布版本。将 Conventional Commits(feat:、fix: 等)合并到 main 后,系统会保持一个包含下个版本和变更日志的发布 PR。合并该 PR 会创建 vX.Y.Z 标签并发布 GitHub Release,其 release: published 事件会触发 release 工作流,构建并发布到 PyPI 和 MCP Registry。版本由 release-please 管理,位于 gwsadm_mcp/__init__.py 和 server.json 中(请勿手动更新)。
重要
release-please 工作流需要配置仓库密钥
RELEASE_PLEASE_TOKEN(具有contents: write和pull-requests: write权限的 PAT)。 默认的GITHUB_TOKEN无法创建用于触发下游release工作流的 Release(GitHub 会阻止由GITHUB_TOKEN触发的工作流运行),因此如果未配置 PAT,就不会发布任何内容。未设置该密钥时,工作流会回退到使用GITHUB_TOKEN,以便 Fork 中的 PR CI 仍能正常运行。
许可证
MIT
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。