gmail-mcp
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-mcpfind emails from support about account issue"
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 for your AI assistant — several accounts at once, on a server you own.
gmail-mcp connects Gmail to Claude and any other MCP client. It can search and read mail, send and reply-all with quoted history, forward, handle attachments and inline images, and manage drafts, labels, and threads — across several Google accounts at the same time.
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 the Google refresh token stays in your Cloudflare account.
Two things push people here. The Gmail connectors built into Claude and Google read mail and write drafts, but cannot send, and hold one Google account per assistant account. Servers that can send are usually local processes — fine at a desk, invisible from a phone.
How it compares
gmail-mcp | ||||||
Where it runs | Cloudflare Workers | vendor-hosted | your server or local | local | local | local |
Reachable from a phone | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
Several mailboxes at once | ✅ bound per connection | ❌ | ✅ chosen per call | ❌ aliases only | ❌ | ✅ chosen per call |
Send mail | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ |
Attachments · inline | ✅ | undocumented | ✅ | ✅ | ❌ | ✅ |
Reply-all with quoted history | ✅ | ❌ | drafts only | no quoting | ❌ | ✅ |
Forward | ✅ | ❌ | ✅ | ❌ | ❌ | ✅ |
Honors each part's charset | ✅ | — | ❌ UTF-8 assumed | ❌ UTF-8 assumed | ❌ | ❌ |
Rejects CRLF header injection | ✅ | — | ✅ framework | ✅ strips | ❌ none | ✅ |
Mailbox settings (filters, vacation) | ❌ out of scope | ❌ | filters | filters | ✅ | ❌ |
Tool count | 24 | 11–16 | 14 (Gmail) | 30 | 64 | 11 |
Who holds your refresh token | you | vendor | you | you | you | you |
google_workspace_mcp is the most complete project here. It covers all of Workspace rather than Gmail alone, and it appends your Gmail signature and pulls attachments straight from a URL, neither of which gmail-mcp does. shinzo-labs/gmail-mcp reaches vacation responders, delegates, and S/MIME through its 64 tools; those live under gmail.settings.*, a scope gmail-mcp never requests, so they stay beyond its reach whatever happens to a grant.
Two design differences decide most of the rest. Routing accounts by a call argument lets one grant touch every connected mailbox, while binding the mailbox to the connection means a wrong argument reaches nothing. And on reading, the local servers decode every part as UTF-8: ISO-2022-JP and Shift_JIS mail arrives garbled, and long messages that Gmail stores as attachment blobs come back with an empty body.
Related MCP server: Gmail MCP Server
Deploy it
About ten minutes. You need a Cloudflare account with a domain on it, bun, and a Google account.
1 · Create a Google OAuth client
PROJECT="gmail-mcp-$(openssl rand -hex 3)"
gcloud auth login
gcloud projects create "$PROJECT" --name="gmail-mcp"
gcloud config set project "$PROJECT"
gcloud services enable gmail.googleapis.comGoogle exposes no API for the next two steps, so they happen in the Cloud console:
OAuth consent screen → External. While the app is unverified, add each mailbox you plan to connect under Test users.
Credentials → Create credentials → OAuth client ID → Web application, with
https://<your-domain>/callbackas an authorized redirect URI. Keep the client ID and secret.
2 · Deploy the Worker
git clone https://github.com/mkpoli/gmail-mcp && cd gmail-mcp
bun install
# in wrangler.jsonc, set `name` and the routes `pattern` to your domain
bun run setupbun run setup creates the KV namespace, asks for the client ID and secret, generates a cookie key, and deploys. Re-running it 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-domain>/mcp
claude mcp add --transport http gmail-work https://<your-domain>/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.
Your deployment serves this guide at https://<your-domain>/.
What it can do
whoami
search_messages
get_message
get_thread
get_attachment
send_message
reply_all
forward_message
create_draft
update_draft
send_draft
delete_draft
list_drafts
list_labels
create_label
update_label
delete_label
modify_labels
modify_thread_labels
batch_modify_messages
trash_message · untrash_message
trash_thread · untrash_thread
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, 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.
Reading is bounded on purpose: message and thread bodies have character budgets and attachments a size ceiling, so a mailing-list thread cannot flood the assistant's context.
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 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->>C: MCP access token (Google tokens sealed inside the grant)
C->>W: POST /mcp — tools/call
W->>G: Gmail REST (token refreshed as needed)
G->>W: message / thread / label 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 |
🤖 Agent |
| One Durable Object per MCP session, bound to the account that opened it; single-flight token refresh, throttled fan-out |
| RFC 822 construction, MIME tree walking, charset decoding, reply and forward composition |
Built with
TypeScript on Cloudflare Workers — Durable Objects hold one MCP session each, KV holds the OAuth grants
Hono — routing for the OAuth endpoints, the Google callback, and the setup page at
/@cloudflare/workers-oauth-provider— the OAuth 2.1 server that MCP clients register againstagents—McpAgent, the MCP transport over Durable Objects@modelcontextprotocol/sdkwith Zod — tool definitions and argument validation
Gmail itself is called over plain fetch against the REST API. The official googleapis SDK assumes Node and carries far more than a Worker should ship, so message building, MIME parsing, and token refresh live in src/gmail.ts and src/utils.ts instead.
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 |
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 |
Each grant reaches only the mailbox that authenticated it, so widening this list never widens access to mailboxes already connected. Setting * lets strangers use your deployment, and your Google client's quota, for their own mail.
Limits
Two ceilings keep a shared deployment from being drained, both set in wrangler.jsonc:
Setting | Where | Default | What it bounds |
|
|
| 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 keep this below that. |
|
|
| Gmail calls one account may make in that window, counted across all of its sessions. |
Raise either and redeploy. Cloudflare's rate limiter reads its ceiling from the
binding at build time, so simple.limit is the only place that changes it. A
single-user deployment can leave both alone — normal assistant use sits far
below them.
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; the hour-lived access token lives in the session's Durable Object. Mail is never stored — it passes through.
One session, one mailbox. The MCP session is bound to the account that opened it, so a grant for one mailbox cannot act on another through a borrowed session id.
Scope minimalism.
gmail.modifycovers reading, sending, labels, and trash. It excludes permanent deletion and all ofgmail.settings.*, keeping auto-forwarding rules and filter exfiltration — the classic mailbox backdoors — outside what any stolen grant could do.Headers cannot be smuggled. Every outgoing header value is rejected if it contains CR, LF or NUL, so no argument can break out of its own field to append one — a
Bccinside a subject line, say. Media types are validated, and quoted history is HTML-escaped. What this does not do is police the arguments themselves:bccis a real parameter, so a model acting on an instruction hidden in a message body could still fill it in, and your client's approval prompt remains the check on that.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
66 unit tests cover message construction (MIME nesting, RFC 2047 folding, RFC 2231 filenames, CR/LF rejection, base64 wrapping), body extraction across charsets, reply and forward composition, the Google token flows, and the sign-in allowlist.
Beyond that, every tool has run against real Gmail accounts, with a separate account checking what arrived:
Area | Result |
Encoding | Japanese subjects folded across encoded words; emoji, ZWJ sequences, RTL Arabic, combining marks, and rare CJK round-tripped unchanged |
Attachments | A CSV named |
Threading |
|
Two accounts | Both connected to one deployment at once; a message id from one returned |
Organizing | A nested CJK label created, renamed, applied by batch, and deleted; thread and message trash both reversed |
Scale | A 15,000-message mailbox searched with Gmail operators and pagination without tripping a rate limit |
Development
bun run dev # wrangler dev on :8788
bun run check # biome + tsc
bun test # 66 unit tests
bun run assets # regenerate the light and dark diagrams
bun run deployQuestions and bugs
Open an issue.
License
Copyright © 2026 mkpoli. Released under the MIT License.
src/workers-oauth-utils.ts is derived from the remote-mcp-github-oauth demo in cloudflare/ai, Copyright © 2025 Cloudflare, Inc., used under the MIT License. See THIRD-PARTY.md.
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.
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/mkpoli/gmail-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server