MCP server for Google Workspace security auditing — login audit, external sharing, daily brief
- ✓Open-source license (MIT)
- ✓Actively maintained (<30d)
- ✓Clear description
- ✓Documented (README)
claude mcp add gwsadm-mcp -- uvx gwsadm-mcp{
"mcpServers": {
"gwsadm-mcp": {
"command": "uvx",
"args": ["gwsadm-mcp"]
}
}
}MCP Servers overview
<!-- mcp-name: io.github.shigechika/gwsadm-mcp --> # gwsadm-mcp English | [日本語](README.ja.md) Google Workspace **security-audit** MCP (Model Context Protocol) server — read-only visibility into account locks, suspicious logins, and external file sharing, built on the Admin SDK Reports API (audit activities). Named after the admin-console viewpoint (`gwsadm` = Google Workspace admin), sibling of [`boxadm-mcp`](https://github.com/shigechika/boxadm-mcp). This is **not** a general-purpose Workspace MCP: it surfaces risk, it never mutates anything. ## Features | Tool | Description | |------|-------------| | `health_check` | Server version, config path, and per-domain auth probe — call at session start or after a timeout | | `login_audit` | Reports API `login` — accounts **auto-disabled by Google** (`account_disabled_*`: leaked password, hijacked, spamming), suspicious logins, failure top-N | | `gmail_usage_report` | Reports API `customerUsageReports` — daily Gmail send/receive counts per domain, one date at a time, ending yesterday (the API's own UTC-8:00/PST date anchor). Requires the separate `admin.reports.usage.readonly` DWD scope (see Auth model below) — a DIFFERENT grant from `admin.reports.audit.readonly` even though both are Reports API | | `suspended_accounts` | Directory API — current snapshot of **suspended** accounts (`isSuspended=true`); cross-reference against a downstream IdP (e.g. KeyCloak) to find suspended-but-still-enabled accounts | | `get_user` | Directory API `users().get` — **one named account's** current state: `suspended` (with reason and time), `archived`, `last_login`, 2SV enrolled/enforced, org unit, creation time, pending password change. The "why can't this person sign in" lookup: one request, no pagination, for an address you already know — unlike `suspended_accounts`, which lists only accounts that ARE suspended, so it can never confirm that a given address is *not* suspended (and once that list exceeds its page cap, absence stops being evidence either way). Needs no scope beyond the one `suspended_accounts` already uses | | `user_oauth_tokens` | Directory API `tokens().list` — third-party OAuth app grants for **one user**; a compromise vector `login_audit` is blind to, since a previously-granted token needs no fresh login. Domain resolved from the username's suffix, with an optional `domain` override for alias/secondary-domain addresses | | `user_activity` | Reports API `activities().list` with `userKey=<username>` — everything **one account** did in a window, merged across applications (login, token, user_accounts, drive, groups, calendar, chat, meet, saml by default) newest first. `flags` surfaces the events that change the account's security posture regardless of volume — 2SV enrolled/disabled, recovery email/phone changed, password changed, outbound forwarding, Google's `risky_sensitive_action_*` / `suspicious_login` verdicts, every OAuth `authorize` with client and scopes — because a second factor the attacker enrolled survives a password reset; `ips` lists the distinct source addresses with first/last seen. Google's own `account_disabled_*` / `suspicious_login` verdicts are not served under the account's `userKey` (their actor is Google), so they are fetched the way `login_audit` does and reported under the synthetic application `login_verdicts`. The post-compromise question `login_audit` cannot answer: it sees only the events Google itself raised, domain-wide | | `ip_activity` | Reports API `activities().list` with `actorIpAddress=<ip>` — every account **one source address** acted as, per domain and application, with the same `flags`. The follow-through after `user_activity` names an attacker's address: did the same source reach other accounts? A spray shows up as `login_failure` counts before it ever succeeds | | `compromise_sweep` | The Google-side follow-through after **one account** is confirmed compromised, in one call: `activity` (`user_activity`, flags first), `state` / `grants` (`get_user`; OAuth grants with mail / Drive / contacts / admin scopes called out as `risky`), `ips` (`ip_activity` across every domain for the attacker's addresses — given, or taken from the account's own flags — with the account itself excluded), and `gmail`: the settings an attacker leaves behind to keep reading mail (auto-forwarding, forwarding addresses, filters that forward / delete / archive, delegates, send-as and reply-to) plus what the mailbox sent in the window as counts and recipient domains — never a subject or body. `gmail.kind` says whether it could be read at all: `ok` / `partial` (one settings section or the sent summary failed; the rest stands) / `api_disabled` (the Gmail API is off in the service account's own Cloud project) / `no_grant` / `error` — a failure is reported, never read as "nothing found". `highlights` lists the facts to act on, `checklist` what remains to do by hand. Shares `gmail_message_trace`'s `gmail.readonly` DWD scope (every settings read, `delegates.list` included, is served by it — verified live) | | `drive_external_sharing` | Reports API `drive` — ACL **grants** to external addresses or domains (revocations reported separately) and visibility **transitions** into link/public exposure | | `drive_doc_activity` | Reports API `drive` with a server-side `doc_id` filter — **one document's** owner, ACL changes, and lifecycle events. Triage companion to `drive_external_sharing`: the owner (an individual vs. a shared drive's name) disambiguates the shared-drive false-positive class, where files created inside a shared drive propagate member ACLs and read as bulk external sharing | | `shared_drive_membership_changes` | Reports API `drive` (`shared_drive_membership_change`) — who added/removed/re-roled shared-drive members and when, with external classification of the affected member and a client-side drive-name filter | | `gmail_message_trace` | Gmail API — did a **known** Message-ID reach **specific** mailboxes, and where (inbox/spam/trash/archived)? For each recipient it impersonates that user via DWD and searches their own mailbox. Requires the separate `gmail.readonly` DWD scope (see Auth model below); a domain missing that grant reports a per-recipient error, never a false "not found" | | `dmarc_rua_summary` | Gmail API — DMARC aggregate (RUA) report pass/fail summary and top reject-candidate source IPs, per domain. Impersonates the domain's configured `dmarc_rua_mailbox` (a real user; default `postmaster@<domain>`), searches it for mail addressed to `dmarc_rua_recipient` (the published `rua=` address, e.g. `postmaster+rua@`; default: the mailbox) and reads the compressed report attachments those messages carry. Shares `gmail_message_trace`'s `gmail.readonly` DWD scope, but unlike that tool this one DOES read attachment content (the report XML), not just metadata — see Auth model below | | `group_delivery_policy` | Groups Settings API — a Google Group's own posting/delivery policy (`who_can_post`, `allow_external_members`, moderation levels). A group's access control sits **in front of** Gmail delivery: a domain-only posting policy silently drops an external sender's mail before it generates any Gmail delivery event at all, indistinguishable from a delivery failure without reading the policy directly. Requires the separate `apps.groups.settings` DWD scope (see Auth model below) | | `list_group_members` | Directory API — a Google Group's basic metadata and member roster, resolved directly rather than inferred from who happened to receive one particular message. Requires the separate `admin.directory.group.readonly` and `admin.directory.group.member.readonly` DWD scopes (see Auth model below) | | `daily_brief` | One-call summary across all configured domains | | `daily_brief_start` / `daily_brief_result` | Same as `daily_brief`, run in the background: `start` returns a `job_id` immediately, then poll `result(job_id)` until `done`. Use on large tenants where the synchronous call risks the client's ~60s tool-call timeout | Planned: `dlp_events` (Reports `rules`; requires a Workspace edition with DLP), `token_events`, `admin_events`. ## Auth model Service account with **domain-wide delegation (DWD)** impersonating an audit-capable admin. Fully non-interactive — no browser, no token refresh rotation — so the server runs unattended (cron, MCP gateway, CI). Grant **all** of the following DWD scopes on the same service-account client ID up front, in one setup pass. Adding them one at a time as each tool gets built is how a scope goes missing until the one tool that needed it starts degrading — one place, one pass, avoids the trap: | Scope | Needed by | Missing it | |-------|-----------|------------| | `https://www.googleapis.com/auth/admin.reports.audit.readonly` | `login_audit`, `user_activity`, `ip_activity`, `drive_external_sharing`, `drive_doc_activity`, `shared_drive_membership_changes`, `daily_brief*` | those tools degrade to a per-domain error | | `https://www.googleapis.com/auth/admin.directory.user.readonly` | `suspended_accounts`, `get_user` | those two tools degrade to an error (per-domain for `suspended_accounts`); everything else keeps working | | `https://www.googleapis.com/auth/admin.directory.user.security` | `user_oauth_tokens` | that tool degrades to a per-domain error; everything else keeps working | `health_check` needs no scope at all to respond: it is the tool to call when a grant might be missing — it probes each domain and reports the failing auth in a structured per-domain result instead of failing itself. `gmail_usage_report` needs its own separate scope too, despite living under the same Admin SDK Reports API as the base pass above — the "Usage report" family (`customerUsageReports`) and the "Audit" activity stream (`activities().list`, everything else in the base pass) are gated by two different scopes, and having one does not imply the other: | Scope | Needed by | Missing it | |-------|-----------|------------| | `https://ww
What people ask about gwsadm-mcp
What is shigechika/gwsadm-mcp?
+
shigechika/gwsadm-mcp is mcp servers for the Claude AI ecosystem. MCP server for Google Workspace security auditing — login audit, external sharing, daily brief It has 0 GitHub stars and its last recorded update is dated 2026-10-17.
How do I install gwsadm-mcp?
+
You can install gwsadm-mcp by cloning the repository (https://github.com/shigechika/gwsadm-mcp) or following the README instructions on GitHub. ClaudeWave also provides quick install blocks on this page.
Is shigechika/gwsadm-mcp safe to use?
+
Our security agent has analyzed shigechika/gwsadm-mcp and assigned a Trust Score of 87/100 (tier: Trusted). See the full breakdown of passed checks and flags on this page.
Who maintains shigechika/gwsadm-mcp?
+
shigechika/gwsadm-mcp is maintained by shigechika. The last recorded GitHub activity is dated 2026-10-17, with 10 open issues.
Are there alternatives to gwsadm-mcp?
+
Yes. On ClaudeWave you can browse similar mcp servers at /categories/mcp, sorted by popularity or recent activity.
Deploy gwsadm-mcp to your cloud
Ship this repo to production in minutes. Each platform spins up its own environment with editable env vars.
Maintain this repo? Add a badge to your README
Drop the badge into your GitHub README to show it's tracked on ClaudeWave. Each badge links back to this page and reflects the live Trust Score.
[](https://claudewave.com/repo/shigechika-gwsadm-mcp)<a href="https://claudewave.com/repo/shigechika-gwsadm-mcp"><img src="https://claudewave.com/api/badge/shigechika-gwsadm-mcp" alt="Featured on ClaudeWave: shigechika/gwsadm-mcp" width="320" height="64" /></a>More MCP Servers
Reverse engineer anything with agents, from app behavior down to native binaries.
Fair-code workflow automation platform with native AI capabilities. Combine visual building with custom code, self-host or cloud, 400+ integrations.
User-friendly AI Interface (Supports Ollama, OpenAI API, ...)
An open-source AI agent that brings the power of Gemini directly into your terminal.
Real-time global intelligence dashboard. AI-powered news aggregation, geopolitical monitoring, and infrastructure tracking in a unified situational awareness interface
🕷️ An adaptive Web Scraping framework that handles everything from a single request to a full-scale crawl! Don't be shy, join here: https://discord.gg/EMgGbDceNQ and follow here for daily tips and tricks: https://x.com/Scrapling_dev