← All Writing
June 12, 20267 min read

How I Self-Hosted a Gmail MCP Server

The official claude.ai Gmail connector can read and draft — but it can’t archive, label, or trash. I wanted Claude to actually clean up my inbox, so I built a small self-hosted MCP server with the modify scope. But handing an AI agent write access to your email is exactly the setup that just wiped a Meta researcher’s inbox — so the real story here is doing it without that risk, plus the 7-day OAuth bug that took me a month to understand.

YieldA self-hosted MCP server that lets Claude search, count, archive, label, and trash Gmail threads on my behalf — the write operations the official read-and-draft connector can’t do
DifficultyIntermediate (Google OAuth, a Python MCP server, and one genuinely confusing token-expiry gotcha)
Total Cook Time~2 hours to build and wire up — then six weeks of a recurring auth failure I didn’t understand until I found the real root cause

Ingredients

The Problem: The Read-Only Wall

Claude.ai ships with a built-in Gmail connector, and it’s genuinely useful — Claude can search my mail, read threads, pull a receipt out of a five-year-old email. I lean on it all the time.

But it’s read-and-draft by design: it can search, read, and stage a draft for me to send — but it can’t clean up. No archiving, labeling, trashing, sending, or deleting ( Anthropic’s own connector docs spell out the limits, and the write/label scope is a known gap). It can look at my inbox and hand me a list; it can’t act on it. And the thing I actually wanted was cleanup. I’m on the 200GB Google plan and it was filling up with years of newsletters and retailer promos. I wanted to sit down with Claude and say “find every Best Buy marketing email from the last 90 days and trash them,” and have it just… do it. The read-only connector could find them and hand me a list. It couldn’t pull the trigger.

Why the connector holds back

To be clear, read-and-draft is a sensible default for a connector millions of people click “allow” on. You don’t want a stray instruction quietly deleting mail. But it’s my inbox on my machine, and I was willing to take on that risk myself. Self-hosting is how you opt into the extra power — and the extra responsibility.

One thing up front, because it’s the real story here: handing an AI agent write access to your inbox is genuinely dangerous. In February 2026 an OpenClaw agent deleted 200+ of a Meta researcher’s emails while she typed “STOP” from her phone ( PCMag, TechCrunch). I built this anyway — but deliberately, with a hard ceiling on what it can do. I’ll come back to exactly how I keep it from nuking my mail; it’s the part that matters most.

MCP (Model Context Protocol) is the standard that lets you give Claude your own tools. If I could write a small MCP server that exposed “archive this thread” and “trash these threads” as tools, Claude Code would pick them up and use them like any built-in. So I built that.

What the Server Can Actually Do

The whole thing is one Python file that exposes seven tools. Claude calls them the same way it calls any tool — it decides when, based on what I ask for:

That last group is the whole point — those are the writes the official connector won’t do.

The scope I chose — and the one I deliberately didn’t

Google’s Gmail permissions come in tiers. I asked for gmail.modify, which lets the app read, relabel, archive, and trash — but pointedly not permanently delete, and not send email as me. Trash is recoverable for 30 days; there’s no “gone forever” button wired up, and Claude can’t send a single message from my address. That ceiling is intentional. The blast radius of a mistake is “oops, un-trash it,” not “a stranger got an email that looks like it came from me.”

Building Guardrails Into the Tools

Because these tools can change my mail, I built the caution into the server itself rather than trusting myself to be careful every time. A few small decisions did most of the work:

🔧 Developer section: tool design choices

How It Plugs Into Claude Code

There’s no web server, no port, nothing listening on the network. MCP servers talk over stdio — Claude Code launches the Python script as a subprocess and they talk over plain standard input and output, the same pipes a normal command-line program uses. When the session ends, the process ends.

Wiring it up is a few lines of config that tell Claude Code: “to start the Gmail server, run this Python interpreter on this script.” That’s the entire integration.

🔧 Developer section: the MCP config

The One-Time OAuth Dance

The trickiest part isn’t the code — it’s convincing Google to let my homemade app touch my own Gmail. That’s OAuth (the “Sign in with Google” permission flow you’ve clicked a hundred times), and it has a few moving parts:

🔧 Developer section: OAuth setup

The “unverified app” scare screen

When you approve your own unverified app, Google throws up a stern warning screen — “Google hasn’t verified this app” — because normally an app asking for mailbox-modify access would go through a formal review. For a single-user app that only ever touches my own inbox, that review is overkill: you click Advanced → continue and move on. It looks scary; it’s just Google being appropriately loud about a real permission.

The 7-Day Mystery That Took Me a Month

Here’s the part I’m most glad I finally understood, because for weeks I was wrong about it.

The server worked great… for about a week. Then it would die with an error called invalid_grant — which is OAuth-speak for “that refresh token is no longer valid.” I’d re-run the auth script, it’d spring back to life, and roughly seven days later it would die again. I assumed I’d misconfigured something and kept papering over it with re-auths.

The real cause was subtle. Google’s OAuth consent screen has two states: Testing and Production. I’d left mine in Testing, which felt harmless — it was just me. But for sensitive scopes like gmail.modify, Google expires refresh tokens after 7 days while an app is in Testing, no matter who’s using it. That 7-day clock was the bug. It wasn’t my code, my token storage, or my network. It was a policy switch I didn’t know existed.

The fix was one setting: publish the consent screen to Production. In production, refresh tokens persist indefinitely. I flipped it, re-authenticated one last time, and the weekly death stopped cold. Re-auth went from a chore I did every Monday to a rare one-off.

The lesson that outlasts the bug

I’d even written down the wrong explanation in my own notes — “tokens stay valid as long as you’re a test user.” That’s just false for modify scope. The takeaway isn’t about Gmail; it’s that a plausible-sounding explanation you never actually verified will happily waste a month of your life. When something breaks on a suspiciously regular schedule, the schedule itself is a clue — that 7-day rhythm was the answer trying to get my attention.

How It Grew: From Inbox Cleanup to Quiet Infrastructure

I built this for one job — a big spring inbox purge — but because the tools were sitting right there in every Claude session, it kept turning out to be the right tool for problems I hadn’t built it for:

None of those were in the original plan. That’s the quiet argument for self-hosting a small capability: once Claude can do a thing safely, it starts doing it everywhere it makes sense.

The Real Danger: An Agent With Write Access to Your Inbox

Everything above is fun until you remember what I actually did: I handed an AI the power to change my mail, on its own, in a batch. That’s not a hypothetical risk anymore. There’s a story that made the rounds that reads like a warning written specifically for this project.

A Meta AI security researcher named Summer Yue asked her OpenClaw AI agent to look at her overstuffed inbox and suggest what to delete or archive. Instead it went on a deletion “speed run” — wiping out 200-plus emails from her primary inbox while she fired off stop commands from her phone that it sailed right past. Her own post-mortem is the part that stuck with me: her inbox was so large that reading it triggered context compaction, and somewhere in that compaction the agent lost her original instruction to confirm before acting. The safety constraint didn’t get overridden loudly — it just silently vanished, and the thing went from “working fine” to “deleting everything” in seconds. She called it a “rookie mistake.” A security researcher. On her own mailbox.

I want to be honest that my project is exactly that shape of access — Claude with real write power over Gmail — so that story isn’t someone else’s problem, it’s the cautionary case for the thing I built. Here’s how I think about not becoming it.

The scope ceiling is a safety rail, on purpose

This is why I keep coming back to gmail.modify. It can archive, relabel, and trash — but it cannot permanently delete. If my server ever had its own speed-run moment, every message it touched would land in Trash, recoverable for about 30 days, not gone. I deliberately did not grant the full-delete scope. When the agent’s worst possible action is “un-trash it,” you can sleep at night in a way you simply can’t when “permanent” is on the menu. Least privilege isn’t a limitation here; it’s the whole plan.

The other half is the keys themselves. Whoever holds that token file can act as me on my mailbox, full stop — so it’s treated like a password, not a config value:

And then the operating discipline, which is really the lesson from Summer Yue’s inbox: run destructive operations in small, reviewable batches; prefer trash over permanent delete every single time; and keep a human in the loop for bulk actions. The count → search → confirm → act flow I built earlier isn’t bureaucracy — it’s the thing standing between “clean up my promos” and “where did 200 of my emails go.”

Where It Landed

One Python file, seven tools, a locked-down credential, and a consent screen finally set to Production. It runs as a subprocess whenever I need it and vanishes when the session ends. The inbox that was creeping toward its storage cap is under control, and “clean up my mail” is now a sentence I say to Claude instead of an afternoon I lose to Gmail’s bulk-select UI.

What went fast

What needed patience

The best part isn’t any single tool — it’s that the read-only wall is gone. When I want Claude to read my mail, the official connector is still perfect. When I want it to actually tidy up, my own little server is right there. Same inbox, one more verb.

← Back to all writing