Open-source production project

Multi-tenant Gmail MCP server with OAuth hardening

A drop-in MCP server that wraps the Google Gmail API for any MCP-compatible client. Production-grade, not a demo. FastAPI, Postgres, Railway-ready, multi-tenant by design.

View on GitHub → What it is
651
tests
91.35%
code coverage
15/15
findings closed
32
MCP tools
0
hardcoded creds
What it is

An OAuth-authenticated Gmail bridge for MCP clients

gmail-mcp-oauth exposes the Google Gmail API as 32 tools any MCP-compatible client can call (Claude.ai, IDE assistants, internal agents). It handles the full upstream and downstream auth chain in one service.

01

Auth0 for client identity

The MCP client authenticates with Auth0. Every request to the server validates issuer, audience, scope, signature, and expiry on the JWT before any tool dispatch happens.

02

Google OAuth for account linking

Each Auth0 user can link multiple Gmail accounts. Refresh tokens are encrypted at rest with Fernet and keyed on (auth0_sub, account_email), with MultiFernet support for key rotation.

03

32 tools across the Gmail API

13 read tools, 14 write tools, 4 cleanup tools, 1 bootstrap tool. Every authenticated route is gated by an explicit allowlist; failure modes default to closed.

Why it matters

The MCP ecosystem is new. Most Gmail wrappers skip the hard parts.

Most public Gmail-MCP projects are toy single-user demos. They are useful for a weekend, not for shipping. The harder integration concerns only show up once a service is real.

This project closes them, then went through a 15-finding static security review (all closed), then was extracted from an internal monorepo to a standalone public repo through a systematic, agent-driven process.

Specifically:

What's under the hood

Hardened against the failure modes the demos don't talk about

  1. 1

    32 MCP tools across read, write, cleanup, and bootstrap

    13 read + 14 write + 4 cleanup + 1 bootstrap. Tools are dispatched only after JWT validation, scope check, and allowlist match.

  2. 2

    Multi-account linking per user

    One Auth0 sub can manage multiple Gmail accounts. Every tool call takes an account_email parameter that resolves to the correct encrypted refresh token.

  3. 3

    OAuth identity binding with two layers

    Explicit allowlist gates which Auth0 subs can link accounts at all. A post-callback confirmation page (auto-activates in multi-user mode) verifies the user completing the Google consent is the same user who initiated it. This is the consent-phishing defense.

  4. 4

    Encrypted refresh tokens at rest

    Fernet symmetric encryption with MultiFernet key rotation support. Tokens are decrypted only at the moment of API call, never logged, never returned in a response.

  5. 5

    JWT validation via Auth0

    Issuer, audience, scope, signature, and expiry all validated on every authenticated request. JWKS calls are throttled to prevent upstream abuse.

  6. 6

    Defense-in-depth input validation

    JSON Schema patterns catch CRLF injection, oversized inputs, Unicode lookalikes, and null-byte attacks at the schema boundary. Handler-entry checks back that up. 9 adversarial probe categories live in the test suite.

  7. 7

    Hash-pinned dependencies and reproducible builds

    Every dependency pinned by hash. Container builds are reproducible. The base image is digest-pinned, not tag-pinned.

  8. 8

    Non-root container with digest-pinned base

    Runtime drops privileges. Filesystem layout assumes a non-root user. No chown dance at startup.

  9. 9

    Async throughout

    FastAPI + httpx + asyncio. Concurrent Gmail API calls inside a single tool dispatch where the workload allows it.

  10. 10

    Production fail-closed gates

    Explicit replica-count acknowledgment is required before single-replica deployments. Allowlist enforcement at every authenticated route. The default for any ambiguous configuration is "do not run."

  11. 11

    Disaster recovery runbook

    Documented procedures for key rotation, DB compromise response, and account re-linking. Not just code; the operational playbook ships with the repo.

Architecture

Request flow and account-linking flow

The two paths that matter: the steady-state tool-call path, and the one-time account-linking path.

MCP Client (Claude.ai)
  -> Auth0 (issues JWT to client)
  -> gmail-mcp-oauth /mcp endpoint (validates JWT, allowlist)
  -> tool dispatch
  -> Gmail API (with per-user encrypted refresh token,
       fetched + decrypted from Postgres)

Account linking flow:
  MCP Client invokes connect_gmail_account(account_email)
  -> gmail-mcp-oauth returns Google OAuth authorization URL
  -> User opens URL, signs in to Google, consents to scopes
  -> Google redirects to /oauth2callback
  -> Server validates state, checks email_verified
  -> (multi-user mode) renders confirmation page;
       user confirms identity binding
  -> Refresh token encrypted with Fernet, stored under
       (auth0_sub, account_email)
  -> Tool calls now work for that account
A tool call in practice

What it looks like from the client

The account_email parameter is the multi-account model in one line. Real Gmail query syntax inside.

{
  "method": "tools/call",
  "params": {
    "name": "search_emails",
    "arguments": {
      "account_email": "[email protected]",
      "query": "from:[email protected] newer_than:30d",
      "max_results": 25
    }
  }
}
Stack

Built on boring, audited infrastructure

Python 3.11 FastAPI asyncio SQLAlchemy Alembic PostgreSQL httpx Auth0 OAuth 2.0 MCP Docker Railway pytest ruff
Behind the scenes

How it got from internal monorepo to public repo

The codebase originated as part of an internal Amazon FBA operations platform. Over a six-week period it went through five distinct phases.

  1. 1

    Initial implementation

    OAuth flow, 14 read tools, basic tool dispatch. Enough to prove the model worked end to end against a real Gmail account.

  2. 2

    Static security review

    15 findings across HIGH, MEDIUM, and LOW severity. The review was done before the repo went public, not after.

  3. 3

    Hardening series

    12 PRs systematically closing every finding: consent-phishing identity binding, refresh-token wipe, JWKS throttling, body-size limits, scope hierarchy, declarative schema patterns, and the rest. Each PR scoped tight enough to review without skimming.

  4. 4

    Test maturity

    Test count grew from roughly 200 to 651 with adversarial probe coverage across 9 categories. Coverage settled at 91.35%.

  5. 5

    Extraction and operational cutover

    Multi-pass scrub for PII, brand context, and internal process tags before extraction to a standalone public repo. CI workflow, branch protection ruleset, Railway redeploy from public source. Multi-tenant validation exercised end to end in production with multiple accounts under a single Auth0 sub.

This is what production-grade open source looks like end to end, not just the code.

Get into the code

Read it, fork it, deploy it

The repo is the source of truth. The walkthrough and tool reference go deeper than this page.

View on GitHub → Read the OAuth walkthrough See the tool reference
Legal

Privacy & terms

How the service accesses, uses, stores, and deletes your Google user data, and the terms that govern its use: Privacy Policy and Terms of Service.

Need this kind of work?

Welch Commerce Systems builds production-grade automation systems for e-commerce brands doing $2M to $10M. If this looks like the discipline you'd want on your stack, schedule a call.

Schedule a Discovery Call →