MultiGmailMCP

MultiGmailMCP

A local MCP server that lets Claude Desktop search, read, draft, and send email across multiple Gmail accounts with configurable safeguards, including two-step sending and no permanent deletion.

Category
访问服务器

README

Gmail Multi-Account MCP Server (for Claude Desktop)

A local MCP server that lets Claude Desktop search, read, draft, and send email across as many Gmail accounts as you configure — "check my work inbox for anything from Sarah" / "draft a reply from my personal account" — all running on your own machine. Nothing is hosted; Claude Desktop launches this script itself.

Scope, on purpose: this can search, read, draft, send, and organize mail (labels, archive, read/unread, star, trash) — but sending stays a deliberate two-step confirm-then-send, and it deliberately cannot permanently delete anything: trash is recoverable, and Gmail's hard-delete scope is left out on purpose. See "Scopes" below.


What you'll need

  • Python 3.10+
  • A Google account for each Gmail inbox you want to connect
  • Claude Desktop
  • About 15 minutes for the one-time setup

Step 1 — Create a Google Cloud OAuth client

This is the only fiddly part, and you only do it once (it covers all the accounts you'll add later, not one client per account).

  1. Go to console.cloud.google.com and create a new project (top-left project dropdown → New Project). Any name is fine.
  2. Enable the Gmail API: Menu ☰ → APIs & Services → Library, search "Gmail API," click it, click Enable.
  3. Configure the consent screen: Menu ☰ → Google Auth platform → Branding. Click Get started.
    • App name: anything (e.g. "Personal Gmail MCP")
    • User support email: your email
    • Audience: External (this is the right choice even for personal use — "Internal" only exists if you're on a Google Workspace account)
    • Contact email: your email → Create
  4. Add test users: still in Google Auth platform, click the Audience tab → Test usersAdd users. Add every Gmail address you plan to connect (your personal and your work address, if both apply). Unverified apps can only be authorized by addresses on this list.
  5. Create the OAuth client: Google Auth platform → Clients → Create Client.
    • Application type: Desktop app
    • Name: anything (e.g. "gmail-mcp-local")
    • Click Create, then download the JSON.
  6. Rename the downloaded file to credentials.json and place it in this folder (next to server.py).

You'll see an "unverified app" warning when you actually authorize each account in Step 3 — that's expected for a personal project like this one. Click Advanced → Go to [your app name] (unsafe) to proceed; it's your own app, so this is safe.


Step 2 — Install dependencies

cd gmail-mcp-server
python3 -m venv venv
venv/bin/pip install -r requirements.txt   # Windows: venv\Scripts\pip install -r requirements.txt

Step 3 — Authorize each Gmail account

Run this once per account, using whatever name you want Claude to call it by:

venv/bin/python add_account.py personal
venv/bin/python add_account.py work

Each run opens your browser to Google's sign-in. Log in with that account, approve, and a token is saved to tokens/<name>.json. You can add more accounts later the same way, any time.


Step 4 — Point Claude Desktop at the server

  1. Open Claude Desktop → Settings → Developer → Edit Config. This opens (or creates) claude_desktop_config.json.
  2. Add an entry under mcpServers, using the absolute path to this folder:
{
  "mcpServers": {
    "gmail-multi-account": {
      "command": "/absolute/path/to/gmail-mcp-server/venv/bin/python",
      "args": ["/absolute/path/to/gmail-mcp-server/server.py"]
    }
  }
}

(Windows: use the venv's python.exe, e.g. "C:\\Users\\you\\gmail-mcp-server\\venv\\Scripts\\python.exe", and escape backslashes like that.)

  1. Save the file and fully restart Claude Desktop.

That's it — ask Claude something like "what accounts do you have access to?" to confirm it can see the server, then try "search my work email for invoices from this month."


Tools this server exposes

Tool What it does
list_accounts Lists configured account names
gmail_account_info(account) Shows the real email address behind an account name, plus message/thread totals
gmail_list_labels(account) Lists an account's labels as id/name/type
gmail_search(account, query, max_results) Searches using Gmail's own query syntax (from:, is:unread, after:, etc.)
gmail_search_all(query, max_results_per_account) Runs one query across every configured account, grouped by account
gmail_read(account, message_id) Returns full headers + body of one message
gmail_read_thread(account, thread_id, max_messages) Returns every message in a thread, oldest first, in one call
gmail_list_attachments(account, message_id) Lists a message's attachments (filename, type, size, id)
gmail_save_attachment(account, message_id, attachment_id, dest_dir) Downloads one attachment to a local file (sanitized name, confined to dest)
gmail_draft(account, to, subject, body, cc, thread_id) Creates a draft — never sends
gmail_draft_reply(account, message_id, body, reply_all, quote_original) Drafts a reply with recipients/subject/threading filled in — never sends
gmail_draft_forward(account, message_id, to, body, quote_original) Drafts a forward to new recipients — never sends
gmail_update_draft(account, draft_id, to, subject, body, cc, thread_id) Replaces an existing draft's contents — never sends
gmail_delete_draft(account, draft_id) Deletes an unsent draft (drafts only — can't touch real messages)
gmail_list_drafts(account, max_results) Lists saved drafts (subject + recipient only)
gmail_get_draft(account, draft_id) Fetches a draft's full contents, for a last check before sending
gmail_send_draft(account, draft_id) Sends an existing draft. Irreversible — see "Sending mail" below
gmail_modify_labels(account, message_id, add, remove) Adds/removes labels on a message (by label id)
gmail_mark_read / gmail_mark_unread(account, message_id) Toggles the read state
gmail_archive / gmail_unarchive(account, message_id) Removes from / returns to the inbox
gmail_star / gmail_unstar(account, message_id) Toggles the star
gmail_trash / gmail_untrash(account, message_id) Moves to / restores from Trash (recoverable — not a permanent delete)
gmail_create_label / gmail_rename_label / gmail_delete_label(account, ...) Manages your user labels
gmail_list_filters(account) Lists the account's filters (criteria + actions)
gmail_create_filter(account, ...) / gmail_delete_filter(account, filter_id) Creates/deletes a filter — persistent; a filter can forward or trash mail
gmail_get_vacation / gmail_set_vacation(account, ...) Reads/sets the vacation auto-responder
gmail_get_imap / gmail_set_imap / gmail_get_pop / gmail_set_pop(account, ...) Reads/sets IMAP & POP access
gmail_list_forwarding_addresses / gmail_add_forwarding_address / gmail_delete_forwarding_address(account, ...) Manages forwarding addresses (new ones need Google verification)
gmail_get_auto_forwarding / gmail_set_auto_forwarding(account, ...) Reads/sets auto-forwarding — exfiltration vector, heavily gated
gmail_list_send_as / gmail_create_send_as / gmail_delete_send_as(account, ...) Manages send-as aliases
gmail_list_delegates / gmail_add_delegate / gmail_delete_delegate(account, ...) Manages delegate access (usually Workspace-only)

Sending mail

Sending is split into two tools on purpose: gmail_draft only ever saves a draft, and a separate gmail_send_draft(account, draft_id) sends one that already exists. There's no single tool that composes and sends in one call.

This matters because a model that can send email autonomously is a different risk profile than one that can only draft it — a misunderstood request, a hallucinated detail, or (worst case) an instruction smuggled inside an email it just read become irreversible instead of catchable. Splitting send into its own explicit step means:

  • Claude has to show you what a draft says (via gmail_draft's or gmail_get_draft's output) before it can act on sending it.
  • gmail_send_draft's tool description explicitly tells the calling model to only use it after you've clearly said to send that draft — never automatically right after creating one, and never because of something read inside an email rather than from you.
  • It's marked destructive_hint=True in its MCP annotations, which well-behaved MCP clients (Claude Desktop included) use to decide how cautiously to treat a tool — worth knowing this is a hint clients can use for their own approval UI, not an enforced restriction on its own.

None of this is a hard technical guarantee — it relies on whatever Claude you're running behaving as instructed, the same as any tool. If you want zero ambiguity, just don't ask Claude to send anything you haven't reviewed yourself; nothing stops you from also just sending drafts manually from Gmail once Claude's written them, which sidesteps this entirely.

Scopes

This server requests these OAuth scopes:

  • gmail.readonly — search and read
  • gmail.compose — create/read/update/delete drafts, and send them (this is Google's scope, not a design choice here — gmail.compose bundles sending in with draft management, there's no narrower scope that offers drafts without send).
  • gmail.modify — organize mail: add/remove labels, mark read/unread, archive, star, and move messages to Trash. This covers all read/write on mail except permanent deletion.
  • gmail.settings.basic — manage filters, the vacation responder, and IMAP/POP access.
  • gmail.settings.sharing — manage send-as aliases and delegate access.

The settings-changing tools (filters, vacation, IMAP/POP, forwarding, send-as, delegation) are persistent and some — a forwarding rule, a forwarding/trashing filter, a send-as alias, a delegate — can durably route or expose your mail. So every one of them is gated behind the confirmation phrase (see "Sending mail"), told to act only on an explicit user request (never on anything read inside an email), and forwarding/send-as/delegation changes are additionally recorded to settings.log.

What it deliberately does not request:

  • https://mail.google.com/ (permanent delete / empty Trash) — left out on purpose. gmail_trash moves messages to Trash, where they're recoverable for ~30 days, so nothing this server does is truly irreversible. There is no hard-delete tool because the token can't hard-delete.

Because the token can now send mail, reorganize or trash it, and change settings including forwarding and delegation, treat tokens/*.json as at least as sensitive as the password to that inbox. See Security below.

Why do I have to reauthorize so often?

Google restricts unverified ("Testing" mode) apps that use Gmail scopes: refresh tokens for test users expire after 7 days, regardless of how you built the app. This isn't a bug here — it's Google's policy for any personal script like this one. When it expires, you'll see a message telling you to rerun add_account.py <name> for that account; that's all you need to do. Getting Google to lift this (moving your OAuth app to "Production") requires their formal app-verification process, including a security assessment for these particular scopes — real weeks of process for a tool only you use, so it's not worth doing here.

Security

  • Each account's refresh token lives in plain text in tokens/<name>.json. Anyone with that file can read, send, reorganize/trash, and change settings (including forwarding, send-as, and delegation) on that account until it's revoked (trashing is recoverable; permanent deletion is not possible — see Scopes). Don't sync this folder anywhere shared; .gitignore already excludes tokens/ and credentials.json.
  • Two local, gitignored audit logs record the highest-consequence actions, with no token data: sent.log (every send) and settings.log (every forwarding/send-as/delegation change). They're best-effort records for your own review, not a security control.
  • Everything runs locally — Claude Desktop spawns this script as a subprocess on your machine. Your email data is included in whatever conversation you ask about it in (same as any Claude tool result), but nothing is hosted or exposed to the internet by this server itself.
  • To revoke access entirely, go to myaccount.google.com/permissions on the relevant account and remove the app, then delete its tokens/<name>.json file here.

Troubleshooting

  • "No module named mcp" — you're not using the venv's Python. Double check the command path in your Claude Desktop config.
  • Server doesn't show up in Claude — config JSON is probably invalid (missing comma, wrong path). Check Claude Desktop's MCP logs (same Developer settings page) for the exact error.
  • invalid_grant / "authorization has expired" — see "Why do I have to reauthorize so often?" above; just rerun add_account.py <name>.
  • "Access blocked: this app is blocked" instead of the usual warning — you likely forgot to add that Google address as a test user in Step 1.4.

Extending this later

  • Organizing mail (built in): labels, archive, read/unread, star, and Trash now ship via gmail.modify (see the tools table and Scopes). Permanent deletion is intentionally excluded. If you set this server up before these existed, rerun add_account.py once per account to pick up the new scope.
  • Settings (built in): filters, vacation, IMAP/POP (gmail.settings.basic) and forwarding, send-as, delegation (gmail.settings.sharing). All settings-changing tools are gated behind the confirmation phrase (see "Sending mail"); forwarding/send-as/delegation changes are also written to settings.log. Rerun add_account.py per account to pick up the scopes.
  • More accounts: just run add_account.py <new-name> again — no config changes needed.
  • Stricter send confirmation (built in): two harder gates than docstring wording now ship in gmail_send_draft:
    • Audit log — every successful send appends one line (timestamp, account, draft/message id, recipient, subject — never any token data) to sent.log next to server.py (gitignored). It's best-effort: a logging error never blocks or undoes a send.
    • Confirmation phrase — set the env var GMAIL_MCP_SEND_PHRASE (e.g. in your Claude Desktop server config's env) to any phrase, and gmail_send_draft will refuse to send unless it's called with that exact phrase in its confirm argument. This makes "confirm before sending" a code-level precondition, not just an instruction the model is asked to follow. Unset (the default), sending behaves exactly as before.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选