MCP server for Google Workspace security auditing — login audit, external sharing, daily brief
Project description
gwsadm-mcp
English | 日本語
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. 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 |
drive_external_sharing |
Reports API drive — ACL grants to external addresses or domains (revocations reported separately) and visibility transitions into link/public exposure |
daily_brief |
One-call summary across all configured domains |
Planned: dlp_events (Reports rules; requires a Workspace edition with DLP),
suspended_accounts (Directory API snapshot; requires the
admin.directory.user.readonly DWD scope), 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).
Required DWD scope per service-account client ID:
https://www.googleapis.com/auth/admin.reports.audit.readonly
Setup
# uv
uv pip install gwsadm-mcp
# pip
pip install gwsadm-mcp
Or from source:
git clone https://github.com/shigechika/gwsadm-mcp.git
cd gwsadm-mcp
# uv
uv sync
# pip
pip install -e .
Configuration
Point GWSADM_CONFIG at an INI file (default ~/.config/gwsadm-mcp/config.ini,
keep it 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 = audit-admin@example.edu
customer_id = C0xxxxxxx
One [domain.*] section per audited Workspace domain. internal_domains is
the allowlist used to classify sharing targets as internal vs external.
Usage
Claude Code
Add to .mcp.json (no env needed when the config lives at the default path;
add "env": { "GWSADM_CONFIG": "..." } only for a non-default location):
{
"mcpServers": {
"gwsadm-mcp": {
"type": "stdio",
"command": "gwsadm-mcp"
}
}
}
Claude Desktop
Add the same entry to claude_desktop_config.json.
Direct Execution
gwsadm-mcp
CLI Options
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 exit codes: 0 success, non-zero on config or auth failure.
Notes
- Every result section reports
capped: truewhen a window exceeded the page budget, or when a probe's fetch errored outright (seeevent_errors) — partial coverage is never presented as "no findings". The drive scan also reportscapped_events(which eventNames were cut short). Narrowhoursor raisemax_pagesfor full coverage — on a large tenant, term-time weekdays can produce thousands ofchange_user_accessevents/day. - Google's
visibility=shared_externallyis relative to the file owner's domain, so with multipleinternal_domainsa cross-internal-domain grant (e.g. student domain → staff domain) carries it too. External-ness is therefore judged againstinternal_domainsusing the grant's target:target_userfor named grants,target_domainfor domain-scoped grants (e.g. "anyone at partner.edu"; the literal domain"all"means "anyone with the link" and is judged by visibility instead).risky_visibility_eventscounts only transitions intopeople_with_link/public_on_the_web(excluding a narrowing from public down to link-only).untargeted_external_transitionsis a residual bucket for transitions intoshared_externallywith no target address or domain to classify — it is not a cross-check for grants missed elsewhere, since domain-scoped grants are already counted above.external_samples/exposure_samples/untargeted_sampleshold examples of each. - Drive events are queried one audit-relevant eventName at a time, so the
page budget is not consumed by view/edit noise; an event name rejected by the
API degrades into
event_errorsinstead of failing the tool.change_document_visibilityandchange_document_access_scopereport the same transition as simultaneous sibling events on this API — only the latter drives classification (the former is fetched for itsacl_eventscount only), so a domain-scoped grant or a link/public exposure is never double-counted across the two. This also means the former can no longer compensate if the latter's own fetch fails: achange_document_access_scopeentry inevent_errorssetscapped: truefor that domain, and its classification counts for the window are a lower bound even thoughchange_document_visibility(and thusacl_events) may show data. - A failure in one domain degrades only that domain's section (
{"error": ...}). - Read-only by design; the only API call issued is
activities().list. - Output contains account addresses (that is the point of an audit tool): restrict access to authorized security staff.
Development
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 .
Releasing
Releases are automated with release-please.
Merging Conventional Commits (feat:, fix:, …)
to main keeps a release PR open with the next version and changelog. Merging
that PR tags vX.Y.Z and publishes a GitHub Release, whose release: published
event triggers the release workflow to build and publish to PyPI and the MCP
Registry. release-please owns the version in gwsadm_mcp/__init__.py and
server.json (do not bump them by hand).
[!IMPORTANT] The release-please workflow should be given a repository secret
RELEASE_PLEASE_TOKEN(a PAT withcontents: write+pull-requests: write). The defaultGITHUB_TOKENcannot create the Release that triggers the downstreamreleaseworkflow (GitHub blocks workflow runs triggered byGITHUB_TOKEN), so without the PAT nothing gets published. The workflow falls back toGITHUB_TOKENwhen the secret is unset so PR CI keeps working on forks.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file gwsadm_mcp-0.2.1.tar.gz.
File metadata
- Download URL: gwsadm_mcp-0.2.1.tar.gz
- Upload date:
- Size: 28.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
817f9753397ce1b3379fbc243cd107c1243791ff8f774bad5f47a1ae3c9cb4aa
|
|
| MD5 |
d77d8f46077bea9c737ae552fd24c119
|
|
| BLAKE2b-256 |
c9336123303c76a388bf0961dc123caf185a8c24173182d8a6c48aaea5402348
|
Provenance
The following attestation bundles were made for gwsadm_mcp-0.2.1.tar.gz:
Publisher:
release.yml on shigechika/gwsadm-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
gwsadm_mcp-0.2.1.tar.gz -
Subject digest:
817f9753397ce1b3379fbc243cd107c1243791ff8f774bad5f47a1ae3c9cb4aa - Sigstore transparency entry: 2116832115
- Sigstore integration time:
-
Permalink:
shigechika/gwsadm-mcp@03f62fc58007f1e527163f93f1282cccb79a0cc9 -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/shigechika
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@03f62fc58007f1e527163f93f1282cccb79a0cc9 -
Trigger Event:
release
-
Statement type:
File details
Details for the file gwsadm_mcp-0.2.1-py3-none-any.whl.
File metadata
- Download URL: gwsadm_mcp-0.2.1-py3-none-any.whl
- Upload date:
- Size: 19.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9bfd291f480cf3c6cb9a853ae759b6df4ef99046b8217bebde413768adf44992
|
|
| MD5 |
fbe25a691475a69451cf7276e43f91fb
|
|
| BLAKE2b-256 |
7cde9fbcd15c40af880439db7af5099f097920287be4dd346889fdf5af8d7802
|
Provenance
The following attestation bundles were made for gwsadm_mcp-0.2.1-py3-none-any.whl:
Publisher:
release.yml on shigechika/gwsadm-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
gwsadm_mcp-0.2.1-py3-none-any.whl -
Subject digest:
9bfd291f480cf3c6cb9a853ae759b6df4ef99046b8217bebde413768adf44992 - Sigstore transparency entry: 2116832193
- Sigstore integration time:
-
Permalink:
shigechika/gwsadm-mcp@03f62fc58007f1e527163f93f1282cccb79a0cc9 -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/shigechika
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@03f62fc58007f1e527163f93f1282cccb79a0cc9 -
Trigger Event:
release
-
Statement type: