Putting two questions to a mailbox in one call, with the results sectioned per query, is faster than two round trips and reads better in a conversation. Every result carries a direct Gmail link, so checking an answer is a click.
A small Gmail server with three tools: send a message with optional attachments, search the mailbox with one query or several at once, and read a message in full. It runs against the Gmail API when an authorised token is present and falls back to SMTP and IMAP with an app password when it is not.
- A message sent with `to`, `subject`, `body`, and optional `cc`, `bcc`, an HTML body and local file attachments — unreadable files are skipped with a warning rather than failing the whole send (`send_email`)
- The mailbox searched with a single query or a list of them, with `max_results` applied per query and the results sectioned per query (`search_emails`)
- A direct Gmail web URL on every result and at the top of every message read, so an answer can be checked in one click (`search_emails`, `read_email`)
- A message read in full by the hex identifier the search returned (`read_email`)
- Batch search done in a single HTTP round trip on the API backend, where the IMAP backend iterates instead
- The backend chosen at startup and printed, so `backend=api` or `backend=imap` is visible rather than guessed at
Uv, and one of two credentials. For the API backend: a Google Cloud OAuth client of type Desktop app, authorised once through the browser with the `gmail.modify` scope, which writes a refresh token to `~/.config/claude-gmail-mcp/token.json` with 600 permissions. For the fallback: `GMAIL_ADDRESS` and `GMAIL_APP_PASSWORD`. Selection is by precedence at startup — a token file wins, then the two environment variables, and with neither the tools return an error. To force the fallback while a token file exists, point `GMAIL_TOKEN_PATH` at a path that does not exist.
One command plus a key — uvx claude-gmail-mcp, then supply credentials