gmail-mcp
Provides tools for managing Gmail accounts, including searching threads, reading emails, managing labels and drafts, and cross-account search across multiple connected Gmail accounts.
This server does not currently have a deployable release on Glama. Follow the repository's setup instructions to install and configure it for your MCP client, including any required credentials and local dependencies.
gmail-mcp — multi-account, multi-provider mail MCP server
An MCP mail server that connects multiple
accounts across providers — Google (Gmail) and Microsoft (Outlook/
Hotmail) — behind one set of mail tools, plus native provider settings tools.
Read tools take an optional account (email); omit it to use the default.
Applying filter/rule changes requires an explicit account. search_all_threads fans out
across all connected accounts in one call, mixing Gmail and Outlook.
Architecture in one line: an encrypted token store keyed by email, with provider collision guards routes
each call to the right account, and a small provider abstraction
(providers.py) dispatches Google calls to the Gmail API and Microsoft calls to
Microsoft Graph — so the tools never fork per provider.
Layout
gmail-mcp/
├── gmail_mcp/
│ ├── config.py # static config (Google scopes; Microsoft client id, authority, scopes)
│ ├── store.py # SQLite store keyed by email; provider + one Fernet-encrypted auth_data blob
│ ├── auth.py # Google OAuth + Microsoft MSAL device-code; per-provider credential refresh
│ ├── providers.py # MailProvider abstraction: provider_for() -> GmailProvider | GraphProvider
│ ├── gmail.py # Gmail API response helpers
│ ├── graph.py # Microsoft Graph mail helpers (stdlib urllib)
│ ├── rules.py # native Gmail filters / Outlook Inbox rules, preview and recovery
│ ├── drive.py # Google Drive helpers (Google accounts only)
│ ├── server.py # FastMCP server; all tools
│ ├── connect_microsoft.py # `python -m gmail_mcp.connect_microsoft [key-email]`
│ └── __main__.py # `python -m gmail_mcp`
├── credentials/ # client_secret.json (you add) + accounts.db + fernet.key (runtime). Gitignored.
└── requirements.txtRelated MCP server: gmail-mcp
One-time setup
Google (Gmail / Drive)
console.cloud.google.com → enable the Gmail API (and Drive API).
OAuth consent screen → External → add yourself as a Test user.
Credentials → OAuth client ID → Desktop app → download JSON → save as
credentials/client_secret.json.
The Desktop app type enables the loopback redirect add_account uses.
Microsoft (Outlook / Hotmail)
Uses an Entra public client app (device-code flow, no secret). The app id is
already set in config.py (MS_CLIENT_ID). If you ever recreate it: register an
app with "Any Entra ID tenant + personal Microsoft accounts", enable
Allow public client flows, and under Authentication → Mobile & desktop
add the redirect URIs https://login.microsoftonline.com/common/oauth2/nativeclient
and https://login.live.com/oauth20_desktop.srf (the second is required for
personal accounts, or device login errors with redirect_uri).
Python env
cd ~/Developer/gmail-mcp
python3.13 -m venv .venv
.venv/bin/python -m pip install .Python 3.13 or newer is required. requirements.lock.txt supplies pinned runtime
dependencies; requirements-dev.txt adds pinned lint/build tools for verification.
Connecting accounts
Google — opens a browser for consent:
.venv/bin/python -c "from gmail_mcp import auth; print(auth.add_google_account())"Microsoft — device-code login (prints a URL + code to enter):
.venv/bin/python -m gmail_mcp.connect_microsoftIf the Microsoft account's primary alias equals an existing Google account's email, pass the intended address so it stores separately and doesn't collide, e.g.
python -m gmail_mcp.connect_microsoft you@outlook.com.
The first account connected becomes the default.
Wiring into an MCP client
stdio server:
{
"mcpServers": {
"gmail-multi": {
"command": "/Users/ronildo/Developer/gmail-mcp/.venv/bin/python",
"args": ["-m", "gmail_mcp"],
"cwd": "/Users/ronildo/Developer/gmail-mcp"
}
}
}Tools
Tool | Microsoft | |
| ✅ | ✅ |
| ✅ | ✅ |
| ✅ | ✅ |
| ✅ | ✅ |
| ✅ | ✅ |
| ✅ | ✅ |
| ✅ | ✅ |
| ✅ | ✅ |
| ✅ | ✖ (not yet) |
| ✅ | ✖ (Google only) |
| ✅ | ✖ |
| ✖ | ✅ |
archive_thread / move_thread unify filing across providers: Gmail moves
labels (remove INBOX; a folder name maps to a label), Outlook moves the
conversation between mail folders (well-known Archive / Junk Email /
Deleted Items, or a custom folder created on demand). Prefer a custom folder
over Outlook's built-in Junk Email / Deleted Items, which auto-purge.
label_thread and move_thread refuse Gmail's TRASH and SPAM labels, because
Gmail deletes that mail after 30 days. Use a custom label or archive the thread
instead.
get_thread returns readable text by default, so a marketing email costs a
fraction of its raw size. Hidden content and invisible characters are removed, and
long tracking links are reduced to their domain. Pass body_format="html" for the
original markup and exact links. max_chars limits each body, and a cut body sets
body_truncated. Search snippets are cleaned the same way.
Each message lists its attachments, leaving out small embedded logos. Pass its
message_id and an attachment_id to read_attachment for the text of a PDF, Word,
Excel, HTML or plain text file, or to save_attachment to write it to
~/Downloads/gmail-mcp. drive_read reads the same file types from Drive. Scanned
PDFs have no text to read, and a note says so. Files are limited to 25 MB.
Search syntax: use Gmail query syntax for every account (is:unread from:a@b.com).
Microsoft accounts translate it for Graph $search: after:, before:,
newer_than:, older_than:, has:attachment, is:read, is:unread and -term
all work, and quoted phrases are safe. Operators with no Graph equivalent, such as
label: and category:, are dropped and listed in the result's ignored_terms.
Search, draft lists and Drive lists/searches honor max_results, fill short API
pages, and return next_page_token (null when finished). Pass it back as
page_token with the same account and query. Graph cursors may contain buffered
message summaries: treat them as mailbox data, not secrets or portable bookmarks.
search_all_threads returns next_page_tokens by account; continue each account
through search_threads. Graph conversations and folder enumeration follow every
page and detect loops. Moving a Graph conversation enumerates all message IDs
before writing; partial failures include moved, failed and unattempted IDs.
Filter and Inbox rule changes
These tools accept native Gmail filter criteria/action objects
or Graph messageRule fields,
without translating between providers. Unsupported account/tool combinations
fail before acquiring a token. get_account_capabilities describes implemented
features and stored scopes; it does not guarantee the mailbox granted permission.
Every settings mutation defaults to dry_run=true, makes zero API writes, and
returns an apply object with the explicit account. Delete, replace and update
also return expected_version, bound to the account, provider, target and current
object. To apply, pass the returned apply fields to the same tool. A changed
default cannot redirect the operation; a stale object/version is rejected.
Versions are optimistic checks, not a provider transaction or a signed approval.
For example, preview disabling a rule:
{"rule_id": "RULE_ID", "patch": {"isEnabled": false}, "account": "you@outlook.com"}Review the resulting before, patch, recovery and apply fields before using
the returned apply arguments. Save recovery bodies outside the chat if needed;
they are returned to the caller, not automatically journaled. Mutation tool
annotations conservatively mark settings changes as destructive even in preview.
Gmail has no filter update endpoint. replace_filter creates the new filter,
then deletes the old one. Both can briefly be active; replacement needs room
under Gmail's filter limit. A deletion failure reports both IDs and a partial
result; a lost create response leaves the old filter untouched and reports an
uncertain outcome. There is no delete-first fallback or automatic write retry.
Outlook updates PATCH only explicitly supplied writable fields. Omitted
sequence, exceptions, and isEnabled stay unchanged; explicit false and
empty predicate objects are retained. Graph rules marked isReadOnly reject
update/deletion. Creating a rule requires an explicit sequence; Graph can
recalculate other rules' order. Recovery recreates settings, potentially with new
IDs/order, and cannot undo mail that a rule already forwarded, moved or deleted.
Update recovery patches restore only fields changed by the original update, so
unrelated later edits are preserved. Malformed create responses are reported as
uncertain and are never a reason to retry or remove the old Gmail filter.
Permission errors provide reconnect guidance. Existing stored scopes are reused; these tools never launch consent or request additional grants. Any reconnect is a separate user decision. No account connection is needed to run the test suite.
Development checks
.venv/bin/python -m pip install -r requirements-dev.txt
.venv/bin/python -m unittest discover -s tests -v
.venv/bin/python -m ruff check gmail_mcp tests
.venv/bin/python -m build
.venv/bin/python -m pip install --no-deps dist/*.whl
.venv/bin/python -m pip checkTests use mocked APIs and temporary encrypted stores, with no live mail calls. CI runs these checks on Python 3.13 and 3.14 and imports the installed console entrypoint without starting authentication or the stdio server.
Security notes
Each account's credentials live in one Fernet-encrypted
auth_datablob (Google refresh token / MSAL token cache) — no secret material in the clear.client_secret.json,accounts.db,fernet.keyare gitignored — never commit.The store keeps
credentials/andbackups/private to the current user (folders0700, files0600) and tightens wider permissions when it opens.On macOS the encryption key belongs in the Keychain, so copying the credentials folder isn't enough to read the tokens. New stores put it there automatically. An existing store moves with
python -m gmail_mcp.keystore migrate --remove-file, which verifies the key against every stored account before deleting the file. Elsewhere the key stays infernet.key; back it up apart fromaccounts.db.Without the key the stored credentials are unrecoverable, so you'd reconnect the accounts. A missing key stops with an error instead of creating a new one that couldn't decrypt them.
Attachments, Drive files and unusually large HTML bodies are parsed in a short-lived child process with a timeout, so hostile content can't hang the server or reach decrypted tokens. Saved files are private to the current user, stay inside the save folder whatever name the sender chose, and never replace an existing file.
Existing email/default selectors remain unchanged. Provider collisions are rejected in either direction, including case variants. Use a distinct Microsoft alias when its primary address is already stored as Google.
Collision checks and credential writes are serialized across store connections. Refresh persistence compares the original account/provider snapshot and refuses to overwrite credentials changed by another process. First-use key creation publishes one complete key without overwriting another process's key.
Microsoft records bind to MSAL's stable
home_account_id. Legacy caches are bound lazily when there is one exact username match or a sole cached identity (the latter preserves existing alias selectors). Ambiguous/mismatched caches fail with reconnect guidance; they never fall back to the first cached user.Graph accepts only HTTPS
graph.microsoft.comURLs and refuses redirects, including while following pagination links. Account-store creation is lazy; importing the server does not create or change credentials.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
This server cannot be deployed
Maintenance
Related MCP Connectors
A MCP server for Gmail that lets you search, read, and draft emails and replies.
Email infrastructure for AI agents — send, receive, search, and reply to email over MCP.
Hosted email MCP for your own Gmail, Outlook.com, Microsoft 365, iCloud or IMAP inbox: read, search, draft, reply in thread, forward and file mail. It moves or flags up to 500 messages in one call, and a send leaves exactly one copy in Sent. A calendar is a separate connection, and connecting one adds diary and scheduling tools.
Hosted MCP server for your Gmail, Outlook and IMAP mail, with mailboxes and scopes set per agent.
Related MCP Servers
- AlicenseAqualityCmaintenanceConnect multiple Gmail accounts to any MCP client, enabling search, read, draft, send, label, and organize mail across unlimited accounts with local-only OAuth token storage.2211 npm9MIT
- FlicenseNot gradedqualityDmaintenanceMulti-account Gmail MCP server for reading threads, managing labels, and creating drafts across multiple Gmail accounts.-
- FlicenseNot gradedqualityDmaintenanceEnables searching, reading, and drafting emails across multiple Gmail accounts through a single MCP server.-
- AlicenseBqualityBmaintenanceEnables MCP clients to read, search, send, reply, and organize Gmail messages across multiple accounts via OAuth, with real MIME attachments and live signatures.23Apache 2.0