A CLI and MCP server for working with Gmail, Google Drive, Docs, Sheets, and Chat from the command line or from AI agents. Built on the mcp-app framework so the same binary works locally (stdio, one human) and as a hosted multi-user service (HTTP, JWT-authenticated).
Status: local stdio is the documented one-human path. Cloud HTTP deployment is supported via gapp (Cloud Run + custom domain + JWT auth via mcp-app); see Cloud deployment below for the abstract walkthrough.
- Python 3.10+ (3.11 recommended).
- A Google Cloud project you can configure. gwsa needs an OAuth
2.0 Client ID — either one you create in the Cloud Console (the
default path, gives you control over billing and scopes) or
gcloud's well-known client (faster setup, billed via
--quota-project). The Authenticating with Google section below walks through the Console steps. - For the gcloud variant: the
gcloudCLI installed and authenticated (gcloud auth application-default login).
Workspace (corp) accounts: if your Google identity is part of a Workspace org with strict OAuth policies (allowlisted clients, sensitive-scope review, Context-Aware Access), the Cloud-Console OAuth-client path may not work for that identity. See Workspace org constraints before spending time creating a client.
pipx install git+https://github.com/echomodel/gworkspace-access.git@feat/use-mcp-app-frameworkInstalls three commands:
| Command | What it does |
|---|---|
gwsa |
Google Workspace domain operations (mail, drive, docs, sheets, chat). |
gwsa-mcp |
MCP server for AI assistant integration. |
gwsa-admin |
Setup, credentials, user/account management. |
This is the headline path. If you have one Google identity you want to access, do exactly this.
Pick one of the two paths.
In the Google Cloud Console:
- Select or create a project. This project becomes the billing
home for every API call gwsa makes for this account; no
--quota-projectis needed for tokens issued by this client. - Enable APIs you'll use: Gmail API, Google Drive API, Google
Docs API, Sheets API, and (if you want chat tools) Google Chat
API and People API.
APIs & Services → Enabled APIs → ENABLE APIS AND SERVICES. - Configure the OAuth consent screen (only on first OAuth client in the project): set publishing status to Testing, add your Google email under Test users. This lets your own account consent without app verification. Submit nothing.
- Create the OAuth client.
APIs & Services → Credentials → Create credentials → OAuth client ID → Application type: Desktop app. Download the JSON; this is yourclient_secrets.json. Put it anywhere on disk — gwsa reads it once, on demand.
If you don't want to manage a Cloud Console OAuth client:
gcloud auth application-default login
gcloud auth application-default set-quota-project YOUR_GCP_PROJECTThis produces a token at
~/.config/gcloud/application_default_credentials.json issued by
gcloud's well-known OAuth client. Skip step 3 below and jump to the
gcloud variant in step 4 — you don't run
acquire-token, you hand the existing blob directly to
accounts add.
gwsa-admin connect localThis writes a one-line setup file so subsequent admin commands know to read and write the local user store (not a remote URL).
gwsa-admin acquire-token --client-secrets /path/to/client_secrets.json |
gwsa-admin accounts add personal --email me@example.com --token=-acquire-token opens a browser, runs the OAuth consent flow, and writes
the resulting token JSON to stdout. accounts add reads it from stdin
and stores it. On a fresh install the user record is auto-created from
--email and the new account becomes the default — no separate "users
add" ceremony.
gwsa-admin accounts list
gwsa mail search "newer_than:1d"That's it. Everything below is optional — adding more accounts, registering the MCP server with an AI client, or operating on the cloud variant.
If you'd rather use a token issued by gcloud auth application-default login:
gcloud auth application-default login
gwsa-admin accounts add gcloud-account \
--email me@example.com \
--token=@~/.config/gcloud/application_default_credentials.json \
--quota-project YOUR_GCP_PROJECT--quota-project is required because gcloud's well-known OAuth client
has no host project of its own; API calls need a billing project. If
you ran gcloud auth application-default set-quota-project first, the
project is already in the blob and --quota-project becomes optional.
A gwsa user is one human (you). Inside your user record is a list
of accounts — one entry per Google identity you own (personal,
work, etc.). One of them is marked as default_account; gwsa CLI calls
and MCP tools use it implicitly.
Add a second account the same way as the first, plus a name to tell them apart later:
gwsa-admin acquire-token --client-secrets /path/to/work.json |
gwsa-admin accounts add work --email me@example.org --token=-Switch which account is the default:
gwsa-admin accounts use work # now 'work' is implicitInspect, remove, or override:
gwsa-admin accounts list # see all accounts, marked with (default)
gwsa-admin accounts get work # detail one
gwsa-admin accounts remove personalFor per-call override on the gwsa CLI, the --account flag (e.g.
gwsa mail search "..." --account work) is planned but not yet wired
on the domain commands in this branch; for now the default account
applies to every call.
Single-user installs (one user in the local store) need nothing extra — every command resolves credentials through that user's default account:
gwsa mail search "from:bob after:2026-01-01"
gwsa drive list
gwsa docs read DOC_ID
gwsa chat spaces listIf the local store has multiple users, pass --user once on the
top-level command:
gwsa --user me@example.com mail search "newer_than:1d"The --user flag selects the gwsa user; the user's default_account
selects which Google identity inside that user's profile to use.
The planned per-call --account override on the domain commands
is not yet wired on this branch.
gwsa-mcp is a stdio MCP server exposing 30 tools across mail,
docs, drive, chat, and account discovery. (Sheets is CLI-only for
now.) Tools are discovered by mcp-app from
gwsa.mcp.tools.{accounts,mail,docs,drive,chat}.
Every Google-touching MCP tool accepts an optional account
argument — the account name (e.g. "work") or its Google
email (e.g. "me@example.org") — so an AI client can pick a
specific account per call when the user has more than one. Omit
account to use the user's default_account, or the sole
account when only one is configured. The list_google_accounts
tool exposes the names and emails the agent should pass.
The stdio entry point takes a --user KEY selector identifying
which registered local user it should run as. The key is an opaque
local-store handle — not a Google email. The Google account
emails live on each GoogleAccount inside the user's profile and
never need to surface in MCP registrations.
gwsa-admin migrate creates a single user keyed local by default.
Use that key in all registrations below.
Register with your client:
Claude Code:
claude mcp add --scope user gwsa -- gwsa-mcp stdio --user local
claude mcp list # verifyGemini CLI / Gemini Code Assist:
gemini mcp add gwsa gwsa-mcp stdio --user local --scope user
gemini mcp list # verifyClaude.ai web (custom connector): requires the cloud HTTP variant. After cloud deploy (see below), generate a one-shot connector URL with a long-lived token via:
gwsa-admin register --user me@example.com --client claude.aiPaste the resulting https://<your-cloud-domain>/?token=<jwt>
URL into claude.ai → Settings → Connectors → Add custom
connector. Local stdio doesn't apply to this client.
Client-specific quirks, troubleshooting, and detailed transport options live in Claude Code Configuration and Gemini CLI Configuration. The combined story across all clients is in MCP Server Setup.
| Path | Purpose |
|---|---|
~/.local/share/gwsa/users/<email>/auth.json |
Per-user auth record. |
~/.local/share/gwsa/users/<email>/profile.json |
Per-user profile (accounts list). |
~/.config/gwsa/setup.json |
connect local / connect <url> config. |
~/.config/gworkspace-access/profiles/<name>/ |
Legacy vault (pre-mcp-app). Read-only after gwsa-admin migrate; delete manually when satisfied. |
class GoogleAccount(BaseModel):
name: str # 'personal', 'work', ...
email: str # Google account email
token: dict # authorized_user blob (refresh_token, client_id, ...)
quota_project: str | None # required for gcloud-issued tokens; optional otherwise
validated_scopes: list[str]
last_validated: datetime | None
class Profile(BaseModel):
accounts: list[GoogleAccount]
default_account: str | Nonegwsa-admin connect local
gwsa-admin connect <url> --signing-key <key> # for hosted instance (Phase 2)
gwsa-admin acquire-token --client-secrets PATH [--scopes ...] [--out FILE]
gwsa-admin accounts add NAME --email EMAIL --token=<-|@FILE|JSON> [--quota-project ID] [--user KEY]
gwsa-admin accounts list [--user KEY]
gwsa-admin accounts get NAME [--user KEY] [--show-token]
gwsa-admin accounts remove NAME [--user KEY]
gwsa-admin accounts use NAME [--user KEY]
gwsa-admin migrate [--user-key KEY] [--skip-broken] [--dry-run] # one-shot legacy → mcp-app
gwsa-admin users list
gwsa-admin users revoke KEY
gwsa-admin --help lists every command. Each subcommand has its own
--help.
If you used gwsa before this branch (profiles in
~/.config/gworkspace-access/profiles/), run:
gwsa-admin migrate --dry-run # preview
gwsa-admin migrate # do it
gwsa-admin accounts list # verify
rm -rf ~/.config/gworkspace-access/profiles # delete when satisfiedEach legacy profile becomes one GoogleAccount on a single user
record (one human, many accounts). The active legacy profile becomes
default_account. The legacy directory is left in place until you
remove it.
If the Google identity you want to register is a corporate Workspace
account (not a free @gmail.com consumer account), the
Console-OAuth-client path (Path A above) may be blocked by your
org's policies. The common failure modes:
- OAuth client allowlist. Many corp orgs only allow IT-blessed app IDs to request user consent. Your personal-project OAuth client gets refused at the consent screen.
- Sensitive-scope review. Gmail/Drive/Calendar scopes are classified "sensitive" by Google; many orgs require admin allowlisting per app even when the client itself is permitted.
- Context-Aware Access. Policies that gate auth flows on device posture or network can refuse the flow entirely.
What actually works varies by org:
- gcloud variant (Path B) sometimes routes around the OAuth allowlist because gcloud uses Google's own well-known client. Context-Aware Access can still block it.
- You may have one GCP project for your personal identity and zero for your corp identity. Phase-2 cloud deployments shouldn't assume "the user's GCP project" as a singular thing.
This is one of the reasons each account on a gwsa user's profile
carries its own quota_project and was acquired with its own
OAuth client config — different identities may have to use
different projects, not just prefer to. See §5 of
Cloud Multi-User Architecture for the
phase-2 design implications.
OAuth refresh tokens can die without warning. The most common
cause on personal-use installs is the Testing-mode 7-day
expiry: if the OAuth client whose client_secrets.json you
used is set to "Testing" in the Google Cloud Console, every
refresh token it issues expires exactly 7 days after consent,
regardless of use.
Symptom: any gwsa CLI or MCP tool call fails with
invalid_grant: Bad Request from google-auth's token refresh.
Find the project number — it's the leading digits of any
client_id in your stored token, and also of the
installed.client_id field in your client_secrets.json.
Then open:
https://console.cloud.google.com/auth/audience?project=<project-number>
If the page shows Publishing status: Testing, you're on the 7-day clock. If it shows In production, refresh tokens last indefinitely (or until manually revoked) — the failure is from something else (the client was deleted, the secret was rotated, or the user revoked access at myaccount.google.com).
In the audience page above, click Publish app. New refresh tokens minted after that won't expire from the 7-day rule. Apps that request sensitive scopes (Gmail, Drive, Docs, Sheets) can publish without going through Google's verification; users see an "unverified app" warning at consent but the token works fine for personal use.
Tokens that already died from the 7-day clock stay dead — only new tokens minted post-publish benefit. You still need to re-acquire.
Single-user installs auto-resolve to the only user in the store,
so --user is unnecessary in every step below. Add
--user <key> only if you have multiple users locally.
gwsa-admin accounts remove personal
gwsa-admin acquire-token \
--client-secrets ~/.config/gworkspace-access/client_secrets.json \
--out /tmp/gwsa-token.json
gwsa-admin accounts add personal --email you@example.com \
--token=@/tmp/gwsa-token.json
rm /tmp/gwsa-token.jsonacquire-token opens a browser, you consent, the fresh token
JSON lands in /tmp/gwsa-token.json, and accounts add reads it
from there. The pipe form (acquire-token | accounts add --token=-) is equivalent and supported as of version 0.10.1;
earlier versions had a stdout-flush bug that could swallow the
token mid-pipe.
Verify with any read:
gwsa mail search "newer_than:1d" --max-results 1Rare. If you genuinely have multiple humans sharing one workstation
and one gwsa install, add --user <key> to every gwsa-admin
command that's not unambiguous. The cloud variant
is the right answer when multi-user is the actual deployment shape.
gwsa runs as a hosted multi-user HTTP service on Google Cloud Run via gapp. The same binary that serves stdio for a single human serves HTTP for many — mcp-app handles transport, JWT auth, admin REST, and GCS-backed user state automatically.
- Cloud Run service runs
gwsa(the mcp-app App entry point). gapp builds the container, deploys with Terraform, binds a custom domain, and provisions the SSL certificate. - GCS bucket (managed by gapp) backs the per-user
profiles directory at
/mnt/data/usersvia GCS FUSE. - GCP Secret Manager holds the JWT signing key as a
gapp-managed secret; gapp injects it into Cloud Run as the
SIGNING_KEYenv var. - Custom domain (optional) is bound at deploy time via
gapp's
domain:field; the.run.appURL keeps working in parallel. - Auth model: every MCP and admin request carries a JWT
signed by the deployment's signing key.
gwsa-adminmints per-user tokens from the same key;gwsa-admin registerpackages them into client-specific registration commands.
From a clone of this repo:
pipx install gapp
gapp init # writes gapp.yaml (already in repo)
gapp setup <your-gcp-project-id> # one-time
gapp deploy # build + terraform apply
gapp status # service URLThe committed gapp.yaml declares:
public: true(mcp-app handles its own JWT auth)- a generated
SIGNING_KEYsecret APP_USERS_PATH={{SOLUTION_DATA_PATH}}/users(GCS-backed)
A custom domain is opt-in — either edit gapp.yaml's
domain: before gapp deploy, or do it via CI scalar
overlay (see CONTRIBUTING.md). Leaving it absent serves
on the assigned .run.app URL only.
Once gapp status reports the URL:
# Fetch signing key (run from this repo so gapp resolves
# the solution from the working directory)
gwsa-admin connect https://<your-cloud-domain> \
--signing-key "$(gapp secrets get signing-key --plaintext)"
# Probe the deployment end-to-end
gwsa-admin probe
# Add a user and seed their first Google account
gwsa-admin acquire-token \
--client-secrets /path/to/client_secrets.json |
gwsa-admin users add me@example.com --token=-
# Generate client registration commands
gwsa-admin register --user me@example.comgwsa-admin register emits Claude Code, Gemini CLI, and
Claude.ai registration commands/URLs scoped to that user.
For a multi-account human, add more accounts to the same
user via gwsa-admin accounts add ... and pass account=...
on per-tool-call MCP invocations to select between them.
gapp supports GitHub Actions deploys via a reusable
workflow. Pin your deployment workflow to a released gapp
tag and pass solution-repo, WIF identity, and any scalar
overlays (e.g. domain) as inputs. See the gapp deploy
skill (plugin:gapp:deploy) for the full pattern.
Cloud Multi-User Architecture captures the locked design — user/profile/account model, credential resolution flow, and open Phase 2 questions (refresh-token rotation cadence, audit logging, BYOC vs. service-OAuth-client tradeoffs).
"No active profile" / "User not found": you haven't run
gwsa-admin connect local yet, or you haven't added any accounts.
"This token was issued by gcloud's well-known OAuth client...":
pass --quota-project YOUR_GCP_PROJECT (or run gcloud auth application-default set-quota-project and re-export the blob).
Token refresh errors during a CLI call (invalid_grant: Bad Request): the stored refresh_token died — revoked, expired, or
caught by the Testing-mode 7-day clock. See
Rotating tokens for
diagnosis (including how to check the OAuth client's publishing
status) and the exact recovery sequence.
Old gwsa profiles / gwsa client / gwsa setup commands gone:
intentional. See Upgrading from the legacy vault.
Cloud Multi-User Architecture is the canonical design doc — covers the user/profile/account model, the mcp-app framework adoption, the credential resolution flow, and the phased migration plan from the legacy per-profile vault.
See Release Notes for per-version highlights, breaking changes, and migration steps.
See CONTRIBUTING.md for development setup, repo layout, architecture rules (SDK-first), test conventions, and the version-management workflow.