Status: in daily use, unpublished. This is a small piece of infrastructure I built for myself and use every working day: a remote MCP server that lets Claude search and read my own mailbox, and nothing else. It is not a published project — there is no release, no repository link, no public endpoint and no second user. It is v1 read-only by design. No hostname, ports, or mailbox address are published here; the design is the interesting part, and the design is the whole reason this page exists.
Why
Most of my day runs through email. I wanted my assistant to answer questions like "which threads still need a reply this week?" or "did the vendor ever confirm?" without me copy-pasting messages into a chat window — and without handing a hosted model a live credential to a personal inbox. The Model Context Protocol makes the connector part easy. Giving an LLM safe access to a private mailbox is the actual problem, and it is a security problem before it is a coding problem.
So the brief I wrote for myself was short: least privilege, read-only surface, treat every message as hostile input, put the gate at the edge, and make it survive a reboot without me noticing.
Design
- Three tools, all read-only.
list_folders,search_emails(folder, unseen, sender, subject, since, capped limit), andget_email(headers, text body, attachment names). Each is registered withreadOnlyHintso the client — and the person reading the tool list — knows nothing here can send, move, mark, or delete. - Stateless transport, one server per request. Each POST to the MCP endpoint builds a fresh server and Streamable-HTTP transport, and both are torn down when the response closes. No session table, no stale sockets, nothing to leak between calls.
- Short-lived IMAP connections. Every tool call opens its own IMAP session, takes a mailbox lock, does one thing, and releases the lock and logs out in a
finallyblock. It is the simplest thing that cannot leak a connection. - Prompt-injection hygiene. Email is untrusted input: a message can contain text that looks like instructions. Every body
get_emailreturns is capped in length and prefixed with an[UNTRUSTED EMAIL CONTENT]marker telling the model to treat what follows as data, never as instructions. That marker is not a substitute for a good client-side rule, but it puts the boundary in the payload itself, where it travels with the content. - App-password-only auth. The server holds a revocable app password, never the account password, read from an environment file that is not in source control. IMAP predates second factors and has no step at which to present one, so an account protected by 2FA cannot authenticate over the protocol with its account password at all. An app password is the credential issued for exactly that gap, and it can be withdrawn on its own: if this server is ever compromised, revoking its password costs one re-issue and touches nothing else, where rotating the account password would sign out every other client at the same time.
- Loopback bind. The Node process listens on the loopback interface only, so the tunnel connector on the same machine is the only thing that can reach it — it is not on the LAN.
- Cloudflare Tunnel with an edge IP allowlist as the gate. Outbound-only tunnel, no open inbound ports, and a Cloudflare WAF rule that admits only the assistant's published egress range. Authentication happens at the edge; the origin stays authless and unreachable from anywhere else. A bearer-token gate exists in the code for hosts that lack an edge.
Operations
An idempotent, elevated PowerShell installer turns the deployment into two auto-start, auto-restart Windows services: the Node server and the tunnel connector. Both get failure-recovery actions so a crash restarts the service, stdout and stderr are captured to log files, and the environment file is injected through service configuration rather than a shell profile. A service set to start automatically comes up at boot, before anyone logs in, so it never runs a profile — a variable set there simply does not exist at the moment the process goes looking for its credential. Putting the path in the service definition is what makes the deployment survive a restart nobody was present for. The last step of the installer hits the loopback /health endpoint and refuses to declare success until it answers. Re-running the installer is safe.
A Docker image provides the alternative path for a hosted platform, and a smoke test written as a real MCP client — the same Streamable-HTTP client transport a Claude custom connector uses — walks tools/list, list_folders, search_emails, and get_email end to end, printing only a truncated body preview so the test itself never dumps a message. The services have been running since June 2026, restarting themselves as designed.
What it taught, and the roadmap
The useful lesson is that "connect my email to Claude" is mostly a threat-modelling exercise: decide what the model may do, make the forbidden things structurally impossible rather than merely discouraged, and label the data as data. Read-only annotations, an edge gate, and an in-band untrusted marker cost almost nothing and change the risk profile completely.
Planned, deliberately gated additions: send_email behind explicit approval, and mark_read, move_email, and delete_email flagged with destructiveHint; and subaddress-scoped views so a connector can be limited to a single purpose-specific address, shrinking the blast radius further. A write path is earned only when the agent has enough grounded knowledge of what I would and would not approve to judge whether a response is appropriate in the situation; until that test can be demonstrated, the boundary stays read-only.
Boundaries
- v1 is read-only by design, not unfinished. There are zero write paths.
- Personal use only. This is unpublished infrastructure, not a product: one mailbox, one user, no release, and no public endpoint to try. It earns its place here because I depend on it, not because anyone else can.
- No hostname, IP range, ports, tunnel identifier, or mailbox address are published, and the deployment scripts contain machine-specific values that would be scrubbed before any code release.
Related
- The LLM Ladder — where MCP sits among RAG, tools, and agents; this project is the hands-on counterpart.
- Practical AI Implementation — the least-privilege, verify-first posture applied more broadly.
- Security — the site's security posture and how to report an issue.
- PromptPack Studio — the local-first prompt tooling that pairs with a connector like this.
- Ventures — the rest of the project roster.