# A read-only Gmail connector for Claude Code

> One OAuth scope, a refresh token in the Keychain, and email the agent treats as data. Also why this site has a privacy policy.

Canonical: https://mark-web.com/writing/read-only-gmail-mcp
Author: Mark Sulleza
Published: 2026-11-07

Claude Code’s Gmail connector is signed in to a different account. I wanted my personal inbox next to it, and I didn’t want an agent able to send, delete or change anything in it. personal-gmail-mcp is a small MCP server for that. Claude Code starts it over stdio on my Mac, and it talks to Google directly with my own OAuth client.

## Read-only by construction

The only scope it ever asks for is `gmail.readonly`. That’s the boundary, not a prompt or a line in a tool description: Google won’t let the token send, delete, label or change mail, whatever the agent is asked to do. The tools follow from it:

| Tool | Does |
|---|---|
| `get_profile` | the signed-in address and the message and thread counts |
| `search_threads` | Gmail search syntax, newest first, paged |
| `get_thread` | every message in a thread: headers, body as text, attachments |
| `save_attachment` | saves one attachment to a local folder |
| `list_labels` | the account’s labels, for `label:` searches |

`save_attachment` writes to `~/Downloads/personal-gmail/<message id>/` with mode 600 and never overwrites a file.

## Nothing hosted in between

There’s no hosted piece of my own. The server runs locally, and sign-in is a desktop OAuth flow with PKCE and a loopback state check. The refresh token lives in the macOS Keychain, not in a file. Mail is read when I ask for it and goes into that Claude Code session, which sends it to Anthropic to answer me and keeps it in the session history on my Mac, like anything else in the session.

## Email is data

Every piece of email content comes back in fields marked `data_text`, and the agent is told to treat those as data, never as instructions. Email is the most obvious prompt-injection channel there is. Anyone can send me one.

## The 7-day sign-in, and this site

While a Google OAuth app is in Testing, Google expires its sign-in after 7 days, so every week a tool call tells me to run `npm run login` again. Publishing the app lifts the limit, but Google won’t let you publish without a homepage and a privacy policy on a domain you own. That’s part of why mark-web.com exists. The app’s [privacy policy](/privacy) is here.

The tests cover MIME and HTML parsing, the OAuth URL, PKCE and the loopback state check, token refresh, the tools against a fake Gmail API, safe attachment names, and the server over stdio.
