Skip to content
verify mcp Beta VerifyMCP is currently in beta. If you notice any issues, get in touch and we’ll put it right.

Google Workspace Admin Security-Audit MCP

PYPI · GWSADM-MCP

MCP server for Google Workspace security auditing — login audit, external sharing, daily brief

Available components

+10 this week 84 Trust /100

Last scanned (18 hours ago)

Scores higher than 87% of PyPI servers

Category Security

Trust breakdown (7 categories)

How this component scores in each security and reliability category. Every signal is checked automatically from public evidence about the published package, including repeated runs of it in an isolated sandbox, and we only credit what we can confirm. How we score → Why this is hard to score →

Supply Chain Security100
  • No malware found by supply-chain analysis.Pass
  • No known CVEs affecting this package version or its production dependencies.Pass
  • Runs setuptools.build_meta at install time, a recognised build step with no custom scripting around it. View diagnostics → Pass
  • 1 of 47 dependencies flagged as unhealthy. View diagnostics → Partial
Provenance & Transparency97
  • Source repository is publicly reachable at the declared URL. View diagnostics → Pass
  • Cryptographically verified build provenance (signed, bound to shigechika/gwsadm-mcp). View diagnostics → Pass
  • Clear OSI-approved license (MIT).Pass
  • Actively maintained (last published 9 days ago).Pass
  • Disclosure check failed: no security disclosure policy was found in the source repository. See how to fix → Fail
Schema Quality & AI Usability57
  • AI-judged instruction clarity (excellent).Pass
  • Context-footprint check failed: tool/resource definitions use about 10288 tokens (~489/item across 21 items; 21 tools + 0 resources), over budget; trim descriptions and params. See how to fix → Fail
  • Usage-examples check failed: none of the tools include examples. See how to fix → Fail
Stability & Change Management67
  • Stability observed for 20 of 30 days with no destabilising changes; credit accrues until the full window elapses.Partial
Tool Coverage67
  • 100% of tools have a non-trivial description (not blank, and not just the tool's name).Pass
  • 0% of tool parameters carry a description.Fail
Tool Safety100
  • No prompt-injection markers were found in the server instructions, tool names or descriptions we captured.Pass
  • We read all 21 captured tool definition(s), and no name or description among them implies an irreversible operation.Pass
  • An AI judge read all 21 captured unit(s) of tool text and found none that tries to manipulate the model reading it.Pass
Capabilities100
  • Implements a current MCP spec version (2026-07-28).Pass
Changelog

Every change we have recorded for this component, newest first. Security-relevant changes are always shown. ▲ marks a change for the better, ▼ a change for the worse; unmarked changes are neutral.

  • 4 Nov 26 −5
    • Stability: pass → 0.67 functional
  • 30 Oct 26 +15
    • Malware scan: unverified → pass ▲ security
  • 27 Oct 26 −15
    • Malware scan: pass → unverified ▼ security
  • 26 Oct 26 +15
    • Stability: pass → unverified ▼ security
    • Tool safety: pass → unverified ▼ security
    • Malware scan: unverified → pass ▲ security
    • Capabilities: pass → unverified ▼ functional
    • Tool coverage: 100 → unverified ▼ functional
    • Schema quality: Schema quality not yet verified: we do not have a sandbox capture of the MCP schema this version of the package serves yet. functional
    • Package version: 0.23.0 → 0.23.1 functional
  • 24 Oct 26 0
    • Package version: 0.22.1 → 0.23.0 functional
  • 22 Oct 26 0
    • Stability: 0.97 → pass security
    • Schema quality: 9342 → 10290 ▼ functional
    • Package version: 0.21.0 → 0.22.1 functional
  • 21 Oct 26 −15
    • Malware scan: pass → unverified ▼ security
    • Stability: pass → 0.97 functional
  • 20 Oct 26 0
    • Package version: 0.18.0 → 0.21.0 functional
Diagnostics

Diagnostic detail from the automated scan of this channel: what the scanner observed at each step, so you can see exactly where a check passed or failed. It is informational only and never changes the trust score.

Captured 4 Nov 2026 · Analysed pypi/gwsadm-mcp@0.23.1

Provenance Verified

A signed build attestation was found and verified, binding this exact artifact to the source repository it claims to come from.

Result Verified
Ecosystem pypi
Reason Verified
Discovered via Registry attestation endpoint
Source repo shigechika/gwsadm-mcp
Certificate issuer https://token.actions.githubusercontent.com
Certificate SAN https://github.com/shigechika/gwsadm-mcp/.github/workflows/release.yml@refs/tags/v0.23.1
Rekor log index 3478654257
Predicate type PyPI publish attestation https://docs.pypi.org/attestations/publish/v1
Subject digest sha256:1383e1c7916eb447e8ef93b0d810f623a173c27711679bc30690e8dda3cc20db

Background: How many MCP packages publish verified provenance →

Install scripts 1 script
Hook Tier Command
build_backend allowlisted setuptools.build_meta

Background: Why install scripts are a supply-chain risk →

Dependencies 47 packages
Packages resolved 47
No linked repository 1
Tree resolution Complete

Background: SBOMs and build attestations, explained →

MCP tools · 21 exposed · ~10,288 tokens

The tools this component advertises to a client, with an estimated token cost for each. Expand a tool to see its parameters and schema. The per-tool counts are indicative and are not scored directly; the schema's total context footprint is one signal in Schema Quality & AI Usability. A tool's description is untrusted text the model reads on every call, which is what makes this list a security surface and not just an inventory: how tool poisoning works →

Tool Tokens
admin_actions ~701

What administrators did in the Admin console: to one account or group, or tenant-wide. Answers "did someone (IT staff, a sync job) suspend this account, reset its password, move it to another org unit, or take it out of a group?" from the Admin console audit log (Reports API ``applicationName=admin``). Each row names the ``actor`` (the administrator's address, or a profile id for a service account or sync client), ``actor_ip``, the ``event_type`` (``USER_SETTINGS``, ``GROUP_SETTINGS``, ...), the ``event`` (``SUSPEND_USER``, ``UNSUSPEND_USER``, ``CHANGE_PASSWORD``, ``CHANGE_USER_ORGANIZATION``, ``ADD_GROUP_MEMBER``, ``REMOVE_GROUP_MEMBER``, ``CREATE_USER``, ``DELETE_USER``, ``CHANGE_APPLICATION_SETTING``, ...) and its ``parameters`` as the API names them (``USER_EMAIL``, ``GROUP_EMAIL``, ``ORG_UNIT_NAME``, ``SETTING_NAME``, ``OLD_VALUE``, ``NEW_VALUE``, ...; long values cut at 300 characters). What the account did itself (its own password change, a second factor it enrolled) is not here: use ``user_activity``. With ``target`` (a user or group address), the domain is taken from its suffix and two server-side filters run, ``USER_EMAIL==<target>`` and ``GROUP_EMAIL==<target>``; an event of a bulk activity that names some other address is skipped and counted in ``sibling_events_skipped``, as is a sibling of another name when ``event`` is given. Without it, every admin action of the window is read (each configured domain, or ``domain``). ``event`` narrows to one event name. Per domain: ``events`` newest first (cut at ``max_events``, ``events_truncated``), ``by_event`` and ``by_actor`` counts over every matching event, ``capped`` (more pages existed) and ``errors`` (a probe that failed; a domain whose credentials failed is ``{"error": ...}``). ``capped`` at the top is set when any domain was capped or failed. Reports data can lag by hours, so an empty window right after the fact is "not visible yet", not "nothing happened". Read-only, the same ``admin.reports.audit.rea…

NameTypeReqDescription
domain–––
event–––
hoursinteger––
max_eventsinteger––
max_pagesinteger––
target–––

No output schema declared.

No examples provided.

compromise_sweep ~848

The Google-side follow-through after ONE account is confirmed compromised, in one call. Four questions, each answered from its own source and reported in its own section so that a failure in one never hides the others: - ``activity``: what the account did (``user_activity`` for the window, flags first -- a ``2sv_enroll`` from the attacker is the item that survives a password reset). A Reports failure stays in this section as ``error``; the others are still attempted with their own grants. - ``state`` / ``grants``: what the account is now (``get_user``: suspended, 2SV enrolled, last login) and which OAuth grants can read or act on content (``grants.risky``: mail / Drive / contacts / admin scopes; sign-in-only grants are listed but not called out). - ``ips``: whether the same sources reached other accounts (``ip_activity`` across every configured domain for the addresses in ``ips``, or, when none are given, for the addresses on the account's own flags -- which include the attacker's, and also the owner's own when they authorized an app or re-authenticated -- capped at SWEEP_MAX_IPS, newest first). The swept account itself is excluded from ``other_accounts``; a shared egress address (a campus NAT, a gateway host that impersonates accounts) legitimately shows other accounts, so read ``other_accounts`` against where the address sits. - ``gmail``: the settings an attacker leaves behind to keep reading mail (auto-forwarding, forwarding addresses, filters that forward / delete / archive, delegates, send-as aliases and reply-to) plus what the mailbox sent in the window as counts and recipient domains -- never a subject or body. Its ``kind`` says whether it could be read at all: ``ok``, ``partial`` (a settings section or the sent summary failed -- ``section_errors`` / ``sent.error`` say which; the rest stands), ``api_disabled`` (the Gmail API is off in the service account's own Cloud project), ``no_grant`` (no ``gmail.readonly``…

NameTypeReqDescription
domain–––
hoursinteger––
ips–––
max_pagesinteger––
max_sentinteger––
usernamestringyes–

No output schema declared.

No examples provided.

daily_brief ~398

One-call security summary across all configured domains. Aggregates login_audit (account locks, suspicious logins), account-protection changes (``account_changes``: 2-Step Verification enrolled/disabled, passkeys added/removed, Advanced Protection turned off, password and recovery address/phone edits, mail forwarding out of the domain; each row says whether it came from outside the ``GWSADM_SITES_INI`` table and from which country (``GWSADM_IP2ASN``), and is ``flagged`` with ``flag_reasons`` -- ``foreign`` (outside ``GWSADM_HOME_COUNTRIES``), ``unknown_origin``, ``after_suspicious_login`` -- flagged rows first, at most 50 listed, ``total`` counts all; ``suspicious_login_coverage`` says whether the last reason is complete) and drive_external_sharing (external grants, new link exposure, and ``untargeted_external_transitions`` — see that tool's docstring). ``max_pages`` / ``samples`` are passed through to the drive scan; ``max_pages`` defaults to the same page budget as the standalone tool, so both report the same counters for the same window (``samples`` defaults lower here and only trims the example lists). Per-domain ``capped`` in the summary means at least one underlying scan was partial — treat that domain's counts as lower bounds (see ``capped_events`` in the drive section for which probes were cut short). Synchronous: on a large tenant this can exceed a client's ~60s tool-call timeout. If it does, use ``daily_brief_start`` + ``daily_brief_result`` (same result, run in the background) or lower ``max_pages``.

NameTypeReqDescription
hoursinteger––
max_pagesinteger––
samplesinteger––

No output schema declared.

No examples provided.

daily_brief_result ~91

Fetch a ``daily_brief_start`` job by id. ``status`` is ``running`` (keep polling), ``done`` (``result`` holds the full daily_brief payload), ``error`` (``error`` holds the exception type name — the message is omitted to avoid leaking internal detail), or ``unknown`` (bad/expired id).

NameTypeReqDescription
job_idstringyes–

No output schema declared.

No examples provided.

daily_brief_start ~161

Start a daily_brief in the background; returns immediately with a ``job_id``. Use this instead of ``daily_brief`` when the synchronous call risks the client's ~60s tool-call timeout (large tenants). Args mirror ``daily_brief``. You MUST then poll ``daily_brief_result(job_id)`` every few seconds until ``status`` is ``done`` (the full daily_brief payload is under ``result``) or ``error``. On a config error returns ``{"error": ...}``; if too many jobs are already active returns ``{"status": "rejected", ...}``.

NameTypeReqDescription
hoursinteger––
max_pagesinteger––
samplesinteger––

No output schema declared.

No examples provided.

dmarc_rua_summary ~956

Summarize DMARC aggregate (RUA) reports: pass/fail per domain, top reject-candidate IPs. Impersonates the domain's configured ``dmarc_rua_mailbox`` (a real user -- domain-wide delegation cannot act as a group or alias; config default ``postmaster@<domain>``), searches it for mail addressed to ``dmarc_rua_recipient`` (the ``rua=mailto:`` address published in DNS, e.g. the ``postmaster+rua@`` plus-subaddress; default: same as the mailbox), reads the aggregate reports those messages carry, and answers "how much of this domain's mail volume is passing DMARC, and if we moved the policy to ``p=reject``, what would actually get blocked?" A record counts as PASS when either its aligned DKIM or SPF check reads "pass" (DMARC's own OR semantics) — NOT when both do. A record counts as a reject candidate when it is already quarantined or rejected (a tenant already running `p=quarantine`/`p=reject` enforces this today), or when BOTH checks fail with `disposition="none"` (what a stricter policy would newly start blocking); everything else is a pass. This distinction matters a lot in practice: a large share of legitimate mail (mailing-list forwards, some relays) passes DMARC via SPF alignment alone with DKIM unaligned, so counting on DKIM alone overstates the failure rate severalfold. ``reject_candidate_ips`` (the top ``top`` per domain by volume) is where to actually look before flipping a policy to ``p=reject``: a header_from that names a SUBDOMAIN of the audited domain is governed by that subdomain's own ``sp=`` policy, not the parent's ``p=``, so it is not necessarily what a `p=reject` change on the parent would affect. Requires the ``gmail.readonly`` DWD scope — the same one ``gmail_message_trace`` needs, granted PER SERVICE ACCOUNT CLIENT ID in the Admin console (Security > API controls > Domain-wide delegation), separately from the ``admin.directory.*`` / ``admin.reports.*`` scopes the rest of this server uses. Read-only: only ``messages().list``/``messages().get``/ ``…

NameTypeReqDescription
domain–––
hoursinteger––
mailbox–––
max_pagesinteger––
recipient–––
topinteger––

No output schema declared.

No examples provided.

drive_doc_activity ~446

Full audit history of one Drive document: owner, ACL changes, lifecycle. The triage companion to ``drive_external_sharing``: a sharing finding names a ``doc_id``, and judging it requires who OWNS the document (an individual user vs. a shared drive completely changes the risk read) and its grant history over time. Uses a server-side ``doc_id`` filter, so the page budget is spent on this one document only. ``owner`` / ``doc_title`` are taken from the document's own events (a shared-drive-owned file reports the drive's name — not a user address — as owner). ``events`` lists ACL and lifecycle events newest-first (view/edit/download noise is counted in ``event_counts`` but not listed); ``events_truncated`` is set when more matched than ``max_events``. The ``doc_id`` filter matches at the ACTIVITY level and one activity can carry sibling events for OTHER documents (a multi-file share is one activity with one event per file) — events whose own ``doc_id`` parameter does not match (or is absent) are excluded from every output field and tallied in ``sibling_events_skipped`` instead, so a bulk action cannot contaminate this document's history or misattribute its owner. An empty result means no events in the window for the queried tenant — NOT proof the document does not exist (history older than the Reports API retention, or a document living in a different tenant, looks the same). Args: doc_id: Drive document id (from a sharing finding's ``doc_id`` field). days: How far back to scan (Reports API retains roughly 6 months). domain: Restrict to one configured domain/tenant (default: all). max_pages: Reports page cap per domain; ``capped=true`` means more existed. max_events: Cap on listed events (counts are unaffected).

NameTypeReqDescription
daysinteger––
doc_idstringyes–
domain–––
max_eventsinteger––
max_pagesinteger––

No output schema declared.

No examples provided.

drive_external_sharing ~819

Report Drive ACL grants to external targets and new link/public exposure. Counts grants whose target (``target_user`` address, or ``target_domain`` for domain-scoped grants) is outside the configured internal domains (revocations are reported separately, not as exposure) and visibility transitions into link/public access (``people_with_link`` / ``public_on_the_web``, excluding a narrowing from public down to link-only; Google's ``shared_externally`` is owner-domain relative, so external-ness is judged by the target instead). ``untargeted_external_transitions`` counts transitions into ``shared_externally`` with no target address or domain (e.g. scope became "anyone with the link" — ``target_domain: "all"`` — or an unresolved target); it is a residual bucket, not a cross-check for missed named grants, since domain-scoped grants are already classified above. ``external_samples`` / ``exposure_samples`` / ``untargeted_samples`` hold examples of each. A self-grant of ``owner`` on ``change_user_access`` /``change_acl_editors`` (a user creating their own new file — every Form/Sheet/Doc submission does this) is excluded from ``risky_visibility_events``: it always reports a visibility transition from a missing prior state, which is indistinguishable from a genuine narrow-to-wide exposure event by visibility fields alone, but is not exposure of anything pre-existing. When no ``target_user`` is named (the ``change_acl_editors`` shape), a missing target alone is not proof of creation — an admin bulk-transferring ownership of a pre-existing, already-shared file (e.g. offboarding) looks the same — so this case is corroborated against the event's own ``owner`` parameter (self-action only if ``owner`` matches the actor); if ``owner`` itself is absent the conservative default is to count it rather than risk dropping a real ownership change. This exclusion is deliberately never applied to ``change_document_access_scope``/``change_document_visibility`` (see ``SELF_CREATION_GRANT_EVEN…

NameTypeReqDescription
domain–––
hoursinteger––
max_pagesinteger––
samplesinteger––

No output schema declared.

No examples provided.

get_user ~577

Look up ONE named account's current state — the "why can't this person sign in" tool. Answers a helpdesk ticket that already names the exact address: is the account suspended (and for what reason, since when), archived, enrolled in or enforced into 2-step verification, when did it last log in, which org unit is it in, is a password change pending. One Directory API request, no pagination. Use this — not ``suspended_accounts`` — whenever the address is known. That tool 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 — after spending far more API calls than this. ``suspended_accounts`` is for the domain-wide sweep it is actually named for. This directory is downstream of the identity provider, not the master. Read the answer as "what Google Workspace currently believes about this account" and compare it against the IdP's own record, which is authoritative for who the account is. A disagreement is usually drift on this side rather than a mistyped address — an account the IdP still authenticates can be suspended or archived here, and an address the IdP does not assert at all will simply come back ``found: false``. An address that names no account returns ``found: false`` with no state fields. That is a normal, expected answer — a typo'd or long-deleted address — and is itself the diagnostic result, NOT a failure. A missing DWD scope, a rejected credential or a transient API failure is reported as ``{"error": ...}`` instead. The two are deliberately distinct: never read ``found: false`` as "the lookup did not work", and never read an ``error`` as evidence about whether the account exists. Read-only (Directory API ``users().get``; this tool never changes the account). Requires the ``admin.directory.user.readonly`` DWD scope — the same one ``suspended_accounts`` uses, so a tenant already running that tool needs no additional grant.…

NameTypeReqDescription
domain–––
usernamestringyes–

No output schema declared.

No examples provided.

gmail_message_trace ~602

Check whether a message (by RFC 822 Message-ID) reached specific users' mailboxes. Answers "who got this email and who didn't" for a KNOWN Message-ID and a KNOWN candidate recipient list — there is no Workspace API to search across every user for one message, so the caller supplies who to check (a mailing-list roster, or simply the people who reported a problem). For each recipient this impersonates that exact user via domain-wide delegation and searches their own mailbox (including Spam and Trash) for the Message-ID. Requires the ``gmail.readonly`` DWD scope — granted PER SERVICE ACCOUNT CLIENT ID in the Admin console (Security > API controls > Domain-wide delegation), separately from the ``admin.directory.*`` / ``admin.reports.*`` scopes the rest of this server uses, and NOT on by default. A domain missing that grant reports a per-recipient ``error`` rather than a silent "not found" — the two must never be confused, since "not found" here can also legitimately mean the message was delivered and later deleted by the user, or never delivered at all; this tool cannot tell those apart, only "a match currently exists in this mailbox" from "it doesn't". Read-only: only ``messages().list`` and ``messages().get`` (metadata only, never the message body) are issued against each impersonated mailbox — see ``DomainClient.find_message_by_id``. A per-recipient result sets ``ambiguous: true`` (with ``match_count``) when more than one message in that mailbox shares the Message-ID (e.g. a mailing-list copy plus a direct CC) — the other fields describe only the first match in that case, not a combined answer. ``match_count_capped`` is set alongside it when the mailbox has enough matches that ``match_count`` itself is a lower bound, not exact. Args: message_id: The RFC 822 Message-ID to search for, with or without angle brackets. Must be shaped like an address (``local@domain``, no whitespace) — this is validated before use, since it is interpolated…

NameTypeReqDescription
domain–––
message_idstringyes–
recipientsstringyes–

No output schema declared.

No examples provided.

gmail_usage_report ~589

Daily Gmail send/receive counts per domain (Admin SDK customerUsageReports). Answers "how much Gmail traffic did this domain send/receive on day X" -- a customer-level daily counter, NOT a per-user or per-message breakdown (the Reports API has no such thing; ``login_audit``'s activity stream is the per-event alternative for that, at the cost of no volume total). Each day's fetch is independent: a domain missing the DWD scope reports one whole-domain ``error`` (there is no point retrying `days` times for an identical scope failure), but a single date's fetch failing for some other reason (Google's processing for that day not finished yet is a known lag on this API family; a transient error) only skips that one date, recorded in ``date_errors`` -- the rest of the window still comes back. The per-domain result also carries ``capped`` -- true if ANY date's fetch hit the (rare, since a single day's customer report is normally one record) pagination limit -- so a truncated day's counters are never mistaken for the complete picture. Requires the ``admin.reports.usage.readonly`` DWD scope, granted PER SERVICE ACCOUNT CLIENT ID in the Admin console (Security > API controls > Domain-wide delegation) -- a DIFFERENT scope from ``admin.reports.audit.readonly`` (the one ``login_audit``, ``drive_external_sharing``, ``drive_doc_activity``, ``shared_drive_membership_changes`` and ``daily_brief`` use), even though both live under the same Admin SDK Reports API. Having one does not imply the other; grant this one separately. Read-only: only ``customerUsageReports().get()`` is issued, restricted to the two Gmail counters this tool reports via the API's own ``parameters`` filter (:data:`GMAIL_USAGE_PARAMETERS`) rather than pulling every application's counters. Args: days: How many days back to report, ending YESTERDAY (not today -- the current day's data is not final until it ends) in the Reports API's own UTC-8:00/Pacific-Standard-Time date anchor. Mus…

NameTypeReqDescription
daysinteger––
domain–––

No output schema declared.

No examples provided.

group_delivery_policy ~392

Check a Google Group's own posting/delivery policy — why an external sender's mail never arrived. A Group's access-control layer sits IN FRONT of Gmail delivery: when ``who_can_post`` is restricted (e.g. domain-members-only), an external sender's message is rejected there and never generates a per-recipient Gmail delivery event at all — ``gmail_message_trace`` (a real mailbox) and any Reports-API-based delivery trace both see nothing for that address, indistinguishable from a genuine delivery failure without this. Use this FIRST when a group address "isn't receiving" mail from an external sender, before chasing it as a transport/spam problem. Read-only: only ``groups().get()`` is issued (Groups Settings API). Requires the ``apps.groups.settings`` DWD scope — granted PER SERVICE ACCOUNT CLIENT ID in the Admin console (Security > API controls > Domain-wide delegation), separately from every other scope this server uses, and NOT on by default. Returns ``who_can_post`` (e.g. ``ALL_IN_DOMAIN_CAN_POST`` blocks external senders entirely; ``ANYONE_CAN_POST`` allows them), ``allow_external_members``, ``is_archived``, ``message_moderation_level``, ``spam_moderation_level``, ``allow_web_posting``. Sets ``found: false`` (no policy fields) when ``group_email`` does not name any group in this domain — that is a normal, expected answer for a bad/typo'd address, not an ``error``. Args: group_email: The group's address (e.g. "team.gen@example.edu"). domain: Configured ``[domain.*]`` section to route the lookup through. Default: resolved from the address's suffix.

NameTypeReqDescription
domain–––
group_emailstringyes–

No output schema declared.

No examples provided.

health_check ~180

Report service status, version, and per-domain auth/API reachability. Always returns the same keys: status (healthy / degraded / error), service, version, config, and domains (per-domain auth result plus ``gmail``: ok / api_disabled / no_grant / error, with ``gmail_detail`` on an unexpected error -- whether the per-user Gmail tools can work there; it does not affect ``status``), ``sites`` (the optional ``GWSADM_SITES_INI`` table: configured / ranges / error) and ``geo`` (the optional ``GWSADM_IP2ASN`` dataset: configured / home_countries / age_days / error / warning); both are informational and do not affect ``status``. Lightweight: one 1-item login query and one ``users.getProfile`` per domain.

Input schema present but exposes no named parameters.

No output schema declared.

No examples provided.

ip_activity ~485

Every account ONE source address acted as, across applications and domains. The follow-through after ``user_activity`` names an attacker's address: did the same source get into other accounts? The Reports API filters on ``actorIpAddress`` server-side, so this is one probe per (domain × application) over the matching events only, not a scan of the whole tenant's log — and every domain's probes run in one pool, so a slow domain does not delay another's answer. Per domain it returns the acting ``accounts`` (count, first/last seen, which applications) ordered by most recent, the posture ``flags`` the address triggered (same definition as ``user_activity``; Google's own verdicts are included here when they carry the address, since ``userKey=all`` sees them), and per-application accounting. Failed logins from the address appear under ``applications.login.events`` as ``login_failure`` — a spray from this address shows there before it ever succeeds. Coverage reads as in ``user_activity``: ``window.start``, per-application ``capped`` / ``error`` (and the union ``capped``, a failed application included), and a per-domain ``error`` when that domain's credential is rejected (the other domains still answer). A quiet answer for a current-day window can be data lag rather than absence. Read-only (Reports API ``activities().list`` only, the ``admin.reports.audit.readonly`` DWD scope). Args: ip: The source address, IPv4 or IPv6 literal (no zone id). Validated before use. hours: Window length ending now. applications: Comma-separated application names; default as in ``user_activity``. max_pages: Page budget per (domain × application). top: Accounts returned per domain, most recently seen first; ``accounts_total`` says how many there were. domain: Restrict to one configured ``[domain.*]`` section. Default: every configured domain, since an address is not tied to one.

NameTypeReqDescription
applications–––
domain–––
hoursinteger––
ipstringyes–
max_pagesinteger––
topinteger––

No output schema declared.

No examples provided.

list_group_members ~599

List a Google Group's basic metadata and member roster (Directory API). Resolves a group's actual membership directly, independent of any specific message ever having been sent to it — unlike inferring membership from Reports API delivery-event fanout (``applicationName=gmail``), which only shows members who received one PARTICULAR message and requires one to already exist to trace. Pair with ``gmail_message_trace`` to deep-dive a specific member's mailbox once the roster is known, or with ``group_delivery_policy`` to see why the group as a whole may not be receiving mail at all. Read-only: only ``groups().get()`` and ``members().list()`` are issued (Directory API), never a mutating call. Requires the ``admin.directory.group.readonly`` and ``admin.directory.group.member.readonly`` DWD scopes — granted PER SERVICE ACCOUNT CLIENT ID in the Admin console, separately from every other scope this server uses, and NOT on by default. The two calls are independent: a tenant with only one of the two scopes granted still gets that one section, with the other reported as ``{"error": ...}`` in its place rather than failing the whole call — only when BOTH fail does the tool return a single top-level ``error``. Sets ``found: false`` (no ``group``/``members`` sections) when ``group_email`` does not name any group in this domain — a normal, expected answer for a bad/typo'd address, not an ``error``. This triggers both when BOTH calls agree with no error on either side, AND when one call CONFIRMS not-found while the other independently failed (its own error is then attached as ``group_lookup_error`` / ``members_lookup_error``) — a confirmed non-existence from one independently-scoped call is stronger evidence than an unrelated failure on the other, and must not be buried under it. Args: group_email: The group's address. domain: Configured ``[domain.*]`` section to route the lookup through. Default: resolved from the address's suffix. max_pages: Pagination cap…

NameTypeReqDescription
domain–––
group_emailstringyes–
max_pagesinteger––

No output schema declared.

No examples provided.

login_audit ~315

Audit the login log: Google-auto-disabled accounts, suspicious logins, failure top-N. Answers "did Google itself decide something was wrong with an account here, and with which account?" — the question a "my mail suddenly stopped working" ticket usually turns out to be. ``account_disabled_*`` is Google reporting that it locked an account (leaked password, hijacking, spamming); ``suspicious_login`` and ``gov_attack_warning`` are warnings without a lock. In both sections ``user`` is the account the event is ABOUT, not an actor who did something: Google raised these itself, so the actor is Google and the account is read from the event's ``affected_email_address``. Treat a hit as evidence about that account, and reach for ``get_user`` next for its current state. An ``account_disabled_spamming`` entry means Google observed outbound spam, which is a compromise finding, not a delivery problem — the account was almost certainly being used by someone else. The IdP upstream is a separate system and is NOT disabled by this: an account locked here can still authenticate there until it is disabled there too. Combine with a Directory suspended-users snapshot (Phase 2) for current state. Each section carries ``capped`` (window not fully scanned) — treat counts as lower bounds then.

NameTypeReqDescription
domain–––
hoursinteger––
include_failuresboolean––
topinteger––

No output schema declared.

No examples provided.

shared_drive_membership_changes ~366

Membership add/remove/role-change history across shared drives. Answers "who added this (external) member, and when" — the other half of triaging a shared-drive sharing finding (see ``drive_doc_activity``). Membership changes are low-volume, so a plain window scan of the single ``shared_drive_membership_change`` event works even over months. Each entry's ``drive`` is the shared drive's NAME as the audit log reports it (the event's ``owner`` parameter — not an id, not a user address); ``target_is_external`` classifies the affected member against the configured internal domains. The Reports API cannot filter by drive server-side, so ``drive_name`` is a client-side case-insensitive substring match on that name — it narrows the listing, not the scan. An event whose drive name is absent can neither match nor be ruled out; with ``drive_name`` set such events are excluded from ``total``/``entries`` but tallied in ``missing_drive_name`` so the drop is never silent (without ``drive_name`` they are listed normally with ``drive: null``). Args: days: How far back to scan (Reports API retains roughly 6 months). domain: Restrict to one configured domain/tenant (default: all). drive_name: Only list entries whose drive name contains this substring. max_pages: Reports page cap per domain; ``capped=true`` means more existed. max_events: Cap on listed entries (``total`` counts all matches).

NameTypeReqDescription
daysinteger––
domain–––
drive_name–––
max_eventsinteger––
max_pagesinteger––

No output schema declared.

No examples provided.

suspended_accounts ~311

Snapshot of currently suspended Google Workspace accounts, per domain. A suspended-but-still-provisioned account is a common attack surface: an account disabled in Google may remain enabled in a downstream IdP (e.g. KeyCloak), where a password-spray attacker can still authenticate through it. Cross-reference this list against the IdP to find and disable such gaps. Unlike ``login_audit`` (which reports the *event* of Google disabling an account within a time window), this is current *state* — every account suspended right now, regardless of when. Read-only (Directory API ``users().list`` with ``query=isSuspended=true``). Requires the ``admin.directory.user.readonly`` DWD scope; a domain missing that grant degrades to ``{"error": ...}`` for that domain only. ``capped`` is set when ``max_pages`` was hit before the listing was exhausted. Coverage is per configured domain (Directory ``domain=`` filter), unlike the customer-wide Reports tools — every domain you want covered (e.g. a separate student domain) must have its own ``[domain.*]`` config section, or its suspended accounts are not listed. Args: domain: Restrict to one configured domain (default: all). max_pages: Page cap (500 accounts/page); ``capped=true`` means more exist.

NameTypeReqDescription
domain–––
max_pagesinteger––

No output schema declared.

No examples provided.

user_activity ~885

Everything ONE account did across Workspace in a window, newest first. The first question after an upstream identity provider reports an account as compromised: did the attacker get into Google, and what did they do there? ``login_audit`` cannot answer it — it is a domain-wide view of the events Google itself raised, so it shows neither one named account's logins nor the logins Google did not flag. This tool asks the Reports API for that one account (``userKey=<username>``, server-side) across several applications at once and merges the answers. Read ``flags`` first. It holds the events that change the account's security posture or mark it as under attack, regardless of how much ordinary activity surrounds them: a second factor enrolled or disabled (``2sv_enroll`` survives a password reset — it is the attacker's way back in), recovery email/phone changes, a password change, outbound mail forwarding to another domain, Google's own ``risky_sensitive_action_*`` verdicts, a login Google marked ``is_suspicious``, and every OAuth ``authorize`` (client and scopes — note that a service account impersonating the user through domain-wide delegation, this server's own Gmail tools included, appears here too, as an ``authorize`` whose ``client_id`` is the service account's numeric id). Google's ``account_disabled_*`` / ``suspicious_login`` / ``gov_attack_warning`` verdicts are flags too, but they are NOT served under the account's own ``userKey`` (their actor is Google), so they are fetched separately the way ``login_audit`` does and reported under the synthetic application ``login_verdicts`` — its ``event_errors`` names any verdict probe that failed. ``ips`` lists the distinct source addresses with first/last seen and which applications each touched — the input to ``ip_activity`` for the "did the same source reach other accounts" question. ``timeline`` is the merged event list (Drive read/edit/download noise is counted under ``applications.drive.events`` but kept off the tim…

NameTypeReqDescription
applications–––
domain–––
hoursinteger––
max_eventsinteger––
max_pagesinteger––
usernamestringyes–

No output schema declared.

No examples provided.

user_oauth_tokens ~320

List third-party OAuth apps one user has granted account access to. Account-compromise triage tool for the case ``login_audit`` and ``suspended_accounts`` are both blind to: a malicious app used a previously-granted OAuth token to read/delete mail or Drive files without ever generating a fresh login event. Check each entry's ``scopes`` for Gmail/Drive access on an unrecognized ``client_id``/``display_text`` — Google's own apps (e.g. iOS/Android account sync) show up too and are normal noise. Read-only (Directory API ``tokens().list``; never ``tokens().delete()``). Requires the ``admin.directory.user.security`` DWD scope — distinct from ``admin.directory.user.readonly`` used by ``suspended_accounts``; a domain missing that grant returns ``{"error": ...}``. No pagination: the API returns a user's full grant list in one response. Args: username: Exact user email, passed through as the Directory API ``userKey`` (primary or alias address both work on Google's side). domain: Configured ``[domain.*]`` section to route the lookup through. Default: resolved from the username's suffix. Set it explicitly when the address uses an alias/secondary domain that has no config section of its own (common when copying addresses from mail headers or IdP logs).

NameTypeReqDescription
domain–––
usernamestringyes–

No output schema declared.

No examples provided.

workspace_status ~247

Is a Google Workspace service down? Open and recent incidents from Google's status dashboard. Reads the public Google Workspace Status Dashboard feed (no credential, no DWD scope): the incidents Google has declared for Gmail, Drive, Meet, Calendar, Chat and the other Workspace products. ``open`` lists the incidents not yet resolved; ``recent`` those that ended within the last ``hours``. Each row: ``service``, ``affected_products``, ``severity``, ``status_impact``, ``begin`` / ``end`` (UTC), ``summary`` (the latest update's summary line), ``last_update`` and the incident's ``url``. The dashboard is global: an empty answer means Google has declared no incident, not that this tenant cannot be affected, and a local problem (a network path, one account) never appears here. Args: hours: How far back ``recent`` reaches (at most 2160, 90 days). service: Keep incidents whose service or affected product name contains this text (case-insensitive), e.g. ``Gmail``.

NameTypeReqDescription
hoursinteger––
service–––

No output schema declared.

No examples provided.

Install

How do I install the Google Workspace Admin Security-Audit MCP server?

Google Workspace Admin Security-Audit MCP runs locally as a PyPI package, launched with uvx gwsadm-mcp. Ready-made configuration for Claude, Cursor, VS Code, Codex and 5 more is on this page, copied from each client's own documentation.

pypi · gwsadm-mcp

# add to Claude Code
claude mcp add shigechika-gwsadm-mcp -- uvx gwsadm-mcp
Common questions

What is the Google Workspace Admin Security-Audit MCP server?

Google Workspace Admin Security-Audit MCP is listed in the public MCP registry as io.github.shigechika/gwsadm-mcp. MCP server for Google Workspace security auditing, login audit, external sharing, daily brief. This page covers its PyPI package (gwsadm-mcp).

Is the Google Workspace Admin Security-Audit MCP server safe to use?

Google Workspace Admin Security-Audit MCP scores 84 out of 100 on VerifyMCP. We found no known CVEs affecting it as of 4 November 2026. Its build provenance is signed and verified. That is a record of what we were able to check automatically, not an endorsement. The category breakdown on this page shows every signal behind the number, including the ones we could not confirm.

What tools does the Google Workspace Admin Security-Audit MCP server expose?

Google Workspace Admin Security-Audit MCP exposes 21 tools: health_check, login_audit, gmail_usage_report, suspended_accounts, get_user, and 16 more. Their descriptions and schemas cost roughly 10,288 tokens of context every time the server is loaded.

Is the Google Workspace Admin Security-Audit MCP server still maintained?

Google Workspace Admin Security-Audit MCP is still listed as active in the MCP registry. We last reached this channel on 4 November 2026. Those dates come from our own scans of the registry and the channel itself, not from anything the publisher announced.

What licence is the Google Workspace Admin Security-Audit MCP server under?

Google Workspace Admin Security-Audit MCP declares the MIT licence, which is OSI-approved. That covers the source only, and says nothing about the cost of any service it calls.