{
  "markdown": "# Google Multi-Account MCP (v0.7.0)\n\nConnect any number of Google accounts (Gmail + Calendar + Drive) to Claude. Search, send, draft, and manage emails with attachments; manage calendar events; upload, download, search, and share Drive files — all with an `account` parameter that routes to the right identity.\n\nThe `account` parameter is the whole point. Claude's built-in Google connectors bind to a single identity, so a folder in a personal Drive is invisible to a work login and a file lands in whichever Drive the connector happens to hold. Here you name the account per call.\n\n## What It Does\n\n- **Email**: Search, read threads, send, draft, label — across one or all accounts\n- **Attachments**: Send files with any email or draft; list and download attachments off received mail\n- **Drive**: Search, list folders, upload, download, create folders, inspect, share — per account\n- **Calendar**: List events, create/update/delete events, find free time across all calendars\n- **Injection-hardened**: Email content is sanitized before it reaches the model; email headers are CRLF-guarded\n- **Multi-account**: Configure as many Google accounts as you need, each with a custom label\n- **Cross-account search**: Use `account: \"all\"` to search email or calendar across every connected account\n- **Free time finder**: Merges all calendars to show when you're truly available\n\n## Setup\n\n### Prerequisites\n\n- macOS or Windows (Claude Desktop / Cowork)\n- Node.js 18+ (NVM/nvm-windows, volta, fnm, homebrew, or a system install all work)\n\nYou provide your own Google OAuth client (a 5-minute, one-time GCP setup — `/setup` walks you\nthrough it). Nothing is pre-baked; your credentials live only in your local, gitignored config.\nSee [Security](SECURITY.md) for why.\n\n### The consent screen — read this before you start\n\nTwo screens trip people up, in this order:\n\n**1. \"Google hasn't verified this app.\"** Red warning triangle and a **Back to safety**\nbutton. This is expected: the OAuth client hasn't been through Google's formal verification\nreview, which is normal for a self-published plugin asking for Gmail and Drive access.\nClick **Advanced** (bottom left) → **Go to google-multi-account (unsafe)**.\n\n**2. The permissions list, with a checkbox per scope.** **Check every box**, or hit\n**Select all**. An unchecked box is silently omitted from the token, and the usual symptom\nis Drive connecting and then failing every call with `Insufficient Permission`.\n\nBoth screens repeat for each account you connect.\n\n### Windows\n\nOne package covers both platforms — there is no separate Windows build. `start.cmd` ships\nalongside `start.sh` and discovers node from PATH, volta, nvm-windows, fnm, or Program Files.\n\n| | macOS | Windows |\n|---|---|---|\n| Install dir | `~/.google-multi-mcp/` | `%USERPROFILE%\\.google-multi-mcp\\` |\n| Launcher | `start.sh` | `start.cmd` |\n| Desktop config | `~/Library/Application Support/Claude/claude_desktop_config.json` | `%APPDATA%\\Claude\\claude_desktop_config.json` |\n| MCP entry | `\"command\": \"/bin/bash\"` | `\"command\": \"cmd\", \"args\": [\"/c\", \"…\\\\start.cmd\"]` |\n\nRegistering `\"command\": \"node\"` works on Windows only if node came from the official MSI\ninstaller. With nvm-windows, volta, or fnm it fails the same way bare `node` fails under\nNVM on macOS — which is the whole reason both launchers exist.\n\n### Quick Start\n\n1. Install the plugin in Claude\n2. Run `/setup` for the fully guided experience — it handles everything:\n   - Copies server files to `~/.google-multi-mcp/`\n   - Installs npm dependencies\n   - Walks through GCP project setup\n   - Runs OAuth authentication for each account\n   - Registers MCP server in Claude Desktop config\n3. Restart Claude (Cmd+Q → reopen)\n\n### Architecture\n\n```\n~/.google-multi-mcp/\n├── config.json          # Your accounts + OAuth credentials (local only)\n├── start.sh             # Node auto-discovery launcher\n└── server/\n    ├── index.js          # MCP server — dynamically registers tools from config\n    ├── auth.js           # OAuth2 client manager (one per account)\n    ├── config.js         # Config file reader/writer\n    ├── scopes.js          # Single source of truth for OAuth scopes\n    ├── gmail.js           # Gmail API operations (incl. attachments)\n    ├── mime.js            # RFC 5322 message builder for send/draft\n    ├── calendar.js        # Calendar API operations\n    ├── drive.js           # Drive API operations\n    ├── package.json\n    └── scripts/\n        └── setup-tokens.js  # Interactive OAuth setup script\n```\n\nThe Claude Desktop config calls `/bin/bash ~/.google-multi-mcp/start.sh` which auto-discovers node (NVM, volta, fnm, asdf, homebrew) and launches the server.\n\n### GCP Project Setup\n\nYou'll need a GCP project with:\n- Gmail API and Google Calendar API enabled\n- OAuth 2.0 client (Web application type)\n- Redirect URI: `http://localhost:3847/oauth/callback`\n- Test users added for each Google account you want to connect\n\nSee the `/setup` command or `skills/google-setup/references/gcp-walkthrough.md` for detailed instructions.\n\n## Tools\n\n| Tool | Description |\n|------|-------------|\n| `search_emails` | Search emails across one or all accounts |\n| `get_email_thread` | Read a full email thread |\n| `send_email` | Send from any connected account, with attachments |\n| `create_email_draft` | Create a draft (doesn't send), with attachments |\n| `list_attachments` | List files attached to a message |\n| `download_attachment` | Save an attachment to disk (preferred for large/binary) |\n| `get_attachment` | Return an attachment as base64 |\n| `list_email_labels` | List Gmail labels/folders |\n| `label_email` | Add/remove labels from messages |\n| `list_calendar_events` | List events from one or all calendars |\n| `create_calendar_event` | Create events with optional Google Meet |\n| `update_calendar_event` | Update existing events |\n| `delete_calendar_event` | Delete events |\n| `find_free_time` | Find free slots across ALL calendars |\n| `drive_search` | Search one account's Drive by name, full text, folder, or raw query |\n| `drive_list_folder` | List everything in a Drive folder |\n| `drive_upload` | Upload a local file, optionally converting to Docs/Sheets/Slides |\n| `drive_download` | Download a file; Google-native types export to .docx/.xlsx/.pptx |\n| `drive_create_folder` | Create a folder |\n| `drive_file_info` | Metadata and current sharing permissions |\n| `drive_share` | Grant access to a person or make a link-accessible file |\n\nEvery Drive tool takes an ID **or a pasted Drive URL** — a folder link copied from the browser works as-is.\n\n### Attachments\n\n```\nsend_email     account=\"personal\" to=\"…\" subject=\"…\" body=\"…\"\n               attachments=[\"~/Downloads/report.pdf\", \"/tmp/data.csv\"]\n```\n\nPaths are local and may use `~`. Content types are detected from the extension.\nTotal payload is capped at 20MB — past that, `drive_upload` the file and send the link.\n\nReading works the other way: `get_email_thread` now reports an `attachments` array per\nmessage, and `download_attachment` takes either the `attachmentId` from that list or just\nthe `filename`.\n\n## Configuration\n\nAll config is stored in `~/.google-multi-mcp/config.json`:\n\n```json\n{\n  \"clientId\": \"your-client-id.apps.googleusercontent.com\",\n  \"clientSecret\": \"GOCSPX-...\",\n  \"accounts\": [\n    { \"label\": \"personal\", \"email\": \"you@gmail.com\", \"refreshToken\": \"...\", \"scopeVersion\": 2 },\n    { \"label\": \"work\", \"email\": \"you@company.com\", \"refreshToken\": \"...\", \"scopeVersion\": 2 }\n  ],\n  \"driveScope\": \"full\"\n}\n```\n\n`scopeVersion` records which scope set each token was consented under, so the server can\nwarn when a token predates Drive instead of failing with a bare `Insufficient Permission`.\n\n`driveScope` is optional and defaults to `full`:\n\n| Value | Scope | Trade-off |\n|-------|-------|-----------|\n| `full` (default) | `auth/drive` | Read/write the whole Drive. Required to write into a folder you created by hand or to search existing files. A Google *restricted* scope — fine for personal use and Testing-mode apps; a published app carries annual security review. |\n| `file` | `auth/drive.file` | No verification burden, but the plugin only sees files it created itself. Search across an existing Drive won't work. |\n\nChanging it requires fresh consent: `npm run setup -- --reauth`.\n\nAdd more accounts anytime by running `/add-account` or re-running the setup script.\n\n## Commands\n\n| Command | Description |\n|---------|-------------|\n| `/setup` | Guided first-time setup (handles everything, both platforms) |\n| `/add-account` | Add another Google account |\n| `/reauth` | Re-consent every account after a scope change or expired token |\n\nRotating a leaked/old client secret? Update it without re-consenting accounts:\n`cd ~/.google-multi-mcp/server && npm run setup -- --credentials` (see [SECURITY.md](SECURITY.md)).\n\n## Upgrading to v0.5.0\n\nDrive needs a scope existing tokens were never consented for, and v0.4.0 added two\ndependencies. Upgrade with:\n\n```bash\ncd ~/.google-multi-mcp/server && npm install\n```\n\nThen re-authorize once:\n\n```bash\ncd ~/.google-multi-mcp/server && npm run setup -- --reauth\n```\n\nThat walks every configured account through Google's consent screen again — approve the\nDrive permission on each. Then restart Claude (Cmd+Q → reopen). Gmail and Calendar keep\nworking throughout; only the Drive tools fail (`Insufficient Permission`) until you do this.\n\nThe GCP project also needs the **Google Drive API** enabled. If re-auth succeeds but Drive\ncalls return `has not been used in project … or it is disabled`, enable it in the console\nfor the project that owns your OAuth client, then retry.\n\n## v0.7.0 Changelog — security\n\n- **Removed the hard-coded OAuth client secret** (`servers/scripts/setup-tokens.js`). Google's\n  scanner flagged it; see [SECURITY.md](SECURITY.md) for the incident and rotation runbook.\n- **Bring-your-own credentials.** `/setup` prompts for a Client ID + Secret and stores them\n  only in the local gitignored config. No credentials ship in the repo or package.\n- **`npm run setup -- --credentials`** — replace credentials without re-consenting accounts\n  (refresh tokens survive a secret rotation).\n- Removed all \"pre-baked OAuth / no GCP setup needed\" claims from README, manifest, and `/setup`.\n\n## v0.6.0 Changelog\n\n- **Windows support that actually works.** `start.cmd` discovers node from PATH, volta,\n  nvm-windows, fnm, and Program Files, mirroring `start.sh`. `/setup` is now platform-aware\n  with PowerShell equivalents and the correct `%APPDATA%\\Claude` config path.\n- **One package, both platforms.** The old `google-multi-account-windows` zip differed from\n  the macOS build in exactly one line of `.mcp.json` — and used `${HOME}`, which Windows\n  doesn't define, so it was broken there anyway. It had drifted three versions behind. The\n  fork is retired.\n- **Cross-platform path bug fixed.** `process.env.HOME` is undefined on Windows, which broke\n  `~` expansion and the default `~/Downloads` target in `drive_upload`, `drive_download`, and\n  `download_attachment`. All now use `os.homedir()`.\n- **`/reauth` command** — wraps `npm run setup -- --reauth` and documents the consent screens.\n- **Consent screens documented** in the README and `/setup`, with the specific warning that an\n  unchecked permission box produces a confusing `Insufficient Permission` later.\n- **Distribution trade-offs documented** — shared OAuth client, the 100-user cap, and what\n  the restricted Drive scope costs at verification time.\n\n## v0.5.0 Changelog\n\n- **Drive tools** — search, list, upload, download, create folder, file info, share. Per account.\n- **Email attachments both ways** — send/draft with local files (multipart MIME, 20MB cap);\n  `get_email_thread` surfaces an `attachments` array; `list_attachments` / `download_attachment`\n  pull files off received mail.\n- **URL-or-ID everywhere** — Drive file and folder parameters accept a pasted browser URL.\n- **Scopes centralized** — `scopes.js` is the single source; `auth.js` and the setup script\n  both import it. They used to be duplicated, so a scope added in one place was never consented\n  in the other.\n- **`--reauth` flag** — re-consents every configured account in one pass after a scope change.\n- **Stale-token warning** — the server logs which accounts predate the Drive scope at startup.\n- **RFC 2047 subject encoding** — accented and non-Latin subject lines no longer mangle.\n- **Shared drive support** — Drive calls set `supportsAllDrives`, so Workspace shared drives resolve.\n- **`download_attachment`** — writes to disk and returns a path, instead of `get_attachment`\n  pushing base64 through the conversation. Filenames are `basename`d so a hostile sender\n  can't path-traverse out of the destination folder.\n- **`/setup` copy list fixed** — it enumerated server modules by name and had already gone\n  stale (`sanitize.js` was missing), so a fresh install crashed on import with\n  \"server disconnected\". It now copies `*.js` and verifies every module landed.\n\n## v0.4.0 Changelog\n\nShipped as a package but never committed to git; recovered and merged in v0.5.0.\n\n- **`sanitize.js`** — untrusted email content is parsed, stripped, decoded, NFKC-normalized,\n  and length-capped before reaching the model: script/style bodies, HTML entities that decode\n  to live syntax, CSS-hidden text, zero-width and bidi characters, the Unicode Tags block\n  used for invisible steganography, and homoglyph confusables. Output is wrapped in explicit\n  untrusted-data delimiters.\n- **CRLF header-injection guard** on every send/draft header field.\n- **Correct reply threading** — `In-Reply-To`/`References` use the original RFC 5322\n  `Message-ID` rather than Gmail's internal resource id.\n- **Body extraction fixed** — prefers `text/plain` regardless of MIME tree order.\n- **`withAuthErrorHandling`** — an expired refresh token returns a structured re-auth hint\n  with the exact command instead of an opaque 401.\n- **`replyToMessageId` on drafts**, `list_attachments`, `get_attachment`.\n\n## v0.2.0 Changelog\n\nHardened based on 6 rounds of real-world Cowork debugging:\n\n- **`start.sh` wrapper** — Auto-discovers node from NVM/volta/fnm/asdf/homebrew. Eliminates the #1 failure mode (Cowork can't find node via version managers).\n- **Graceful startup** — Server never crashes on missing config. Starts with zero tools instead of `process.exit(1)`, which Cowork treats as permanently dead.\n- **Clean install path** — `~/.google-multi-mcp/` instead of relying on plugin directory extraction (unreliable in Cowork).\n- **Desktop config registration** — Setup command writes to `claude_desktop_config.json` directly. Cowork ignores plugin `.mcp.json` files.\n- **Health checks** — Setup command runs full diagnostics before and after installation.\n- **Battle-tested setup flow** — Every step validated through production debugging.\n\n## Distribution\n\nEach user brings their own OAuth client — the plugin ships **no** credentials. On first\n`/setup` the user is prompted for a Client ID and Secret and pointed at\n`skills/google-setup/references/gcp-walkthrough.md`. Their values are written only to their\nlocal `~/.google-multi-mcp/config.json` (gitignored, mode `0600`).\n\nThis is deliberate: a shared client means shipping its secret in public source, which is\nexactly what happened in the [2026-07-23 incident](SECURITY.md#incident--leaked-oauth-client-secret-2026-07-23).\nPer-user clients keep each install on its own quota and its own consent, with nothing secret\nin the repo or the package.\n\n`driveScope: \"full\"` (`auth/drive`) is a Google *restricted* scope; a published, verified app\nusing it triggers an annual third-party security assessment. `driveScope: \"file\"` avoids\nrestricted scopes entirely, at the cost of only seeing files the plugin created.\n\n## Security\n\n- Tokens are per-account and stored locally; no credential ever passes through Claude's context\n- `drive_share` changes who can see your data and is never called implicitly — confirm before granting access\n- Tokens stored locally in `~/.google-multi-mcp/config.json` (mode 0600)\n- The plugin never phones home or shares data\n\n## License\n\nMIT\n",
  "bytes": 16114,
  "sha": "78317458a522fdbb88f240348f388517da64070942c6e3835895badf20fa89dd",
  "repo_slug": "stevegustafson32/google-multi-account",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/plg_stevegustafson32_google_multi_account_go_b89a63d0/readme"
}