Skip to main content
Glama
mithun2k5

yahoo-mail-mcp

by mithun2k5
README.md
# Yahoo Mail MCP

Control Yahoo Mail from Claude in plain English — read, search, organize, delete,
and send — with a two-phase confirm step so nothing destructive happens without
you seeing exactly what it would touch.

Claude's connector directory covers Gmail and Outlook, but not Yahoo. Yahoo does
still support IMAP and SMTP with an app password, which is all an MCP server
needs.

```
"Find every email from jobalerts-noreply@linkedin.com"
  → 276 matched, 47MB, candidate_set_id: cs_330a2109377c

"Delete those"
  → PREVIEW - nothing has been deleted yet.
    276 message(s), move to Trash - recoverable
    confirm_token='tok_9f31...' (expires in 10 minutes)

"Yes"
  → 276/276 message(s) moved to Trash from INBOX.
```

---

## How it works

```
  You, in plain English
          │
   Claude Desktop
          │  stdio (MCP protocol)
          ▼
      server.py ─────────────┐
          │                  │
          │ IMAP 993         │ local SQLite index
          ▼                  ▼
  imap.mail.yahoo.com     index.db   (headers only, never bodies)
  smtp.mail.yahoo.com
```

**A local header index.** The server mirrors message *metadata* — sender,
subject, date, size, flags — into SQLite. Never bodies. Searching runs against
that index, so "everything from LinkedIn older than a year" is a local SQL query
returning in milliseconds instead of a slow IMAP round trip. 10,000 headers take
about 5 MB and roughly 25 seconds to sync.

**Candidate sets.** A search freezes every matching UID under an id like
`cs_330a2109377c` and returns it. Acting on that id operates on exactly the set
you were shown — not on a re-run of the query that might match differently a
minute later. This is what separates a safe bulk delete from a risky one.

**Two-phase confirm.** `mail_delete` and `mail_send` preview on the first call
and execute only when handed back the token they issued. The first call has no
code path to IMAP `STORE`/`MOVE` or SMTP at all — it cannot mutate anything even
if instructed to.

---

## Install

Requires **macOS**, **Claude Desktop**, and **Python 3.10+** (macOS ships 3.9,
which the MCP SDK rejects — `brew install python@3.12`, or use conda/pyenv).

```bash
git clone https://github.com/<your-username>/yahoo-mail-mcp.git
cd yahoo-mail-mcp
bash setup.sh
```

The installer finds a suitable Python, prompts for your Yahoo address and a
**app password** (at <https://login.yahoo.com/account/security> →
**External connections** → **Create app password** — your normal login password
will not authenticate over IMAP), stores that password in
the macOS login Keychain, builds a venv, smoke-tests the connection, and merges
itself into `claude_desktop_config.json` without disturbing servers already
registered there.

Then **`Cmd + Q`** Claude Desktop and reopen it. Closing the window is not
enough; MCP servers are launched at process start.

Full walkthrough with checkpoints and troubleshooting: **[INSTALL.md](INSTALL.md)**

---

## Tools

| Tool | What it does |
|---|---|
| `mail_status` | Connection check and index freshness. Start here when debugging. |
| `mail_folders` | List folders and their special-use roles. |
| `mail_sync` | Pull headers into the local index. Incremental. Headers only. |
| `mail_search` | Query the index; returns a preview **and a `candidate_set_id`**. |
| `mail_read` | Full body and attachment list for one message. |
| `mail_mark` | read / unread / star / unstar. Reversible. |
| `mail_move` | Move to any folder, or `archive` / `junk` / `trash`. Reversible. |
| `mail_delete` | **Two-phase.** Trash by default; `permanent=True` expunges. |
| `mail_send` | **Two-phase.** Send, reply (threaded), or save a draft. |
| `mail_audit` | Every mutation the server has made, with counts. |

### Things to try

```
"Sync my Yahoo inbox"
"What are my biggest emails from the last year?"
"Show me unread mail from this week"
"Find everything from noreply@example.com older than 6 months"
"Move those to Archive"
"Delete those"                    → previews first, then asks you to confirm
"Reply to the message from Sarah saying I'll review it Monday"
"Show me the mail audit log"
```

---

## Safety design

| Property | Behavior |
|---|---|
| Two-phase confirm | `mail_delete` / `mail_send` preview first; no mutation path on call one |
| Single-use tokens | Replaying a used token is refused |
| 10-minute expiry | Stale approvals die on their own |
| Set-bound | A token carries an exact UID list; the query is never re-run |
| Action-bound | A `send` token presented to `delete` is refused |
| Trash by default | `permanent=True` required to expunge; the preview says `NOT RECOVERABLE` |
| 2,000-message cap | A mis-scoped query cannot empty an inbox in one call |
| UIDVALIDITY tracking | Server-side renumbering rebuilds the index instead of acting on stale UIDs |
| Deletion reconcile | Sync drops rows for server-side deletions; a failed `SEARCH` aborts rather than false-wiping |
| Audit log | Every mutation recorded with requested/succeeded/failed counts |
| Keychain credentials | App password never in config files, environment, or logs |

---

## Known limitations

- **Yahoo caps `SEARCH ALL` at 10,000 results**, silently. If your inbox is
  larger, plain sync sees only the newest 10,000 and warns you when the total
  lands on exactly that number. `deep=True` walks backwards in date windows
  instead, and each window returns an untruncated set:

  ```
  "Sync my Yahoo inbox with deep=True"
  ```

- **Trash counts may disagree** with what you moved there. Yahoo appears to apply
  its own limits, and possibly deduplication, to that folder. Treat webmail as
  ground truth when numbers don't reconcile.
- **`body=` search is server-side** and much slower than every other filter,
  since it cannot use the local index.
- **Keychain storage is macOS-only.** The rest is portable — other platforms can
  supply the app password via the `YAHOO_APP_PASSWORD` environment variable.
- **Yahoo-specific folder names** are handled (`Bulk` for spam, `Draft`
  singular), but other IMAP providers are untested.

---

## Files

| Path | Purpose |
|---|---|
| `server.py` | The MCP server. Single file, standard library plus `mcp`. |
| `setup.sh` | Installer: Python detection, Keychain, venv, config merge. |
| `INSTALL.md` | Step-by-step guide with checkpoints and troubleshooting. |
| `requirements.txt` | Just `mcp`. |
| `config.json` | *Generated.* Your address only — no secret. Git-ignored. |
| `index.db` | *Generated.* Local header index. Git-ignored. |
| `venv/` | *Generated.* Git-ignored. |

`config.json`, `index.db`, and `venv/` are created by `setup.sh` and excluded by
`.gitignore` — they hold personal data and must never be committed.

---

## Uninstall

```bash
# remove the stored app password
security delete-generic-password -s yahoo-mail-mcp -a you@yahoo.com

# remove the server entry from Claude Desktop
python3 - <<'PY'
import json, os
p = os.path.expanduser("~/Library/Application Support/Claude/claude_desktop_config.json")
d = json.load(open(p))
d.get("mcpServers", {}).pop("yahoo-mail", None)
json.dump(d, open(p, "w"), indent=2)
PY
```

Then delete the cloned directory, and delete the app password at
<https://login.yahoo.com/account/security> → **External connections** → app
passwords → **Delete**. That is what actually cuts off access, regardless of what
is left on disk — note that **changing your Yahoo account password does not
revoke app passwords**; only deleting them does.

---

## Configuration

| Setting | Value |
|---|---|
| IMAP | `imap.mail.yahoo.com:993` SSL |
| SMTP | `smtp.mail.yahoo.com:465` SSL |
| Keychain service | `yahoo-mail-mcp` |
| Data directory | The repo directory, or `$YAHOO_MCP_DIR` if set |

`YAHOO_EMAIL` and `YAHOO_APP_PASSWORD` override `config.json` and the Keychain
respectively, which is how you would run this on a non-macOS host.

## License

MIT — see [LICENSE](LICENSE).