Gwsadm
БесплатноНе проверенGoogle Workspace security-audit MCP server — read-only visibility into account locks, suspicious logins, and external file sharing, built on the Admin SDK Repor
Описание
Google Workspace security-audit MCP server — read-only visibility into account locks, suspicious logins, and external file sharing, built on the Admin SDK Reports API (audit activities).
README
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 |
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 |
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 |
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 |
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, drive_external_sharing, daily_brief* |
those tools degrade to a per-domain error |
https://www.googleapis.com/auth/admin.directory.user.readonly |
suspended_accounts |
that tool degrades to a per-domain error; 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.
suspended_accounts and user_oauth_tokens both operate per configured
domain (Directory domain=/userKey=), unlike the customer-wide Reports
tools — so every domain you want covered (e.g. a separate student domain)
needs its own [domain.*] config section. Note the failure modes differ:
suspended_accounts silently omits an unconfigured domain from its
result, while user_oauth_tokens fails loudly with an unknown-domain error.
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 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
Установка Gwsadm
У этого сервера нет опубликованного пакета — он собирается из исходников. Открой репозиторий и следуй инструкции в README.
▸ github.com/shigechika/gwsadm-mcpFAQ
Gwsadm MCP бесплатный?
Да, Gwsadm MCP бесплатный — установка в пару кликов через Unyly без оплаты.
Нужен ли API-ключ для Gwsadm?
Нет, Gwsadm работает без API-ключей и переменных окружения.
Gwsadm — hosted или self-hosted?
Доступен hosted-вариант: Unyly запускает сервер в облаке, локальная установка не обязательна.
Как установить Gwsadm в Claude Desktop, Claude Code или Cursor?
Открой Gwsadm на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.
Похожие MCP
GitHub
PRs, issues, code search, CI status
автор: GitHubFilesystem
Secure file operations with configurable access controls.
Memory
Knowledge graph-based persistent memory system.
Template MCP Server
A CLI tool to create a new Model Context Protocol server project with TypeScript support, dual transport options, and an extensible structure
автор: mcpdotdirectCompare Gwsadm with
Не уверен что выбрать?
Найди свой стек за 60 секунд
Автор?
Embed-бейдж для README
Похожее
Все в категории development
