multi-gmail-cowork-mcp
This server enables users to search, read, compose, and send emails across multiple independently-authenticated Gmail accounts through a single Claude custom connector.
README
multi-gmail-cowork-mcp
A small, self-hosted MCP server that lets one Claude custom connector search, read, compose, and send through multiple, independently-authenticated Gmail accounts. Built to be deployed by anyone into their own Google Cloud project, with zero shared infrastructure and zero code changes required per deployment.
Claude Cowork
|
v
Your private Multi-Gmail MCP (your own Cloud Run project)
|
+-- Gmail account: "personal"
+-- Gmail account: "work"
+-- Gmail account: "billing"
+-- ...more, added any time via the admin page
Ask Claude things like:
- "Search my work Gmail for emails from David."
- "Search all connected Gmail accounts for 'invoice 4831'."
- "Read the latest email from ACH Works, whichever account received it."
- "Which of my Gmail accounts received an email from John yesterday?"
Every result is explicitly attributed to the account alias and email address it came from. If you ask for an account that isn't connected, or whose authorization has expired, you get a clear error — this server never silently substitutes a different account.
What this is not
Gmail only. No Calendar, Drive, Docs, Sheets, or Contacts. No permanent-delete tool. No shared backend, no central account, no telemetry.
Privacy model — who can see your email
Google <-> Your Google Cloud deployment <-> Claude / Anthropic
- You deploy this into your own Google Cloud project, using your own Google OAuth client and your own Cloud Run service.
- Google issues/can revoke the OAuth grants; it sees ordinary OAuth traffic.
- Your deployment is the only place Gmail refresh tokens are ever stored.
- Claude/Anthropic sees whatever the tools return when Claude calls them (same as any other MCP tool) — nothing more, nothing when you're not using it.
- The author of this repository sees none of your email, ever. There is no shared server. Your cousin's deployment and your deployment have nothing in common except the source code.
Read SECURITY.md for the full trust-boundary and design-rationale writeup — including exactly why authentication is implemented the way it is, and this project's known limitations. This README does not repeat that reasoning.
Architecture at a glance
- Language/runtime: TypeScript on Node.js 20+, using the official
@modelcontextprotocol/sdkand Google'sgoogle-auth-library. - Transport: Streamable HTTP (the current MCP-recommended remote transport), stateless — every request is handled independently, so it scales cleanly on Cloud Run with no session affinity required.
- Claude <-> server auth: MCP OAuth 2.1 authorization-code flow with PKCE/S256, Dynamic Client Registration, short-lived access tokens, rotating refresh tokens, and deployment-local authorization state in Secret Manager.
- Server <-> Google auth: standard OAuth 2.0 with PKCE, one grant per connected Gmail
account,
gmail.modifyscope (read, compose, send, and mailbox modification; no permanent delete). - Account storage: one Google Secret Manager secret holding a small JSON array (alias, email, refresh token). No database.
- Admin UI: a few unstyled HTML pages behind HTTP Basic Auth — just enough to connect or disconnect accounts.
Prerequisites
- A Google account and a Google Cloud project with billing enabled (the bootstrap prints the exact billing page if billing is not linked).
- A Claude plan that supports custom connectors (for connecting to Cowork/claude.ai).
- Nothing else is required for a deployment: Google Cloud Shell already includes
gcloud,curl,openssl, andjq.
Cost and billing
Cloud Run requires a billing-enabled project. This deployment is sized for personal/small-business use: min instances 0 (scales to zero when idle) and max instances 3. Normal usage — a handful of searches, reads, and sends per day — stays well inside Google Cloud's Always Free allowance for Cloud Run, so the realistic ongoing cost is $0. That is not a guarantee: a sustained burst of requests beyond the free allowance would incur normal Cloud Run charges. The bootstrap prints an optional budget-alert link if you want a notification before any spend.
One-command Cloud Shell setup (recommended)
- Open this repository in Google Cloud Shell using the button above (or use Open in Cloud Shell on GitHub).
- Authenticate if Cloud Shell asks, then run:
./scripts/bootstrap.sh
The script asks you to select a project (or creates one), checks billing, enables the required APIs, creates the dedicated Cloud Run runtime service account, assigns only the Secret Manager roles it needs, creates all secrets, deploys Cloud Run, and prints PASS/FAIL checks. It is safe to rerun: existing secrets, account records, OAuth credentials, and Cloud Run services are preserved.
The script never prints a password, OAuth client secret, refresh token, account-store JSON, or
MCP OAuth token. Secret values are written as exact bytes (no trailing-newline credential bug).
Use ./scripts/bootstrap.sh --check for a read-only Cloud Shell prerequisite check.
The one unavoidable Google browser step
Google does not provide a safe, supported API/CLI operation for creating a general-purpose web OAuth client. When the bootstrap asks, open the Google Auth Platform page it prints and do the following:
- Configure the app as External, add the scope
https://www.googleapis.com/auth/gmail.modify, and add the Gmail addresses you will use as test users. - Create an OAuth client with application type Web application.
- Enter the exact callback URI printed by the script:
https://<your-cloud-run-host>/oauth/google/callback. - Paste the resulting Client ID and Client Secret into the hidden prompts in Cloud Shell.
If Google shows an unverified-app warning, that is expected for a personal deployment. Publish the consent screen to In production if you want refresh tokens to remain valid beyond the Testing-mode seven-day limit; verification is not required for a personal/small deployment.
At the end, bootstrap prints the Admin URL, exact Google OAuth callback URL, MCP URL, and the next human action. Claude authenticates to the MCP endpoint through its supported OAuth flow; there is no static connector header to copy or put in a URL.
Connect Gmail accounts
- Retrieve the admin password (generated by the bootstrap, never printed) with the command the
bootstrap printed at the end:
Then open the printed Admin URL and sign in with usernamegcloud secrets versions access latest --secret=admin-password --project=YOUR_PROJECT_IDadminand that password. - Enter a short alias such as
personalorwork, click Add Gmail Account, and complete Google authorization. The authorization URL requestsconsent select_account, so Google shows the account chooser every time. The address displayed after callback is the address Google actually authorized; it is not taken from the alias field. - Repeat for as many Gmail accounts as desired. Each alias is independent and all results are attributed to both alias and verified Gmail address.
Connect Claude Cowork
In Claude, open Settings → Connectors → Add custom connector and enter exactly:
- Connector name:
Multi Gmail - Remote MCP URL: the printed URL ending in
/claude-mcp - OAuth Client ID: leave blank (the server supports Dynamic Client Registration)
- OAuth Client Secret: leave blank
The /mcp route remains available for existing clients; use /claude-mcp for new Claude
connectors so its OAuth resource identity is independent of older connector records.
Click Add, then Connect. Claude discovers the MCP authorization metadata, registers
itself, and opens the deployment's Authorize MCP access page. Sign in there with username
admin and the admin password stored in your own admin-password Secret Manager secret, then
approve. Claude redirects through its callback at
https://claude.ai/api/mcp/auth_callback, stores the OAuth tokens, and reconnects. Do not
enter the Gmail OAuth client ID or secret in Claude — those belong only to Google's Gmail setup.
After the connector is connected, ask Claude to call list_accounts, then run an
alias-specific search for each account and search_all_accounts to verify attribution.
Verify your deployment
scripts/acceptance_test.mjs proves every tool works end-to-end against your own
deployment — account isolation, no-fallback on a bad alias, drafts, and send with
arrival and correct From identity. It performs the full MCP OAuth flow as a real
remote client and never prints tokens:
MCP_BASE_URL=https://your-service.run.app \
MCP_ADMIN_PASSWORD='...' # Secret Manager -> admin-password
node scripts/acceptance_test.mjs
The send test sends one email account-A → account-B and one account-B → account-A,
so the recipient is always an account you own. Set SKIP_SEND=1 to skip sends.
Local development (optional)
For source development only, install Node.js 20+, run npm install, copy .env.example to
.env, set TOKEN_STORE=file, and use npm run dev. Local Gmail OAuth requires a separate
OAuth client callback such as http://localhost:8080/oauth/google/callback; do not reuse or
commit production secrets. Windows users can use scripts/setup.ps1 and scripts/deploy.ps1
instead of the Cloud Shell bootstrap.
Reauthorizing / revoking accounts
After upgrading from the earlier read-only release, each existing account is marked Needs
Gmail permission upgrade. Open /admin, click Reauthorize for the same alias, and
complete Google's consent screen. The callback verifies that Google actually granted
https://www.googleapis.com/auth/gmail.modify before replacing that alias's stored refresh
token. Until then, read tools continue to work with the old grant, while write tools return a
clear reauthorization message; no other alias is ever used.
To remove access, click Disconnect next to an account. This revokes the grant with Google (best-effort) and removes it from the credential store immediately — Claude will get a clear "not connected" error if it's asked for that alias afterward, never a silent fallback.
To reconnect the same alias after revoking access on Google's side, use Connect account with the same alias — it overwrites the old record only after successful Google authorization.
Write tools and safety
The deployed server exposes create_draft and send_email. Both require an explicit
connected-account alias and never fall back to another account. send_email always sends
from the selected Gmail identity; the result includes that verified address. The tools use
gmail.modify, not the broader mail.google.com scope, and there is no permanent-delete
tool. Claude connector permissions should allow read tools automatically while keeping
create_draft and send_email set to Needs approval.
Updating and rotating
- Update the deployment after changing source or to pick up a new secret
version: rerun
./scripts/bootstrap.sh(idempotent — preserves accounts, tokens, and the OAuth client) or, on Windows,scripts/deploy.ps1. Existing Gmail connections and the Claude connector are unaffected. - Rotate the admin password: add a new Secret Manager version for
admin-passwordand redeploy. Existing Claude connector tokens stay valid (they are not derived from the admin password); only future MCP consent approvals use the new password. - Rotate
oauth-state-secret: this signs every MCP OAuth token, so rotating it invalidates the Claude connector's existing tokens — reconnect Claude afterward. Pending Gmail-linking state tokens (10-minute lifetime) also invalidate; connected Gmail accounts are untouched. - Rotate a Gmail account's grant: reconnect the alias from the admin page (see Reauthorizing / revoking accounts).
How to delete everything
- Remove Gmail access: disconnect each account from
/admin, or revoke access directly at https://myaccount.google.com/permissions. - Tear down the deployment:
gcloud run services delete multi-gmail-mcp --region us-central1 gcloud secrets delete mcp-oauth-state admin-password oauth-state-secret google-client-id google-client-secret gmail-mcp-accounts gcloud iam service-accounts delete multi-gmail-mcp-run@YOUR_PROJECT_ID.iam.gserviceaccount.com - Delete the OAuth client: Cloud Console -> APIs & Services -> Credentials -> delete the OAuth client ID, and optionally delete the OAuth consent screen configuration.
- Or simplest: delete the whole Google Cloud project.
Troubleshooting
- "Account needs to be reconnected" errors: the stored refresh token was rejected by
Google (revoked, expired, or the consent screen is stuck in "Testing" — see below).
Reconnect it from
/admin. - Refresh tokens keep dying after ~7 days: your OAuth consent screen is still in "Testing" publishing status. Publish it to "In production" (see step 3) — it can stay unverified, that's fine for personal use.
- Claude can't reach the connector / connection fails silently: confirm the service URL
resolves over plain HTTPS with no redirect to a different host, then open the MCP URL ending
in
/claude-mcpin Claude and click Connect again. The server must return OAuth metadata and a 401 challenge when called without an access token; no static request header is required. gcloud run deployfails on APIs not enabled: re-runscripts/setup.ps1, or rungcloud services enable run.googleapis.com cloudbuild.googleapis.com artifactregistry.googleapis.com secretmanager.googleapis.com gmail.googleapis.com iam.googleapis.com.- Local dev can't reach Google over HTTPS (certificate errors): this is almost always a local machine issue (a corporate proxy or antivirus doing TLS interception), not a bug in this project — check your machine's trusted root certificates.
Google OAuth Testing vs. longer-term use
Google Cloud OAuth clients start in Testing publishing status. While in Testing,
refresh tokens for sensitive/restricted scopes (which includes gmail.modify) expire
after 7 days, regardless of how few users you have — this will silently break the
connector on a weekly basis if left as-is.
The fix is not Google verification (a multi-month process built for public SaaS). It's
simpler: click Publish app to move the consent screen to In production. For an app
requesting only gmail.modify and staying under 100 total connected Google accounts,
Google's own documentation treats this as a fully supported personal/small-scale use case —
no verification required. The only visible effect is that each newly-connected account sees
a one-time "Google hasn't verified this app" click-through warning before granting consent.
That warning is expected; it does not mean anything is misconfigured. See
SECURITY.md for the underlying rules and
sources.
Repository layout
src/ TypeScript source (server, MCP tools, admin/setup UI, OAuth flows)
scripts/ bootstrap.sh (Cloud Shell), setup.ps1/deploy.ps1 (Windows),
acceptance_test.mjs (verify any deployment end-to-end)
.env.example Local-dev configuration template (placeholders only)
SECURITY.md Trust model, design rationale, known limitations
License
MIT — see LICENSE.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
mcp-server-qdrant
这个仓库展示了如何为向量搜索引擎 Qdrant 创建一个 MCP (Managed Control Plane) 服务器的示例。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器