mcp-hey
# mcp-hey
[](https://glama.ai/mcp/servers/Sealjay/mcp-hey)
[](https://bun.sh)
[](https://www.typescriptlang.org/)
[](https://www.python.org/)
[](https://modelcontextprotocol.io/)
[](LICENCE)
[](https://github.com/Sealjay/mcp-hey/issues)
[](https://github.com/Sealjay/mcp-hey)
A local MCP server that gives Claude read/write access to your [Hey.com](https://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`](docs/API.md).
## Setup
**Prerequisites:** [Bun](https://bun.sh) 1.1+, Python 3.10+ and a Hey.com account. Tested on macOS and Linux; Windows likely needs WSL.
```bash
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+C
```
The helper saves session cookies to `data/hey-cookies.json` (mode `600`). Later runs reuse them until they expire.
## MCP client configuration
```json
{
"mcpServers": {
"hey": {
"command": "bun",
"args": ["run", "/absolute/path/to/mcp-hey/src/index.ts"]
}
}
}
```
| Client | Where |
|--------|-------|
| Claude Code | `claude mcp add --transport stdio hey --scope user -- bun run /absolute/path/to/mcp-hey/src/index.ts`, or the block above in `.mcp.json` |
| Claude Desktop | `~/Library/Application Support/Claude/claude_desktop_config.json` |
| Cursor | `~/.cursor/mcp.json` |
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`](docs/TOOLS.md).
| Category | Tools |
|----------|-------|
| Read | `hey_list_emails` (imbox, feed, paper_trail, trash, spam, drafts, sent), `hey_imbox_summary`, `hey_list_set_aside`, `hey_list_reply_later`, `hey_list_screener`, `hey_read_email`, `hey_download_attachment`, `hey_get_calendar_invite` |
| Labels & Collections | `hey_list_labels`, `hey_list_label_emails`, `hey_label`, `hey_list_collections`, `hey_list_collection_emails`, `hey_collection` |
| Send & drafts | `hey_send_email`, `hey_reply`, `hey_forward`, `hey_save_draft`, `hey_edit_draft`, `hey_delete_draft` |
| Triage | `hey_set_aside`, `hey_unset_aside`, `hey_reply_later`, `hey_remove_reply_later`, `hey_move_to`, `hey_set_status`, `hey_mark_unseen`, `hey_mark_seen`, `hey_read_status`, `hey_thread_mute` |
| Bubble up | `hey_bubble_up`, `hey_bubble_up_if_no_reply`, `hey_pop_bubble` |
| Screener | `hey_screen`, `hey_screen_by_id` |
| Search & cache | `hey_search`, `hey_cache_status` |
## 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`](docs/TOOLS.md#filtering-by-sender-group).
## 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](https://simonwillison.net/2025/Jun/16/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`](SECURITY.md).
## Troubleshooting
- **Webview does not open:** check Python 3.10+ and that `python -c "import webview"` works (Linux needs a webview backend).
- **`401`/`403` after weeks:** the session expired. Delete `data/hey-cookies.json` and run `bun run dev`.
- **Sustained `429`s:** 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.md`](docs/API.md) changelog.
## Contributing
Conventional commits; `bun run format`, `bun run lint` and `bun test` must pass; update [`docs/API.md`](docs/API.md) when Hey's behaviour changes. See [`CLAUDE.md`](CLAUDE.md).
## Licence
MIT. See [LICENCE](LICENCE).
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.