deepthi-vimal

Gmail MCP Connector

Community deepthi-vimal
Updated

Gmail MCP Connector

A production-ready Model Context Protocol server that exposes Gmailread/search/send/draft capabilities as tools, built with FastMCP and theGmail 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, sothe caller never has to think about nextPageToken.

Every tool above also accepts an account parameter (default "default") to select which authenticatedGmail 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 ofwhat the email should say (e.g. "let the team know the deploy is delayed"), rather than writing an exactsubject 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 mirrorstopic fairly literally rather than reading as naturally composed prose.

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

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

1. Google Cloud project & OAuth consent screen setup

  1. Go to the Google Cloud Console and create a new project (or select anexisting 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 connectorrequests 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 anyoneelse'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_idand 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 — seeEnvironment 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 existsyet, 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 — nobrowser popup — unless the refresh token itself has been revoked or expired, in which case the interactiveflow 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 atpython 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 onmore than one Gmail account — all sharing the same OAuth client (credentials.json), but each with its owntoken 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 resultingtoken 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 operateon 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 configdirectory (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 athttp://<host>:<port>/mcp. Deploy it behind your usual process manager / container orchestrator / reverseproxy like any other Python web service. Make sure GMAIL_MCP_CONFIG_DIR (or the individualGMAIL_OAUTH_CREDENTIALS / GMAIL_TOKEN_FILE paths) point at a persistent volume, since the token file mustsurvive 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 SSHtunnel to the deployment host) to produce token.json, then ship that token file to the deployedenvironment — 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-compatibleclient or platform:

  • Local/stdio clients (e.g. Claude Desktop, an MCP Inspector): add an entry pointing at the commandpython /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 MCPserver using the URL http://<host>:<port>/mcp (or your HTTPS reverse-proxy URL in production). Consultyour 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 — itcannot run headlessly inside a container. So do first-run auth once, on your host, before touchingDocker, 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. Thisgmail-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 aproxy 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 theinspector 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-generatedforms built from their docstrings/type hints, so you can call each one directly from the browser to verifyeverything 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 tohide 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 streamableHTTP 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'sinput 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. Openhttp://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. Oncecalled, the email leaves your account and cannot be recalled by this connector. Treat the gmail.send scopeas 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 yourOAuth client but does not by itself grant access to any mailbox.
  • The Google OAuth consent screen will show these scopes as sensitive (gmail.send) andrestricted-adjacent (gmail.readonly, gmail.compose); moving the app to "In production" for usebeyond 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 areever returned to the calling model — avoid logging raw email bodies or credentials at levels that ship toexternal log aggregators you don't control.

MCP Server · Populars

MCP Server · New

    feenlace

    mcp-1c

    MCP server for 1С:Enterprise — AI assistant sees your configuration and generates accurate BSL code. One binary, zero dependencies, 10 tools.

    Community feenlace
    mesh

    Mesh MCP Server

    A simple Model Context Protocol (MCP) server for Mesh.

    Community mesh
    zapier

    Zapier MCP Plugin Distribution

    Official plugin distribution for the hosted Zapier MCP server. Install it in your AI client and connect to thousands of apps.

    Community zapier
    sanity-io

    Sanity Agent Toolkit

    Collection of resources to help AI agents build better with Sanity.

    Community sanity-io
    kaeawc

    AutoMobile

    Mobile automation suite of tools including an MCP and libraries for test authoring & execution

    Community kaeawc