gmail-mcp-plus
Provides tools for searching, reading, sending, replying, forwarding, and organizing emails, including attachments, drafts, labels, threads, filters, and auto-unsubscribe, across one or multiple Gmail accounts.
Provides tools for listing calendars, searching and managing events, creating, updating, deleting events, and responding to event invitations in Google Calendar.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@gmail-mcp-plussearch all accounts for emails about the merger"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Gmail and Calendar for your AI assistant โ every account at once, on a server you own.
gmail-mcp-plus connects Gmail and Google Calendar to Claude and any other MCP client. It can search and read mail โ in one account or across every connected account at once โ send, reply-all, and forward with quoted history, handle attachments and inline images, manage drafts, labels, threads, and filters, unsubscribe from mailing lists by their own one-click headers, and read, create, and answer calendar events.
It runs as a remote server on your own Cloudflare Worker, so the same connection answers from Claude Code on a laptop, claude.ai in a browser, and Claude on a phone. Each connection signs in to one Google account and can carry a friendly alias ("work", "personal"); the Google refresh tokens stay encrypted in your Cloudflare account.
This project is a fork of mkpoli/gmail-mcp (MIT), which contributes the entire Gmail core, the two-sided OAuth machinery, and the session model. The fork exists to close the feature gaps the upstream deliberately or historically left open:
Added here | Missing upstream |
๐ | โ one account per session, no cross-account view |
๐ท๏ธ Account aliases โ | โ accounts named only by address |
๐งน Filters โ list, create, delete (forwarding actions deliberately excluded) | โ settings scope never requested |
๐ค Auto-unsubscribe โ RFC 8058 one-click POST, mailto fallback, | โ |
๐ Calendar โ list calendars, list/search events, create, update, delete, RSVP | โ Gmail only |
โ
| โ only via label edits |
Every feature is a deployment flag (ENABLE_FILTERS, ENABLE_CALENDAR, ENABLE_CROSS_ACCOUNT, all "true" by default), and each flag brings exactly its own Google scopes โ turn one off and sign-in never asks for what it will not use.
Deploy it
About ten minutes. You need a Cloudflare account, bun, and a Google account. A domain on the Cloudflare account is optional โ without one the Worker answers on workers.dev.
1 ยท Create a Google OAuth client
PROJECT="gmail-mcp-$(openssl rand -hex 3)"
gcloud auth login
gcloud projects create "$PROJECT" --name="gmail-mcp-plus"
gcloud config set project "$PROJECT"
gcloud services enable gmail.googleapis.com calendar-json.googleapis.comGoogle exposes no API for the next two steps, so they happen in the Cloud console:
OAuth consent screen โ External, then under Audience press Publish app. Left in Testing, Google expires every refresh token after 7 days and each connection dies with its token. Published, the app shows an unverified-app warning at sign-in and serves up to 100 accounts.
Credentials โ Create credentials โ OAuth client ID โ Web application, with
https://<your-host>/callbackas an authorized redirect URI. Keep the client ID and secret.
<your-host> is the domain you point at the Worker, or the workers.dev hostname it gets otherwise. Deploying first and coming back to fill this in works โ the guide the Worker serves at / shows the exact value.
2 ยท Deploy the Worker
The button copies the repository into your GitHub account, creates the KV namespace and the Durable Object, and asks for the four secrets. It deploys to workers.dev; a custom domain is attached afterwards under Settings โ Domains & Routes.
From a terminal instead:
git clone https://github.com/BowTiedSwan/gmail-mcp-plus && cd gmail-mcp-plus
bun install
bun run setupbun run setup asks which domain to answer on, creates or reuses the OAUTH_KV namespace, takes the client ID and secret, generates a cookie key, and deploys. Those first two answers land in wrangler.local.jsonc, which git ignores โ wrangler.jsonc names no account's namespace and no one's domain, so a clone deploys anywhere. Re-running setup to rotate a single secret is safe.
3 ยท Connect a client
Leave the client ID and secret fields empty โ MCP clients register themselves.
claude mcp add --transport http gmail-personal https://<your-host>/mcp
claude mcp add --transport http gmail-work https://<your-host>/mcp/workRun /mcp in Claude Code to sign each connection in to its Google account. In claude.ai it is Settings โ Connectors โ Add custom connector with the same URL. Any single-segment label works after /mcp/, which is how one deployment serves several mailboxes to clients that reject two servers sharing a URL.
Once two accounts are connected, give them names and search them together:
set_account_alias("work")ยทlist_accountsยทsearch_all_accounts("invoice newer_than:7d")
Your deployment serves this guide at https://<your-host>/.
Related MCP server: mcp-gsuite
What it can do
whoami
search_messages
get_message
get_thread
get_attachment
get_unsubscribe_info
send_message
reply_all
forward_message
create_draft
update_draft
send_draft
delete_draft
list_drafts
stage_attachment_begin
stage_attachment_append
stage_attachment_finish
unsubscribe
list_labels
create_label
update_label
delete_label
modify_labels
modify_thread_labels
batch_modify_messages
mark_read ยท mark_unread
trash_message ยท untrash_message
trash_thread ยท untrash_thread
list_filters
create_filter
delete_filter
list_accounts
set_account_alias
search_all_accounts
list_calendars
list_events
get_event
create_event
update_event
delete_event
respond_to_event
Messages leave the way a mail client sends them: plain text with an HTML alternative, file attachments, and inline images referenced by cid:, nested as multipart/mixed โบ multipart/related โบ multipart/alternative. Subjects and display names use RFC 2047, filenames use RFC 2231, so Japanese, Chinese, and emoji survive the trip.
reply_all reads the original's Reply-To, From, To, and Cc, drops your own address and any address you send mail as, answers from the one the sender wrote to, carries the References chain, and quotes the original in whichever parts you send. forward_message reproduces the forwarded envelope and can re-attach the original's files. create_draft with replyToMessageId writes the reply as a draft to edit before sending. A file whose base64 will not fit through tool arguments is staged instead: stage_attachment_begin returns an upload URL that takes the raw bytes in one curl -T.
unsubscribe acts on the headers a mailing list publishes for machines โ RFC 2369 List-Unsubscribe and RFC 8058 List-Unsubscribe-Post โ never on tracking links in the body. A one-click sender gets a single https POST; a mailto sender gets a properly formed unsubscribe message from the account itself; a sender offering only a web page gets its URL handed back for a human. get_unsubscribe_info shows what would happen before anything does.
search_all_accounts runs one Gmail query against every account connected to the deployment, in parallel, each under its own rate budget, results grouped and labeled by alias. One dead mailbox reports its error in place rather than emptying the answer.
Calendar events round-trip with attendees, recurrence, and time zones: create_event refuses a zoneless local time before the API can garble it, update_event patches only what it is given, and respond_to_event answers an invitation as the connected account without disturbing the rest of the attendee list.
Reading is bounded on purpose: message and thread bodies have character budgets, a whole response has a byte ceiling, and an attachment is returned inline only while it stays small enough to read.
How it works
Two OAuth flows meet in one Worker. The MCP client authenticates to the Worker; the Worker authenticates to Google on your behalf. Neither side holds the other's credentials.
sequenceDiagram
autonumber
participant C as MCP client<br/>(Claude Code ยท claude.ai)
participant W as Worker<br/>(OAuthProvider + McpAgent)
participant G as Google<br/>(OAuth + Gmail/Calendar API)
C->>W: POST /register (dynamic client registration)
C->>W: GET /authorize (PKCE challenge)
W->>C: approval dialog
C->>G: consent screen โ pick the account
G->>W: GET /callback?code=โฆ
W->>W: allowlist check on the verified email
W->>G: exchange code โ access + refresh token
W->>W: sealed registry record (aliases ยท cross-account)
W->>C: MCP access token (Google tokens sealed inside the grant)
C->>W: POST /mcp โ tools/call
W->>G: Gmail / Calendar REST (token refreshed as needed)
G->>W: message / thread / event data
W->>C: tool resultLayer | File | What it does |
๐ MCP-side OAuth | Dynamic client registration, PKCE, grants in KV with the Google tokens sealed inside | |
๐ Google-side OAuth |
| Authorization code with offline access, one-time state bound to the browser session, double-submit CSRF, allowlist on the verified email, registry write |
๐ Features |
| Flag parsing, and the scope list each feature set implies |
๐ฅ Registry |
| AES-256-GCM-sealed account records in KV: aliases, and (only with cross-account on) refresh tokens |
๐ค Agent |
| One Durable Object per MCP session, bound to the account that opened it; single-flight token refresh, throttled fan-out |
โ๏ธ Mail |
| RFC 822 construction, MIME tree walking, charset decoding, reply and forward composition |
๐ Calendar |
| Calendar v3 over plain fetch, event summarization, time-zone validation |
๐ค Unsubscribe |
| RFC 2369 / 8058 header parsing, https target screening |
Gmail and Calendar are called over plain fetch against their REST APIs. The official googleapis SDK assumes Node and carries far more than a Worker should ship.
Endpoints
Path | Purpose |
| MCP endpoint |
| The same server under any single-segment label, for clients that reject two servers sharing a URL |
| This setup guide |
| OAuth machinery |
Feature flags
Three vars in wrangler.jsonc, all "true" by default. Changing one changes the scopes the next sign-in asks for; connections made before the change keep the scopes they were granted and are told to reconnect when a tool needs more.
Flag | Tools it registers | Scopes it adds |
|
|
|
| the seven calendar tools |
|
|
| none โ but see below |
The cross-account tradeoff, plainly. Upstream's rule is one session, one mailbox: a grant for one account can never touch another. Cross-account search necessarily crosses that line โ at sign-in the account's refresh token is also written, AES-256-GCM-sealed, into the registry, and any connected session may then search (not send from, not modify) every registered mailbox. That is the right trade for one person's own accounts and the wrong one for a deployment shared between people who shouldn't read each other's mail: set ENABLE_CROSS_ACCOUNT to "false" there, and only aliases remain. On a deployment whose ALLOWED_EMAILS is *, cross-account is forced off whatever the flag says โ a public relay where strangers search each other's mail is not a configuration, it is an incident.
Who can sign in
ALLOWED_EMAILS decides, checked against the address Google reports as verified โ after consent, before any grant exists.
Value | Who gets in |
(empty) | nobody |
| those accounts |
| anyone in that domain |
| any verified Google account (and cross-account search is forced off) |
Each grant reaches only the mailbox that authenticated it โ cross-account search aside, which is why that feature is a flag.
Limits
Two ceilings keep a shared deployment from being drained, both set in wrangler.jsonc:
Setting | Where | Default | What it bounds |
|
|
| Roughly how many distinct Google accounts may ever complete sign-in. Accounts already connected keep working when the cap is reached; new ones are turned away. Google caps unverified apps at 100 users, so leave room below that. |
|
|
| Google API calls one account may make in that window, across all of its sessions. A wide read spends several: |
|
|
| Client registrations one address may make in that window. |
On the Workers Free plan a further ceiling applies: 50 outbound requests per invocation, so search_messages and list_drafts want maxResults at 45 or below there, and search_all_accounts wants maxResultsPerAccount kept small with many accounts connected. The paid plan allows 1000.
Security
Self-hosting moves the trust question rather than removing it, so here is where everything sits.
Your tokens stay yours. Refresh tokens are encrypted inside their OAuth grant in your KV namespace, and โ with cross-account on โ AES-256-GCM-sealed in the account registry under a key derived (HKDF) from
COOKIE_ENCRYPTION_KEY. Both stores are your own Cloudflare account, encrypted at rest. Mail is never stored โ it passes through.One session, one mailbox โ for everything that writes. Sending, replying, drafts, labels, filters, and calendar changes only ever act on the account the session authenticated. Cross-account search is the sole, read-only, flag-gated exception.
Scope minimalism, feature by feature.
gmail.modifycovers reading, sending, labels, and trash while excluding permanent deletion. Filters addgmail.settings.basicand notgmail.settings.sharingโ forwarding addresses and delegates, the classic exfiltration backdoors, stay out of reach, andcreate_filteroffers no forwarding action either. Calendar adds event rights without ACL, settings, or calendar-deletion rights. Turn a feature off and its scopes are never requested.Unsubscribe is screened. Only
httpstargets with real hostnames are POSTed to โ nohttp, no credentials in URLs, no IP literals, no localhost โ and only when the sender declared RFC 8058 one-click. Everything else falls back to mailto or to a URL handed to a human.Headers cannot be smuggled. Every outgoing header value is rejected if it contains CR, LF or NUL โ including addresses parsed out of a hostile
List-Unsubscribeheader. Media types are validated, and quoted history is HTML-escaped.Access can be withdrawn. Narrowing
ALLOWED_EMAILSstops new sign-ins. A single account's access is revoked at myaccount.google.com/connections. Rotating the Google client secret invalidates every grant at once.
The Worker decrypts mail in memory while serving a request, as any hosted relay must. If that is unacceptable for a particular mailbox, run a local MCP server for that one.
How it was tested
305 unit tests cover message construction (MIME nesting, RFC 2047 folding, RFC 2231 filenames, CR/LF rejection), body extraction across charsets, reply and forward composition, the Google token flows, the sign-in allowlist, the CSRF and state-binding checks, the registry's seal/open round-trip and its refusal of tampered records, List-Unsubscribe parsing including header-injection attempts, feature-flag and scope resolution, and the tools themselves against a stand-in Gmail and Calendar โ session ownership, cross-account grouping and token isolation, one-click POST bodies, filter validation, attendee patching, and what a partly-failed read returns.
Development
bun run dev # wrangler dev on :8788
bun run check # biome + tsc
bun test # 305 unit tests
bun run deployQuestions and bugs
Open an issue.
License
Released under the MIT License. Based on mkpoli/gmail-mcp, Copyright ยฉ 2026 mkpoli, MIT โ see THIRD-PARTY.md, which also covers src/workers-oauth-utils.ts, derived from Cloudflare's remote-mcp-github-oauth demo.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- Alicense-qualityDmaintenanceA high-performance MCP server that enables AI assistants to interact with Gmail securely via OAuth 2.0, supporting smart email retrieval and thread-aware drafting.1MIT
- Alicense-qualityDmaintenanceMCP server to interact with Google Gmail and Calendar APIs. Supports multiple accounts, email search and drafting, and calendar event management.MIT
- Alicense-qualityDmaintenanceAn MCP server that enables AI assistants to interact with Google Calendar and Gmail, allowing users to manage events, send emails, and organize their inbox through natural language.MIT
- Alicense-qualityAmaintenanceLocal MCP server for reading/sending email via Gmail and managing Google Calendar events, enabling an AI agent to handle email and calendar operations through natural language.1MIT
Related MCP Connectors
Hosted Google Calendar MCP server for AI agents. No self-hosting or Google Cloud setup.
AI-powered medical document management for cancer patients. Google Drive, Gmail, Calendar via MCP.
Read, search, send, organize, draft and schedule email across your inboxes from any MCP client.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/BowTiedSwan/gmail-mcp-plus'
If you have feedback or need assistance with the MCP directory API, please join our Discord server