Gmail SMTP MCP Server
README.md
# Gmail SMTP MCP Server (Python)
Sends email as a teammate's Gmail account via SMTP + an **App Password**,
instead of Google OAuth. No Google Cloud project, no client secret, no
`redirect_uri` allowlist, no consent screen. This is a Python port of the
Node.js version — same architecture, same endpoints.
## How auth works
claude.ai's connector setup still expects a normal OAuth 2.1 + PKCE dance —
that part of the MCP spec doesn't change. What changes is *who* the
"identity provider" is:
1. User adds your connector in claude.ai → claude.ai opens `/authorize`.
2. Instead of "Sign in with Google", they see a form asking for their Gmail
address + an [App Password](https://myaccount.google.com/apppasswords)
(requires 2-Step Verification on their Google account).
3. The server test-sends an email to confirm the credentials work, encrypts
and stores the app password, then redirects back to claude.ai with an
auth code — same as a normal OAuth flow.
4. claude.ai exchanges that code at `/token` for a bearer token.
5. Every `send_email_tool` call carries that bearer token; the server looks
up which Gmail account it maps to (via the token's `subject`) and sends
via SMTP using only Python's standard library (`smtplib`).
Nothing here talks to Google's OAuth API. There's no client secret to leak
or rotate.
## Setup
```bash
pip install -r requirements.txt
# Generate a 32-byte encryption key for storing app passwords at rest
export MASTER_KEY=$(python3 -c "import secrets; print(secrets.token_hex(32))")
export BASE_URL=http://localhost:3000
uvicorn server:app --port 3000
```
Add both `MASTER_KEY` and `BASE_URL` to a `.env` file if you'd rather not
export them each time — just make sure something loads it before `uvicorn`
starts (e.g. `python-dotenv`, or your platform's env var settings).
## Deploying to Render
1. Push this repo, create a new Web Service pointing at it.
- Build command: `pip install -r requirements.txt`
- Start command: `uvicorn server:app --host 0.0.0.0 --port $PORT`
2. Set env vars: `MASTER_KEY` (generate as above), `BASE_URL` (your Render
URL, e.g. `https://your-app.onrender.com`).
3. **Important:** `data.json` (where users/tokens live) is written to local
disk. Render's free web services have ephemeral disks — a redeploy wipes
it and every teammate has to reconnect. For anything beyond testing,
either add a Render persistent disk mount, or swap `store.py` for
Postgres/SQLite (the function signatures in `store.py` are the only
thing you'd need to keep the same).
4. In claude.ai, add a custom connector pointing at
`https://your-app.onrender.com/mcp`.
## Files
- `server.py` — FastAPI app: OAuth-shaped `/authorize` + `/token` endpoints,
the MCP server (built with `mcp`'s `FastMCP`) mounted at `/mcp` with
bearer-token auth enforced automatically by the SDK.
- `store.py` — encrypted (AES-256-GCM) storage for app passwords, auth
codes, and access tokens.
- `mailer.py` — sends via `smtp.gmail.com:465` using Python's built-in
`smtplib`, no extra package needed.
## A gotcha worth knowing about if you modify this
FastMCP's `streamable_http_app()` normally manages its own startup/shutdown
(a "session manager") when run standalone. When you mount it inside another
FastAPI app with `app.mount("/mcp", ...)`, FastAPI does **not** automatically
run the sub-app's lifespan — so streaming requests fail with
`RuntimeError: Task group is not initialized`. The fix already in
`server.py` is wiring `mcp.session_manager.run()` into the outer FastAPI
app's own `lifespan`. If you restructure this file, keep that wiring intact.
## Security notes
- App passwords are encrypted at rest with `MASTER_KEY`, but they're still
bearer credentials with full mail-send access — treat `data.json` (or
your DB) like you would a password table.
- Access tokens are long-lived (90 days) bearer tokens with no rotation.
Fine for an internal team tool; add refresh tokens or shorter expiry if
this ever leaves that trust boundary.
- Anyone who can reach `/authorize` can register any Gmail address they
control an app password for — there's no allowlist. Add one (e.g. only
`@caratlane.com` addresses) if you want to restrict this before sharing
the connector link.
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues