shigechika

gwsadm-mcp

Community shigechika
Updated

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

gwsadm-mcp

English | 日本語

Google Workspace security-audit MCP (Model Context Protocol) server —read-only visibility into account locks, suspicious logins, and external filesharing, built on the Admin SDK Reports API (audit activities).

Named after the admin-console viewpoint (gwsadm = Google Workspace admin),sibling of boxadm-mcp. This isnot a general-purpose Workspace MCP: it surfaces risk, it never mutatesanything.

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 theadmin.directory.user.readonly DWD scope), token_events, admin_events.

Auth model

Service account with domain-wide delegation (DWD) impersonating anaudit-capable admin. Fully non-interactive — no browser, no token refreshrotation — 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 = [email protected]
customer_id = C0xxxxxxx

One [domain.*] section per audited Workspace domain. internal_domains isthe 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: true when a window exceeded the pagebudget, or when a probe's fetch errored outright (see event_errors) —partial coverage is never presented as "no findings". The drive scan alsoreports capped_events (which eventNames were cut short). Narrow hoursor raise max_pages for full coverage — on a large tenant, term-timeweekdays can produce thousands of change_user_access events/day.
  • Google's visibility=shared_externally is relative to the file owner'sdomain, so with multiple internal_domains a cross-internal-domain grant(e.g. student domain → staff domain) carries it too. External-ness istherefore judged against internal_domains using the grant's target:target_user for named grants, target_domain for domain-scoped grants(e.g. "anyone at partner.edu"; the literal domain "all" means "anyonewith the link" and is judged by visibility instead). risky_visibility_eventscounts only transitions into people_with_link / public_on_the_web(excluding a narrowing from public down to link-only).untargeted_external_transitions is a residual bucket for transitions intoshared_externally with no target address or domain to classify — it isnot a cross-check for grants missed elsewhere, since domain-scoped grantsare already counted above. external_samples / exposure_samples /untargeted_samples hold examples of each.
  • Drive events are queried one audit-relevant eventName at a time, so thepage budget is not consumed by view/edit noise; an event name rejected by theAPI degrades into event_errors instead of failing the tool.change_document_visibility and change_document_access_scope report thesame transition as simultaneous sibling events on this API — only thelatter drives classification (the former is fetched for its acl_eventscount only), so a domain-scoped grant or a link/public exposure is neverdouble-counted across the two. This also means the former can no longercompensate if the latter's own fetch fails: a change_document_access_scopeentry in event_errors sets capped: true for that domain, and itsclassification counts for the window are a lower bound even thoughchange_document_visibility (and thus acl_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. Mergingthat PR tags vX.Y.Z and publishes a GitHub Release, whose release: publishedevent triggers the release workflow to build and publish to PyPI and the MCPRegistry. release-please owns the version in gwsadm_mcp/__init__.py andserver.json (do not bump them by hand).

[!IMPORTANT]The release-please workflow should be given a repository secretRELEASE_PLEASE_TOKEN (a PAT with contents: write + pull-requests: write).The default GITHUB_TOKEN cannot create the Release that triggers thedownstream release workflow (GitHub blocks workflow runs triggered byGITHUB_TOKEN), so without the PAT nothing gets published. The workflow fallsback to GITHUB_TOKEN when the secret is unset so PR CI keeps working on forks.

License

MIT

MCP Server · Populars

MCP Server · New

    FootprintAI

    Containarium — Agent Runtime

    Open-source agent runtime — SSH-native isolation, eBPF egress policy, Kubernetes + LXC backends, GPU passthrough, MCP-native CLI

    Community FootprintAI
    openfate-ai

    @openfate/bazi-mcp

    OpenFate Bazi MCP server with deterministic Four Pillars calculation, True Solar Time, branch interactions, and reverse Bazi lookup.

    Community openfate-ai
    mohitagw15856

    🧠 PM Skills — 454 Professional Agent Skills for Claude, ChatGPT, Gemini, Cursor, Codex & Hermes

    In Anthropic's official Claude plugin directory · 400 professional Agent Skills (PRDs, launches, compliance, CVs & more) for Claude, ChatGPT, Gemini, Cursor & Codex. Try free in-browser, or 'npx pm-claude-skills add'.

    Community mohitagw15856
    kagan-sh

    kagan-legacy

    The Orchestration Layer for AI Coding Agents

    Community kagan-sh
    cbtw-apac

    QDrant Loader

    Enterprise-ready vector database toolkit for building searchable knowledge bases from multiple data sources. Supports multi-project management, automatic ingestion from Confluence/JIRA/Git, intelligent file conversion (PDF/Office/images), and semantic search. Includes MCP server for seamless AI assistant integration.

    Community cbtw-apac