Skip to main content
Glama
deepthi-vimal

Gmail MCP Connector

Gmail MCP Connector

A production-ready Model Context Protocol server that exposes Gmail read/search/send/draft capabilities as tools, built with FastMCP and the Gmail REST API.

Package layout

server.py                    # entry point: registers tools, runs stdio or HTTP transport
gmail_connector/
  __init__.py
  auth.py                    # OAuth2 credential lifecycle only
  errors.py                  # shared error handling (run_safely decorator) only
  tools.py                   # the MCP tool functions only
  compose.py                 # template-based subject/body drafting for compose_email
  notifications.py           # background polling for notifications_alter
requirements.txt
Dockerfile                   # containerizes the connector (HTTP transport)
docker-compose.yml           # connector + MCP Inspector + custom Tools-only web UI
inspector/Dockerfile         # containerizes the official MCP Inspector
web-ui/                      # custom Tools-only browser UI (FastAPI backend + static page)
  app.py                     # proxies MCP tool calls from the browser to gmail-connector
  static/index.html          # self-contained frontend: pick a tool, fill fields, run it
  Dockerfile

Tools exposed

Tool

Purpose

list_recent_emails(max_results=10, label="INBOX", from_email=None)

List recent emails in a label, or from one specific sender when from_email is given, with sender/subject/date/snippet.

search_emails(query="", max_results=10, from_email=None)

Search using Gmail query syntax (from:, subject:, after:, is:unread, ...); from_email is combined with query if both are given.

reply_to_email(from_email, body)

Immediately sends a threaded reply to the most recent email from from_email (same subject/thread, proper In-Reply-To/References headers) — no message ID lookup needed.

modify_email(from_email, add_labels=None, remove_labels=None)

Adds/removes Gmail labels on the most recent email from from_email — no message ID needed. Requires the gmail.modify scope — see the note below.

trash_email(from_email)

Moves the most recent email from from_email to Trash — recoverable for 30 days via restore_email. Requires the gmail.modify scope.

restore_email(from_email)

Restores the most recent trashed email from from_email back to the Inbox. Requires the gmail.modify scope.

forward_email(from_email, to_email, body=None)

Immediately sends the most recent email from from_email, forwarded to to_email, with the original sender/date/subject/body embedded and your optional body note prepended.

send_email(to, subject, body, cc=None)

Immediately sends a real, new email with a caller-supplied subject/body.

create_draft(to, subject, body)

Creates a draft without sending.

compose_email(to, topic, cc=None)

Generates a subject and body from a short topic using a fixed template (no external API), then immediately sends.

notifications_alter()

Read-only. Starts a background poller (on first call) that watches for any new email in Inbox or Spam — brand-new conversations and replies alike — and returns/clears whatever's queued since your last call. Never sends, replies, modifies, archives, labels, or deletes anything.

Both listing tools accept max_results up to 200 and paginate internally past Gmail's 100-per-page cap, so the caller never has to think about nextPageToken.

Every tool above also accepts an account parameter (default "default") to select which authenticated Gmail account to operate on — see Multiple Gmail accounts below.

compose_email is for when the caller only wants to give a recipient and a one-line description of what the email should say (e.g. "let the team know the deploy is delayed"), rather than writing an exact subject and body. The subject is derived from the topic text itself, and the body wraps it in a plain "Hi, ... Best regards," template — there's no AI rewriting and no API key required, so the output mirrors topic fairly literally rather than reading as naturally composed prose.

modify_email needs a broader OAuth scope. It uses gmail.modify, in addition to the gmail.readonly/gmail.send/gmail.compose scopes the other tools use. If you authenticated before this tool existed, your cached token.json only covers the old scopes and modify_email will fail with a permission error until you re-run the consent flow (delete the account's token file, or run the bootstrap snippet from First-run authentication again) so it's re-issued with the new scope included.

notifications_alter works by polling, not true push notifications — a public HTTPS webhook plus a Google Cloud Pub/Sub topic would be needed for real push, which isn't practical for a local/private deployment. Instead, the first call for a given account starts a background thread (inside the same server process) that checks for new inbox mail roughly every 2 minutes. Every message is tracked by ID so it's only ever reported once, even across overlapping poll windows or repeated calls. Matches queue in memory; calling the tool again drains and returns whatever's accumulated since your last call. Because state is in-memory only, it resets if the container/process restarts, and polling only starts once something has actually called this tool at least once (it doesn't run automatically at server startup).


  1. Go to the Google Cloud Console and create a new project (or select an existing one).

  2. In the left nav, go to APIs & Services → OAuth consent screen.

    • User type: External (unless you have a Google Workspace org and want Internal).

    • Fill in app name, support email, and developer contact email.

    • Scopes: you can add them here or let the OAuth flow request them at runtime; either way the connector requests exactly:

      • https://www.googleapis.com/auth/gmail.readonly

      • https://www.googleapis.com/auth/gmail.send

      • https://www.googleapis.com/auth/gmail.compose

    • Test users: while the app is in "Testing" publishing status, add your own Gmail address (and anyone else's who needs access) as a Test user, or the consent screen will reject the login.

2. Enable the Gmail API

In the same project, go to APIs & Services → Library, search for Gmail API, and click Enable.

3. Create a Desktop OAuth client

  1. Go to APIs & Services → Credentials → Create Credentials → OAuth client ID.

  2. Application type: Desktop app.

  3. Give it a name (e.g. "Gmail MCP Connector") and click Create.

  4. Download the resulting JSON file. This is your OAuth client credentials file (contains a client_id and client_secret — not a token, and not a service account key).

  5. Save it somewhere on disk, e.g. ~/.gmail-mcp/credentials.json (the default location — see Environment variables to use a different path).

4. Install dependencies

python -m venv .venv
# Windows: .venv\Scripts\activate
# macOS/Linux: source .venv/bin/activate
pip install -r requirements.txt

5. First-run authentication

The connector authenticates lazily, on the first tool call that needs Gmail access — there's no separate "login" command to run. On that first call:

  1. auth.py looks for a cached token at GMAIL_TOKEN_FILE (default ~/.gmail-mcp/token.json). None exists yet, so it falls back to the interactive flow.

  2. It reads your Desktop OAuth client from GMAIL_OAUTH_CREDENTIALS (default ~/.gmail-mcp/credentials.json) and opens your default browser to Google's consent screen.

  3. Sign in and grant the requested Gmail scopes.

  4. The resulting credentials (including a refresh token) are written to the token file automatically.

On every subsequent run, the cached token is loaded and silently refreshed with the refresh token — no browser popup — unless the refresh token itself has been revoked or expired, in which case the interactive flow runs again automatically.

To test locally over stdio before wiring it into any client:

python server.py

This starts the MCP server on stdio. You can point an MCP Inspector or compatible local client at python server.py to exercise the tools directly, which will also trigger the first-run browser consent.

6. Multiple Gmail accounts

Every tool accepts an optional account parameter (default "default"), so this connector can operate on more than one Gmail account — all sharing the same OAuth client (credentials.json), but each with its own token file.

To add another account, e.g. "work", run the same bootstrap snippet as first-run auth, naming the account:

python -c "from gmail_connector.auth import get_credentials; get_credentials('work')"

This opens the browser consent flow again — sign into the other Google account this time. The resulting token is saved separately at <config dir>/tokens/work.json; the "default" account keeps using <config dir>/token.json, completely unaffected. From then on, pass account: "work" to any tool to operate on that mailbox instead, e.g. list_recent_emails(account="work").

No extra Docker or environment configuration is needed for this — tokens/ lives inside the same config directory (gmail-mcp-data/ when using Docker) that's already persisted.

7. Running as HTTP (for deployment)

python server.py --http --port 8000
# optionally: --host 0.0.0.0 (default) or a specific bind address

This runs the server as a long-lived HTTP process serving the MCP streamable HTTP transport at http://<host>:<port>/mcp. Deploy it behind your usual process manager / container orchestrator / reverse proxy like any other Python web service. Make sure GMAIL_MCP_CONFIG_DIR (or the individual GMAIL_OAUTH_CREDENTIALS / GMAIL_TOKEN_FILE paths) point at a persistent volume, since the token file must survive restarts to avoid re-triggering interactive auth.

The very first authentication still requires an interactive browser consent flow (run_local_server), which needs a local browser and a reachable loopback port. Run first-run auth once locally (or over an SSH tunnel to the deployment host) to produce token.json, then ship that token file to the deployed environment — the deployed process will only need silent refreshes after that.

8. Registering the endpoint in your MCP platform

Once the server is reachable (locally over stdio, or remotely over HTTP), register it with your MCP-compatible client or platform:

  • Local/stdio clients (e.g. Claude Desktop, an MCP Inspector): add an entry pointing at the command python /path/to/server.py, run from the project's virtual environment.

  • HTTP-based platforms: open your platform's MCP integrations / connectors panel and add a new remote MCP server using the URL http://<host>:<port>/mcp (or your HTTPS reverse-proxy URL in production). Consult your specific platform's docs for the exact field names, since this varies by product.

9. Docker setup with a browser UI (MCP Inspector)

docker-compose.yml runs two containers:

  • gmail-connector — this project, in --http mode on port 8000.

  • inspector — the official @modelcontextprotocol/inspector, a web UI for calling MCP tools by hand, on port 6274.

Step A — bootstrap token.json locally first

The interactive OAuth consent flow opens a real browser and listens on a loopback port on your machine — it cannot run headlessly inside a container. So do first-run auth once, on your host, before touching Docker, and let Docker mount the resulting token:

mkdir -p gmail-mcp-data
cp /path/to/credentials.json gmail-mcp-data/credentials.json

# triggers the same get_credentials() lazy-auth logic auth.py uses, without starting a server
GMAIL_MCP_CONFIG_DIR=./gmail-mcp-data python -c "from gmail_connector.auth import get_credentials; get_credentials()"

A browser window opens, you sign in and grant access, and gmail-mcp-data/token.json is written. This gmail-mcp-data/ folder is exactly what docker-compose.yml mounts into the gmail-connector container at /data, so the containerized server only ever needs silent token refreshes from then on.

Step B — build and start

docker compose up --build

Step C — open the Inspector UI

Visit http://localhost:6274 in your browser. The Inspector container prints a one-time session URL (with an auth token query parameter) to its logs on startup — if the plain localhost:6274 page asks for a proxy session token, run docker compose logs inspector and open the full URL shown there instead.

Step D — connect the Inspector to the connector

In the Inspector's connection form:

  • Transport type: Streamable HTTP

  • URL: http://gmail-connector:8000/mcp

Use the service name gmail-connector, not localhost — the Inspector's proxy process runs inside the inspector container, and on the Compose network the connector is only reachable at its service name.

Once connected, the Inspector's Tools tab lists every tool from the table above, with auto-generated forms built from their docstrings/type hints, so you can call each one directly from the browser to verify everything end-to-end.

To stop everything: docker compose down (your token/credentials survive in ./gmail-mcp-data/ on the host).

10. Custom Tools-only web UI (alternative to the Inspector)

The MCP Inspector always shows Resources/Prompts/Ping/Sampling/Roots tabs alongside Tools, with no way to hide them. If you just want a page that lists the Gmail tools and lets you call them — nothing else — docker-compose.yml also runs a web-ui service at http://localhost:3000.

It's a small FastAPI backend (web-ui/app.py) that speaks MCP protocol to gmail-connector over streamable HTTP using the official mcp Python client, plus a single self-contained static page (web-ui/static/index.html) with no framework: a dropdown to pick a tool, a form built from that tool's input schema, and a "Run Tool" button. No session token to copy, no other tabs — just the 7 Gmail tools.

It comes up automatically with docker compose up -d alongside the connector and Inspector. Open http://localhost:3000, pick a tool from the dropdown, fill in the fields, and click Run Tool.

Environment variables

Variable

Default

Purpose

GMAIL_MCP_CONFIG_DIR

~/.gmail-mcp

Base directory for credentials/token files when the two below aren't set explicitly.

GMAIL_OAUTH_CREDENTIALS

<config dir>/credentials.json

Path to the Desktop OAuth client JSON downloaded from Google Cloud Console.

GMAIL_TOKEN_FILE

<config dir>/token.json

Path where the cached/refreshed user token is read from and written to.

Security notes

  • send_email, reply_to_email, and compose_email perform a real, irreversible action. Once called, the email leaves your account and cannot be recalled by this connector. Treat the gmail.send scope as sensitive: only grant this connector to models/agents and users you trust to decide when to send mail on your behalf, and consider steering the calling model toward create_draft by default when a request is ambiguous. compose_email also sends without a preview step, though its output is a deterministic template (not AI-generated), so the wording is predictable from topic rather than a black box.

  • token.json contains a live refresh token that grants ongoing read/send/compose access to the account — protect it like a credential (file permissions, secret storage, not committed to version control).

  • credentials.json (the OAuth client secret) should also be kept out of version control; it identifies your OAuth client but does not by itself grant access to any mailbox.

  • The Google OAuth consent screen will show these scopes as sensitive (gmail.send) and restricted-adjacent (gmail.readonly, gmail.compose); moving the app to "In production" for use beyond your own test users requires Google's OAuth verification review for sensitive scopes.

  • All errors are logged server-side with full detail via logging, but only short, non-sensitive messages are ever returned to the calling model — avoid logging raw email bodies or credentials at levels that ship to external log aggregators you don't control.

-
license - not tested
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

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

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/deepthi-vimal/Gmail_mcp_coonector'

If you have feedback or need assistance with the MCP directory API, please join our Discord server