mcp-hey
mcp-hey is a local MCP server that gives Claude read/write access to a Hey.com inbox via 37 tools over stdio, using cached SQLite search and session-cookie auth.
List and read emails across imbox, feed, paper_trail, trash, spam, drafts, and sent, with threading, attachments, and calendar invites.
Manage labels, collections, and sender groups; search mail and inspect cache status.
Send new emails, reply, forward, and create/edit/delete drafts.
Triage threads: set aside, reply later, move between views, trash/restore/spam/unspam, mark seen/unseen, and mute/unmute.
Schedule bubble-ups (now, later, conditional on no reply) and pop them.
Approve or reject senders in the Screener by email or clearance ID.
Provides read/write access to Hey.com email inbox, enabling reading emails from various folders (Imbox, Feed, Paper Trail, etc.), sending/receiving emails, searching, organizing mail (set aside, reply later, screen in/out), and managing labels/collections.
mcp-hey
A local MCP server that gives Claude read/write access to your Hey.com inbox. A Bun/TypeScript server exposes the tools over stdio, and a small Python helper captures session cookies through the system webview at login. No cloud relay, no stored credentials.
Unofficial API. Hey.com has no public API. mcp-hey uses its web endpoints with browser-identical requests, so things can break without notice. The current surface is in
docs/API.md.
Setup
Prerequisites: Bun 1.1+, Python 3.10+ and a Hey.com account. Tested on macOS and Linux; Windows likely needs WSL.
git clone https://github.com/Sealjay/mcp-hey.git
cd mcp-hey
bun install
uv pip install -r auth/requirements.txt
bun run dev # first run: log in to Hey in the webview, then Ctrl+CThe helper saves session cookies to data/hey-cookies.json (mode 600). Later runs reuse them until they expire.
Related MCP server: Outlook MCP
MCP client configuration
{
"mcpServers": {
"hey": {
"command": "bun",
"args": ["run", "/absolute/path/to/mcp-hey/src/index.ts"]
}
}
}Client | Where |
Claude Code |
|
Claude Desktop |
|
Cursor |
|
Use absolute paths. GUI apps often lack your shell's PATH, so if you see spawn bun ENOENT, set command to the output of which bun (Homebrew: /opt/homebrew/bin/bun).
Docker: docker build -t mcp-hey ., then printf '{"jsonrpc":"2.0","id":1,"method":"tools/list"}\n' | docker run -i mcp-hey. The image has no login helper, so mount existing cookies at data/hey-cookies.json.
Tools
37 tools. Parameters, return shapes and errors are in docs/TOOLS.md.
Category | Tools |
Read |
|
Labels & Collections |
|
Send & drafts |
|
Triage |
|
Bubble up |
|
Screener |
|
Search & cache |
|
Sender groups
Every message a tool returns carries sender_group: where Hey delivers new mail from that sender (imbox, feed, paper_trail, screened_out, else unknown), from the "Deliver their emails to…" setting on their contact page. It describes the sender, not the message.
hey_search, hey_list_set_aside, hey_list_reply_later, hey_list_label_emails and hey_list_collection_emails take an optional group, for example group: ["imbox"] to leave out newsletters and receipts. By default (HEY_SENDER_GROUPS=filter) tools tag from the local cache and only a filtered call asks Hey; on looks up a few senders on every call, off removes the field. Details: docs/TOOLS.md.
Architecture
Component | Description |
MCP server | Bun/TypeScript on MCP TypeScript SDK v2. stdio serves MCP 2026-07-28 and older revisions; about 30 MB idle |
Auth helper | Python/pywebview, launched only when cookies are missing or expired |
Cache | Local SQLite for messages, threads and full-text search |
Requests carry browser-realistic headers, write operations fetch a fresh CSRF token first, and the client backs off on x-ratelimit.
Privacy and limitations
Only session cookies are stored (mode
600); login happens in Hey's own page. No telemetry, no network listener.Prompt injection: this server has the lethal trifecta. A malicious email could try to make Claude exfiltrate others, so review risky actions before approving them.
No calendar event creation, attachment uploads or real-time notifications. One account per instance.
Hey's HTML is parsed against English UI strings, so a non-English Hey locale will not work.
Unusual access patterns could trip Hey's anti-abuse systems despite the rate-limit handling.
Report vulnerabilities via SECURITY.md.
Troubleshooting
Webview does not open: check Python 3.10+ and that
python -c "import webview"works (Linux needs a webview backend).401/403after weeks: the session expired. Deletedata/hey-cookies.jsonand runbun run dev.Sustained
429s: reduce concurrent tool use or wait a few minutes.Auth silently fails after a Hey update: Hey has renamed session cookies before. See the
docs/API.mdchangelog.
Contributing
Conventional commits; bun run format, bun run lint and bun test must pass; update docs/API.md when Hey's behaviour changes. See CLAUDE.md.
Licence
MIT. See LICENCE.
Available Tools
37 toolshey_bubble_upAIdempotent
Schedule an email thread to bubble up (reappear) at a specific time. Returns {success, error?}. Reversible via hey_pop_bubble. Requires the thread's topic_id (use topic_id from any list operation); posting IDs are not accepted and will 404.
Inputs: slot (required), topic_id (required), date.
hey_bubble_up_if_no_replyAIdempotent
Schedule an email thread to bubble up ONLY if there's no reply by a deadline date. Returns {success, error?}. The thread only reappears if no reply arrives. Reversible via hey_pop_bubble. Requires the thread's topic_id (use topic_id from any list operation); posting IDs are not accepted.
Inputs: date (required), topic_id (required).
hey_cache_statusARead-only
Get local cache statistics: message counts, cache age, and storage estimate. Read-only with no side effects. Pass folder to scope message/unread counts to one cached view. Use before deciding whether to pass force_refresh on other list tools.
Inputs: folder.
hey_collectionAIdempotent
Add or remove an email thread from a collection. Returns {success, error?}. Reversible by calling again with the opposite action. Use hey_list_collections to discover collection IDs.
Inputs: action (required), topic_id (required), collection_id (required).
hey_delete_draftADestructiveIdempotent
Permanently delete a draft by ID. This does not move it to Trash — it is gone immediately, same as the trash icon in Hey's Drafts list. Returns {success, draftId, error?}. Irreversible; there is no hey_restore_draft. Use hey_list_emails(folder='drafts') first if you don't already have the draftId from hey_save_draft or hey_edit_draft.
Inputs: draft_id (required).
hey_download_attachmentA
Download a single attachment from an email and save it to disk. First call hey_read_email to get the attachments[] array with IDs, filenames, and sizes, then call this tool with the attachment_id to save the file. Returns {local_path, filename, size, mime}.
Inputs: email_id (required), attachment_id (required), save_path.
hey_edit_draftAIdempotent
Update an existing draft's recipients, subject, or body. Every field you pass replaces the draft's current value outright (no merging) — omit a field to leave it as Hey last saved it. Returns {success, draftId, error?}. Previous field values are not recoverable once overwritten, so re-fetch via hey_list_emails(folder='drafts') first if you need to preserve them. Use hey_save_draft to create a draft before calling this, and hey_delete_draft to remove one instead of blanking it out.
Inputs: draft_id (required), cc, to, body, +1 more.
hey_forwardA
Forward an existing email to new recipients immediately. The original thread remains unchanged. Returns {success, error?}. Use instead of hey_send_email when sharing existing content; use hey_reply for responding within a thread.
Inputs: to (required), entry_id (required), cc, bcc, +1 more.
hey_get_calendar_inviteARead-only
Extract and parse a calendar invite (.ics) from an email. First call hey_read_email — if calendar_invites[] is present, call this tool to get full details: title, start, end, location, attendees, organizer, description, and raw_ics. To save the .ics file to disk, use hey_download_attachment instead.
Inputs: email_id (required), attachment_id.
hey_imbox_summaryARead-only
Get a complete Imbox summary including screener count, bubbled up emails, and new emails. Use this for a comprehensive view of the inbox state.
Inputs: force_refresh.
hey_labelAIdempotent
Add or remove a label on an email thread. Returns {success, error?}. Reversible by calling again with the opposite action. Use hey_list_labels to discover available label IDs.
Inputs: action (required), label_id (required), topic_id (required).
hey_list_collection_emailsARead-only
List emails in a specific collection. Returns cached results unless force_refresh=true.
Inputs: collection_id (required), page, limit, force_refresh.
hey_list_collectionsARead-only
List all collections in Hey.com. Returns array of {id, name}. Use the id with hey_collection or hey_list_collection_emails.
Inputs: no named parameters.
hey_list_emailsARead-only
List emails in a Hey.com folder/view. Returns cached results unless force_refresh=true. Each email includes id, topicId, postingId, entryId, from, subject, date, and unread status. For folder=sent, from is always "Me" and the recipient (unverified against a live Sent page) is under to/toEmail instead.
Inputs: folder (required), page, limit, force_refresh.
hey_list_label_emailsARead-only
List emails with a specific label. Returns cached results unless force_refresh=true.
Inputs: label_id (required), page, limit, force_refresh.
hey_list_labelsARead-only
List all labels/folders in Hey.com. Returns array of {id, name}. Use the id with hey_label or hey_list_label_emails.
Inputs: no named parameters.
hey_list_reply_laterARead-only
List emails currently in the Reply Later stack. Returns cached results (same shape as hey_list_emails) unless force_refresh=true. Use hey_remove_reply_later with the returned postingId to remove an item.
Inputs: force_refresh.
hey_list_screenerARead-only
List senders waiting in the Screener for approval. Returns cached results (same shape as hey_list_emails, plus clearanceId) unless force_refresh=true. Use hey_screen_by_id with the returned clearanceId to approve or reject.
Inputs: force_refresh.
hey_list_set_asideARead-only
List emails currently in the Set Aside stack. Returns cached results (same shape as hey_list_emails) unless force_refresh=true. Use hey_unset_aside with the returned postingId to remove an item.
Inputs: force_refresh.
hey_mark_seenAIdempotent
Clear the orange 'New for you' tray dot — mirrors Hey's 'Mark all as seen' UI affordance. Returns {success, error?}. Pass a posting_id to clear just that one item (POST /postings/seen); omit posting_id to clear the entire Imbox tray in one call (POST /boxes/{imboxId}/observation). Reversible per-item via hey_mark_unseen. Use after triaging or skimming a batch to keep the tray tidy without re-marking threads unread.
Inputs: posting_id.
hey_mark_unseenAIdempotent
Mark a thread as unseen/unread to reset its read status. Returns {success, error?}. Reading the email via hey_read_email implicitly marks it as seen again. To clear the orange 'New for you' tray dot without re-marking unseen, use hey_mark_seen.
Inputs: id (required).
hey_move_toAIdempotent
Move an email thread between Hey.com views: imbox, feed, or paper_trail. Returns {success, error?}. Use paper_trail for receipts/automated mail, feed for newsletters, imbox to restore. Reversible by moving to a different destination. Does not affect trash, spam, or screener — use hey_set_status or hey_screen for those.
Inputs: id (required), destination (required).
hey_pop_bubbleAIdempotent
Pop (dismiss) a bubbled-up email thread so it sinks back into the Imbox. The thread is not deleted — it just stops being pinned at the top. Returns {success, error?}. Requires the thread's topic_id (use topic_id from any list operation); posting IDs are not accepted.
Inputs: topic_id (required).
hey_read_emailARead-only
Read an email thread's full content. Returns all messages in the thread via entries[] array (each with entryId, from, to, cc, date, body). Also returns attachments[] metadata and calendar_invites[] when present — use hey_download_attachment to save files to disk, or hey_get_calendar_invite to parse .ics details. Use format='html' (default) for rich content with thread entries, or format='text' for decoded RFC822 plain text of the first message.
Inputs: id (required), format, force_refresh.
hey_read_statusAIdempotent
Set the read/unread status of an email entry. Returns {success, error?}. Reversible by calling again with the opposite status. Operates on individual entries (use entryId), not whole threads. For marking an entire thread as unseen, use hey_mark_unseen instead.
Inputs: id (required), status (required).
hey_remove_reply_laterAIdempotent
Remove an email from Reply Later, marking it "Done" and moving it back to the Imbox. Returns {success, error?}. Requires the posting_id from hey_list_reply_later. Reversible by calling hey_reply_later again with the thread's topic_id.
Inputs: posting_id (required).
hey_replyA
Reply to an email thread, preserving threading on both ends. Returns {success, error?}. By default the reply goes to the other thread participants (your own address is automatically excluded); pass to to redirect to specific recipients — e.g. chasing your own thread without looping back to yourself, or redirecting off a mailing-list address. Prefer this over hey_send_email when responding to an existing thread; use hey_send_email only for new conversations.
Inputs: body (required), thread_id (required), cc, to.
hey_reply_laterAIdempotent
Move an email thread to Reply Later. Reversible via hey_remove_reply_later (requires postingId from hey_list_reply_later). Returns {success, error?}. Use for emails you intend to respond to but not right now — if no reply is planned, use hey_set_aside instead.
Inputs: id (required).
hey_save_draftA
Save a new draft email without sending it. Creates an entry in Hey's Drafts folder; recipients, subject, and body are all optional so a partially-written draft can be saved. Returns {success, draftId?, error?}. Sending isn't available via MCP yet — finish and send the draft from the Hey web/app UI, or use hey_edit_draft to keep revising it. Use hey_send_email/hey_reply instead when you want to send immediately rather than draft first.
Inputs: cc, to, body, subject.
hey_screenADestructiveIdempotent
Approve or reject a sender by email address. Approve: routes the sender's current and future emails into the chosen destination (defaults to imbox). Reject (a.k.a. screen out): blocks the sender from sending you further emails — works for both pending screener entries AND already-approved senders (falls back to the contact-page 'Screened Out' affordance via /contacts/{id}/clearance). Reject does NOT flag emails as spam; existing emails are left untouched. Reversible from the Hey UI by visiting the contact page; not yet exposed via MCP. Returns {success, error?}. Use hey_list_screener to see pending senders, or hey_screen_by_id for clearance IDs.
Inputs: action (required), sender_email (required), destination.
hey_screen_by_idADestructiveIdempotent
Approve or reject a first-time sender from the Screener by clearance ID. Approve: allows future emails from this sender into the chosen destination (defaults to imbox). Reject: blocks future emails from this sender via the screener. Reversible from the Hey UI's contact page (not yet via MCP). Returns {success, error?}. Use hey_list_screener to get clearance IDs; for senders that have already left the screener, use hey_screen by email instead.
Inputs: action (required), clearance_id (required), destination.
hey_searchARead-only
Search emails by query. Uses local FTS cache first, then network. Use force_refresh for real-time results.
Inputs: query (required), limit, force_refresh.
hey_send_emailA
Send a new email immediately (no draft stage). Returns {success, error?}. Use for standalone outbound messages; use hey_reply for thread responses, or hey_forward to share existing emails with new recipients.
Inputs: to (required), body (required), subject (required), cc.
hey_set_asideAIdempotent
Move an email thread to Set Aside for later. Reversible via hey_unset_aside (requires postingId from hey_list_set_aside). Returns {success, error?}. Use for emails you plan to revisit but don't need to reply to — for emails needing a reply, use hey_reply_later instead. Does not affect future emails from the sender.
Inputs: id (required).
hey_set_statusADestructiveIdempotent
Change an email thread's status. Returns {success, error?}. Trash and spam are reversible via restore and unspam actions respectively. DESTRUCTIVE: trash removes from Imbox, spam blocks the sender. Paper Trail bundles (postingId-only items with no thread) support action=trash only; spam/restore/unspam on a bundle return an explicit error pointing to the bundle's individual entries.
Inputs: id (required), action (required).
hey_thread_muteAIdempotent
Mute or unmute a thread (called 'Ignore' in Hey.com's UI). Muting stops notifications for the thread but keeps it in its current view — the thread is not moved or deleted. Returns {success, error?}. Reversible by calling with the opposite action. To check if a thread is currently muted, use hey_read_email — the response includes a 'muted' field.
Inputs: action (required), posting_id (required).
hey_unset_asideAIdempotent
Remove an email from Set Aside, moving it back to the Imbox or its original location. Returns {success, error?}. Requires the posting_id from hey_list_set_aside. Reversible by calling hey_set_aside again with the thread's topic_id.
Inputs: posting_id (required).
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 37 tools
Most tools target clearly distinct actions, and descriptions explicitly guide choice between similar pairs (hey_screen vs hey_screen_by_id, hey_set_aside vs hey_reply_later, hey_bubble_up vs hey_bubble_up_if_no_reply). The main soft spot is the read-state trio (hey_mark_seen, hey_mark_unseen, hey_read_status), which is subtly differentiated (tray dot vs thread vs entry) and could trip up an agent, but the descriptions do address it.
Every tool uses a consistent hey_ snake_case prefix, which makes the set scan cleanly. Minor deviation: some names are verb_noun (hey_list_emails, hey_send_email) while others are state-noun or ad hoc (hey_imbox_summary, hey_cache_status, hey_thread_mute), but the convention is stable and readable throughout.
At 37 tools this is on the heavy side and pushes past the comfortable 3-15 band. The email-client domain justifies breadth, but several clusters could be consolidated (three read-state tools, three bubble-up tools, separate list/view tools) rather than expanded separately.
Coverage is broad: listing across views, search, read, reply/forward/send, drafts, labels, collections, screener, set-aside/reply-later, bubbling, muting, moving, and status changes. Explicit gaps are acknowledged (drafts can't be sent via MCP, screener reversals are UI-only), which are minor relative to the surface.
Maintenance
Related MCP Connectors
Your IMAP mailbox as an MCP server: read, search and (if you allow it) organize mail. Open source.
MCP server for Nylas — read email, calendars, events and contacts, and send email or create events.
MCP server for MailTempo's public free temporary email inboxes.
Email infrastructure for AI agents — send, receive, search, and reply to email over MCP.
Related MCP Servers
- MIT
- AlicenseNot gradedqualityDmaintenanceEnables semantic search and AI-powered analysis of Outlook emails using RAG-based natural language queries and Vision AI for architectural documents, with specialized support for AEC workflows.MIT
- AlicenseAqualityDmaintenanceLocal MCP server for multi-account IMAP/SMTP email (iCloud + Gmail via app-specific passwords). Never marks mail read. Cross-folder search, idempotent sends, TLS verified.8MIT
- AlicenseAqualityCmaintenanceAn MCP server that exposes a local notmuch email database to an LLM client such as Claude. It is read-first: searching, reading, and understanding mail is always available; writing anything (drafts, tags, exported files) requires an explicit opt-in flag and is confined to clearly bounded locations.133MIT