Skip to content

feat(google): multi-account Gmail/Calendar via in-process MCP#53

Merged
meidad merged 1 commit into
mainfrom
feat/google-multi-account-mcp
May 27, 2026
Merged

feat(google): multi-account Gmail/Calendar via in-process MCP#53
meidad merged 1 commit into
mainfrom
feat/google-multi-account-mcp

Conversation

@meidad
Copy link
Copy Markdown
Collaborator

@meidad meidad commented May 27, 2026

Summary

  • Replaces the broken gws mcp external server (the subcommand was removed from @googleworkspace/cli v0.22.5) with an in-process MCP that exposes 12 typed gmail_* / calendar_* / google_list_accounts tools to the agent.
  • Adds multi-account support following the convention from indentcorp/gws-multi-account (upstream context): each account lives in `/.config/gws//` with its own `client_secret.json` + `credentials.enc`; every gws call gets `GOOGLE_WORKSPACE_CLI_CONFIG_DIR` pointed at the right one. The manifest at `/.config/gws/accounts.json` is the source of truth.
  • Settings UI gains Add another account + per-account Make default buttons. OAuth start route runs each auth attempt in a pending dir, resolves the email from the granted token, then atomically promotes the dir to its final per-account home — so multiple accounts coexist and re-auth replaces only that one.
  • Ingest: `gmail.ts` iterates every authorized account by default; `nomos ingest gmail --account ` scopes to one.

Also bundled (uncommitted bug fixes from earlier in the session)

  • `gws auth export --unmasked` in `gmail.ts` — without `--unmasked`, gws returns truncated `client_secret` / `refresh_token` (`GOCS...I9oF`) that fail token refresh with `invalid_client`. Was masquerading as a credential mismatch issue.
  • Gemini 429 retry in `embeddings.ts` — honors the `retryDelay` field in Google's 429 body. Free-tier quota is 100 embed req/min/project, easy to hit during bulk Gmail ingest; before this, hitting the quota tanked the whole ingest.

Backwards compatibility

Legacy single-account installs keep working: the manifest is empty until the first multi-account auth migrates them, and every code path falls back to the original single-account behavior. After the first re-auth via Settings UI, the install transitions to the per-account layout automatically.

Test plan

  • Re-authorize an existing single Gmail account via Settings UI. Verify `/.config/gws//` is created and `/.config/gws/accounts.json` contains the email with `isDefault: true`.
  • Click Add another account with a different Google account; verify both appear in the UI with the first marked default.
  • Click Make default on the second; verify the badge flips.
  • Run `pnpm dev ingest gmail --since 2026-05-25`; verify it ingests from both accounts (log shows `account: ` for each).
  • Run `pnpm dev ingest gmail --since 2026-05-25 --account first@example.com`; verify only that account is touched.
  • Click Remove on one account; verify its dir is gone, manifest is updated, and the other account is untouched.
  • In a chat: ask the agent to list your accounts; should call `google_list_accounts` and return both.
  • Ask the agent to search Gmail in your work account; verify it calls `gmail_search` with `account: work@example.com`.
  • Restart the daemon and confirm `nomos-google-workspace` is listed in the MCP servers log line.

Known gaps

  • The pre-existing Gmail cursor bug (`last_cursor` stores a message ID where a `pageToken` belongs, producing "Invalid pageToken" on subsequent runs) is unfixed; not part of this work, worth a follow-up.
  • The drive/docs/sheets/slides scopes are still requested at auth time but no corresponding MCP tools yet — the agent can fall back to Bash + `gws drive ...` if needed.

🤖 Generated with Claude Code

Replaces the broken `gws mcp` external server (the subcommand was
removed from @googleworkspace/cli v0.22.5) with an in-process MCP that
wraps gws CLI calls. The wrapper exposes typed gmail_* / calendar_*
tools to the agent and adds native multi-account support along the way.

Multi-account follows the convention from indentcorp/gws-multi-account
(see googleworkspace/cli#78): each account lives in
~/.config/gws/<email>/ with its own client_secret.json + credentials.enc,
and gws is invoked with GOOGLE_WORKSPACE_CLI_CONFIG_DIR pointing at the
right dir. ~/.config/gws/accounts.json is the manifest; the integrations
DB row is just a cache.

What's new:
- src/auth/gws-accounts.ts — manifest CRUD + per-account runGws/runGwsJson
  helper that injects the env var
- src/sdk/google-workspace-mcp.ts — in-process MCP with 12 tools:
  gmail_search, gmail_get_message, gmail_get_thread, gmail_create_draft,
  gmail_send_draft, gmail_list_labels, calendar_list_events,
  calendar_get_event, calendar_create_event, calendar_update_event,
  calendar_delete_event, google_list_accounts. Each takes an optional
  `account` arg (defaults to the manifest's default).
- Settings UI gains "Add another account" + per-account "Make default"
- OAuth start route now runs each auth attempt in a pending dir, then
  resolves the email from the granted token and promotes the dir to its
  final ~/.config/gws/<email>/ home. Multiple accounts coexist; re-auth
  atomically replaces just that one.
- Ingest: gmail.ts iterates every account in the manifest by default;
  `nomos ingest gmail --account <email>` scopes to one.

Also rolls in two fixes from earlier in this session that hadn't
landed yet:
- gmail.ts now passes `--unmasked` to `gws auth export` (without it,
  gws returns truncated client_secret/refresh_token that fail token
  refresh with `invalid_client`).
- embeddings.ts honors Gemini's `retryDelay` on 429 free-tier quota
  hits instead of bailing on the whole ingest.

Legacy single-account installs keep working: the manifest is empty
until the first multi-account auth migrates them, and every code path
falls back to the original single-account behavior.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@meidad meidad merged commit 9487955 into main May 27, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant