I have more than one Gmail account, plus a few company ones, and I wanted Claude to search all of them at once. The built-in connector handles one account at a time, so I built a small MCP server that runs on my own machine. Now there are mcps people have made, but trusting anyone with your emails credentials is not a good idea. I wanted something local and secure.
What it does
Three tools, nothing else:
- list_accounts: shows which addresses are connected
- search_emails: searches one account or all of them, using the same search syntax as the Gmail search bar (
from:,is:unread,newer_than:7d) - get_email: opens one message as plain text
So I can ask “find the invoice from last March” and it checks every inbox, with each result labelled by account.
Why local, why read-only
The only permission it asks Google for is gmail.readonly. It can't send, change or delete anything, even if I asked it to.
There's no hosted service or third-party database in between. Claude starts the server on my laptop, and requests go straight from my laptop to Google's API and back.
How it works
- Each account is connected through Google's normal sign-in page. The sign-in runs on
127.0.0.1and uses PKCE and a state check, so the login code can't be intercepted or replayed. - Google returns a refresh token, which is saved to
tokens/<email>.json. Only my user account can read that file. - Claude talks to the server over stdio, so no network port is opened.
- When I search “all”, every account is searched in parallel. If one account's token has expired, the others still return results, and the error names the account to reconnect.
The whole thing is about 400 lines of TypeScript.
Set it up yourself
- Create a Google Cloud project and enable the Gmail API.
- Set up the OAuth consent screen. Leave it in Testing mode and add each Gmail address you want to connect as a test user.
- Create an OAuth client ID with the type Desktop app. Download the JSON and save it as
credentials.jsonin the project folder. - Have Claude Code build the server using the prompt below.
- Build it and connect your accounts. Run
add-accountonce for each account.
npm install
npm run build
npm run add-account- Add it to Claude Code:
claude mcp add gmail --scope user -- node /path/to/gmail-mcp/build/index.jsThe prompt
Build a local MCP server in TypeScript (Node 18+) that gives Claude
read-only access to multiple Gmail accounts.
Requirements:
- Use @modelcontextprotocol/sdk over stdio, googleapis and zod.
Nothing hosted.
- Request only the gmail.readonly scope.
- Tools:
- list_accounts: returns the connected email addresses.
- search_emails(account, query, max_results): account is an address
or "all"; query uses Gmail search syntax; max_results defaults to
10 per account. Return account, id, threadId, from, subject, date
and snippet.
- get_email(account, message_id): return headers and a plain-text
body. Strip HTML and truncate bodies at 20,000 characters.
- For "all", search the accounts in parallel and label each result by
account. If one account fails, return the other results plus an
error for that account.
- Add an `npm run add-account` CLI that opens the browser for Google
sign-in. Use a loopback redirect on 127.0.0.1 with a random port,
PKCE and a state check. Request offline access so Google returns a
refresh token. Get the address from the Gmail profile and save the
token to tokens/<email>.json with 600 permissions. Running the
command again adds another account.
- Read the OAuth client from credentials.json (a Desktop app client)
in the project root. Resolve file paths from the project root, not
the current directory.
- If a token is expired or revoked, return an error that tells me to
rerun add-account for that address.
- Keep stdout for MCP protocol messages only. Send all logging to
stderr.
- Add credentials.json and tokens/ to .gitignore.
- Write a README covering the Google Cloud setup, adding accounts,
and connecting the server to Claude Code and Claude Desktop.Things worth knowing first
- Tokens expire after 7 days in Testing mode. Rerun
add-accountwhen that happens, or publish the app in Google Cloud to stop it. - No refresh token? That happens if you've already granted access. Remove the app at myaccount.google.com/permissions and sign in again.
- Claude Desktop can't find
node? This is common with nvm. Use the full path thatwhich nodeprints. - Never commit
credentials.jsonortokens/.
What I ask it
- “Any unread emails across all my accounts from this week?”
- “Find the flight confirmation for October and tell me the departure time.”
- “Which account did the Figma receipt go to?”
To disconnect an account, delete its token file or revoke access in your Google account settings.
It's a small project, but now one question checks every inbox I have.
