Skip to main content
Glama
shigechika

gwsadm-mcp

by shigechika

Server Configuration

Describes the environment variables required to run the server.

NameRequiredDescriptionDefault
GWSADM_CONFIGNoPath to the INI configuration file. Defaults to ~/.config/gwsadm-mcp/config.ini~/.config/gwsadm-mcp/config.ini

Capabilities

Features and capabilities supported by this server

CapabilityDetails
tools
{
  "listChanged": false
}
prompts
{
  "listChanged": false
}
resources
{
  "subscribe": false,
  "listChanged": false
}
experimental
{}

Tools

Functions exposed to the LLM to take actions

NameDescription
health_checkA

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). Lightweight: one 1-item login query per domain.

login_auditB

Audit the login log: Google-auto-disabled accounts, suspicious logins, failure top-N.

account_disabled_* events are how Google reports that IT locked an account (leaked password, hijacking, spamming). 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.

suspended_accountsA

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.

user_oauth_tokensA

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).

drive_external_sharingA

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_EVENTS) — those carry no target_user and can legitimately report new_value: "owner" for a real transition, so excluding them there would blind this tool's primary signal instead of just removing creation noise. Each audit-relevant event name is queried separately so the page budget is not consumed by view/edit noise (change_document_visibility is fetched for its acl_events count only — it duplicates change_document_access_scope on this API and does not drive classification, so it cannot compensate if that probe's own fetch fails). capped_events lists event names that exceeded max_pages*1000 events; capped is also set when any probe's fetch errored outright (see event_errors) — either way, treat that domain's counts as lower bounds. Narrow hours or raise max_pages for full coverage (term-time weekdays see >10k change_user_access events/day).

Shared-drive caveat: a file created INSIDE a shared drive emits change_user_access events for each existing drive member (ACL propagation), so an external member merely uploading files looks like bulk external sharing here. When a finding's documents share one owner that is a drive NAME rather than a user address, triage with drive_doc_activity (per-document history: the "grants" coincide with create/upload by the same actor) and shared_drive_membership_changes (who added the members, and when).

drive_doc_activityA

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).

shared_drive_membership_changesA

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).

daily_briefA

One-call security summary across all configured domains.

Aggregates login_audit (account locks, suspicious logins) 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.

daily_brief_startA

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", ...}.

daily_brief_resultA

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).

Prompts

Interactive templates invoked by user choice

NameDescription

No prompts

Resources

Contextual data attached and managed by the client

NameDescription

No resources

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/shigechika/gwsadm-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server