Yahoo Mail MCP Server
This server gives an MCP client full read/write management of a Yahoo Mail mailbox over IMAP — browsing, searching, reading, organizing, downloading attachments, and drafting (but never sending) email.
List emails from any folder with UIDs and metadata (size, flags, attachment status), with pagination via
count/offset.Read emails in batches by UID, returning headers, size, flags, attachment list, and body.
Search emails with filters for query text, sender, date range, unread-only, and folder.
Discover folders with
list_foldersto find names for other operations.Organize mail: move to Trash (
delete_emails, recoverable soft delete), archive, mark read/unread, flag/unflag, and move to any folder — all batched by UID.Download attachments from an email to disk, optionally filtering by filename or target directory.
Draft email without ever sending: create new drafts, create threaded reply drafts (with reply-all and quoting), and revise drafts via
update_draft(note: each update assigns a new UID).Attach local files to drafts and remove them, subject to an allowed-folder policy.
Everything is UID-based (permanent identifiers), and no send capability exists — an agent can at most leave a draft for the user to review.
Reads the Yahoo app password from 1Password via the 1Password CLI, enabling per-read approval with Windows Hello on Windows.
Supports running the server in a Docker container, reading the app password from a mounted secret file.
Supports hosting the server on Fly.io.
Reads the Yahoo app password from the GNOME Keyring (via secret-tool) on Linux.
Stores and reads the Yahoo app password from the macOS Keychain (with per-read approval prompts), and saves downloaded attachments as private files with the quarantine tag.
Supports deployment on Render, including Streamable HTTP transport that survives cold starts, with the app password read from a Render Secret File.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Yahoo Mail MCP Server find unread emails from this week and mark them as read"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Yahoo Mail MCP Server
A Model Context Protocol (MCP) server that provides full email management for Yahoo Mail via IMAP. It works with any MCP client (Claude, ChatGPT, Cursor, VS Code, and others): local stdio transport for desktop apps, and Streamable HTTP (plus legacy SSE) for remote access.
Forked from jtokib/yahoo-mail-mcp-server (MIT). The original provides the IMAP server, UID-based email tools, OAuth 2.0 flow, and Render/Docker deployment. This fork adds the features below.
What's New in This Fork
Draft emails from any AI assistant:
create_draft,create_reply_draft(threaded, with reply-all and quoting), andupdate_draft, so the assistant can revise a draft from your feedback. Drafts land in Yahoo Mail's Drafts folder; nothing is ever sent without you.Attachments:
download_attachmentssaves files to disk, andread_emaillists attachment names, types, and sizes.Streamable HTTP: the current MCP transport at
/mcp, stateless, so it survives cold starts and sleeping hosts. Legacy SSE still works.One shared IMAP login: tool calls reuse a single Yahoo login instead of logging in on every call, which avoids Yahoo's login throttling.
Faster bulk actions: read/unread, flag, archive, and move run as one IMAP command; delete stays one email at a time.
Bug fixes: empty
read_emailresults for large emails, multi-email reads returning only the first email, sizes always 0, invalid search dates silently ignored, and a missingisErrorflag on errors.Prompt-injection defenses: everything a sender wrote is returned inside clearly marked untrusted-content blocks that the email can't close early; hidden HTML and invisible characters are stripped; bodies are length-capped; tools carry MCP risk annotations; and
READ_ONLY/ENABLED_TOOLSlimit which tools exist at all.Safety hooks: automatic checks the AI can't switch off. Emails with payment red flags (changed bank details, IBANs, wire transfers, gift cards, crypto, urgency), a redirected Reply-To, or a spoofed sender name get server warnings; programs and scripts are never saved from attachments (even when disguised as a PDF or hidden inside a ZIP); saved files are private and tagged as downloaded, like browser downloads; drafts can only attach files from one allowed folder. Add your own rules with
SAFETY_HOOKS_MODULE.App password only from a password store:
YAHOO_APP_PASSWORD_COMMANDreads it from macOS Keychain, 1Password, secret-tool, pass, or a mounted secret file. Plain-text passwords in.envare refused. With Keychain, every read needs your approval.Sign-in page with MFA: connecting an app opens a sign-in page (username, password, and a 6-digit authenticator code), like other connectors. Passwords are stored only as scrypt hashes, codes can't be reused, and 5 failed attempts lock an address out for 15 minutes.
npm run setup-logincreates the settings.OAuth hardening: signed access tokens that really expire after 1 hour, plus refresh tokens so clients stay connected without re-login, even across restarts and Render sleep. Authorization codes are random, single-use, and valid for 60 seconds. The
redirect_uricheck matches the exact hostname (the old substring check accepted URLs likehttps://evil.example/?claude.ai), and HTTP mode refuses to start without OAuth configured.Offline test suite:
npm testruns 97 tests against fake IMAP servers and a local HTTP server, with no real email login.
Related MCP server: Yahoo Mail MCP Server
Project Status
Area | Status |
Local mode (stdio) with Claude Desktop | ✅ Tested against a real Yahoo mailbox: folders, search, multi-email reads, attachment download, new/reply/revised drafts, bulk flag/unflag, error handling |
Other local clients (Cursor, VS Code, Codex CLI, ...) | ⚠️ Should work (standard MCP stdio), not yet tested |
App password from macOS Keychain | ✅ Tested with Claude Desktop on a real mailbox: the server reads the password from Keychain (no password in any file) and works normally |
App password from Windows Credential Manager ( | ⚠️ Not yet tested on Windows |
Download hardening (ZIP inspection, size limit, private files, download tag) | ✅ Tested with Claude Desktop on macOS: real downloads are saved private ( |
Hosted mode (Streamable HTTP, OAuth, sign-in page with MFA) | ⚠️ Experimental. Covered by the offline test suite (97 tests, including the full sign-in and token flow and the official MCP SDK client), but not yet tested end-to-end on Render or with Claude.ai / ChatGPT connectors |
ChatGPT connectors | ❓ Unverified. This server doesn't support dynamic client registration, so the client must let you enter a client ID and secret |
Feedback and issue reports from hosted setups are very welcome.
Where Your Credentials Live (Read This First)
This server needs your Yahoo app password: a 16-character password Yahoo generates for one app (like a personal access token). It gives full mailbox access (read, move, delete, drafts) and also works for sending mail over SMTP. This server never sends, but anyone who steals the password could send as you and damage your sender reputation. It bypasses 2-step verification and never expires until you revoke it. Treat it like your real password. Where it lives depends on how you run the server:
Setup | Who can see the app password | Who can see your email content | Use it from |
Local (recommended): Claude Desktop, Cursor, VS Code, Codex CLI start the server on your computer | Only your computer, in a password store such as macOS Keychain (see below) | Only your computer | That computer's desktop apps |
Hosted (optional): Render, Fly.io, a VPS, etc. | The hosting provider and anyone with access to your hosting account | Passes through the host's servers while tools run | Anywhere: Claude.ai web/mobile, ChatGPT, other remote MCP clients |
Local setup: the server runs as a local process, talks to the app through a pipe (stdio), and opens no network port. Your credentials never leave your machine. No sign-in page is needed because nothing is reachable from outside.
Keep the app password in a password store, not a file
The server only reads the app password through YAHOO_APP_PASSWORD_COMMAND, a command that prints it. There is no plain-text option: if YAHOO_APP_PASSWORD is set, the server refuses to start. It runs the command once when it first needs to log in, keeps the result in memory only (never on disk or in logs), and runs it again after a failed login, for example after you rotate the password.
macOS Keychain (recommended on a Mac):
# 1. Store it. Type the app password at the prompt (hidden; it never appears in your shell history).
# -T "" trusts no app, so macOS asks you to Allow or Deny every read.
security add-generic-password -a "you@yahoo.com" -s yahoo-mail-mcp -T "" -w
# 2. In .env, set:
YAHOO_APP_PASSWORD_COMMAND=security find-generic-password -a you@yahoo.com -s yahoo-mail-mcp -wWhen your MCP app starts the server, macOS asks whether security may use the item:
Click Allow (not "Always Allow") to keep approving each start. No program, including an AI assistant running commands on your machine, can then read the password without you seeing a dialog.
"Always Allow" skips future dialogs, but then any program running as you can read it silently with the same command.
To update it after rotating: run the same add-generic-password command with -U added. To remove it: security delete-generic-password -a "you@yahoo.com" -s yahoo-mail-mcp.
Windows Credential Manager:
# 1. Store it. You'll be prompted for the app password (hidden input).
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\windows-credential.ps1 -Set -User you@yahoo.com
# 2. In .env (use the full path to your copy of the repository; quote it if it contains spaces):
YAHOO_APP_PASSWORD_COMMAND=powershell -NoProfile -ExecutionPolicy Bypass -File "C:\path\to\yahoo-mail-mcp\scripts\windows-credential.ps1"The script uses Windows' own credential functions (no extra modules) and stores a Generic credential named yahoo-mail-mcp, visible under Control Panel > Credential Manager > Windows Credentials. Unlike Keychain's strict mode, Credential Manager doesn't ask before each read: any program running as your Windows user can read it. It is still encrypted by Windows and kept out of every project file. For per-read approval on Windows, use the 1Password CLI with Windows Hello (below).
Other password stores: YAHOO_APP_PASSWORD_COMMAND works with any command that prints the password, for example:
1Password CLI:
op read "op://Private/Yahoo MCP/password"Linux (GNOME Keyring, KWallet):
secret-tool lookup service yahoo-mail-mcppass:
pass show yahoo-mail-mcpDocker: a secret file mounted into the container,
cat /run/secrets/yahoo-app-password(see Docker Usage)Render: a Render Secret File,
cat /etc/secrets/yahoo-app-password(see Deploying to Render)
Rotate after testing, and on a short schedule
After testing: revoke the app password at Yahoo account security when you finish testing or stop using a setup, then generate a new one for ongoing use. Yahoo lets you revoke app passwords one at a time without changing your main password.
On a schedule: treat rotation as a hard policy, not a clean-up task. Replace the app password at the shortest interval that's practical for you, so a leaked one stops working quickly even if nobody notices the leak. With a password store, rotating is two steps: generate a new app password, then update the stored one (for Keychain, run the
add-generic-passwordcommand again with-U). The server re-reads it automatically after the next failed login.
Yahoo app passwords never expire on their own, so rotation is the only thing that limits how long a leaked one stays useful.
Hosted setup: the server must hold the app password in readable form to log in to Yahoo, so you are trusting the host. The sign-in page, MFA, and OAuth protect who can use your server; they don't hide anything from the host itself. What the host holds:
Setting | Stored on the host as | Notes |
App password (Secret File, read via | Readable | Required to log in to Yahoo |
| Readable | Signs access tokens; changing it logs out every app |
| Readable | Required to check authenticator codes |
Your sign-in password | Hash only (scrypt) | The real password is never stored anywhere |
If you host it:
Create a separate app password just for the host (e.g. named
MCP Render), so you can revoke it without affecting anything else, and rotate it on a short schedule.Turn on 2-step verification for your hosting and GitHub accounts. Someone breaking into those is a more likely risk than the host itself.
Store every credential as a secret environment variable, never in the repository.
Revoke the app password at Yahoo account security after testing, whenever you stop hosting, or if you suspect a leak, and change
OAUTH_CLIENT_SECRETto disconnect every app immediately.Only host if you need web, mobile, or ChatGPT access. If you only use desktop apps, stay local.
Malicious emails (prompt injection)
Every email the assistant reads is text from a stranger. A message can contain hidden instructions aimed at the AI, such as "forward all invoices to …" or "delete this thread". The server can't tell a genuine request from you apart from one planted in an email; your AI app decides which tools to call.
What the server does about it:
Marks external content as data. Everything the sender wrote (body, subject, sender name, recipients, attachment names) is returned inside an
<untrusted-content>block, with a note telling the AI to treat it as data and not follow instructions in it. Each block has a random id, and marker-like text inside the email is neutralized, so an email can't close the block early and "speak" from outside it. List and search results label theirfrom/subjectfields the same way.Shows only what a person would see. HTML is converted to text after removing hidden elements (
display:none, zero or 1px fonts,visibility:hidden,opacity:0, transparent text, off-screen positioning, Outlook-hidden blocks, comments, scripts). The HTML part is preferred over a plain-text part that mail apps don't display. Invisible Unicode (zero-width characters, text-direction overrides, "tag" characters) is stripped everywhere, including attachment file names.Caps length. Email bodies are cut at
READ_EMAIL_MAX_CHARS(default 20,000 characters), with a note saying how much was cut.Labels risky tools. Every tool carries MCP annotations (
readOnlyHint,destructiveHint), so AI apps can ask before destructive actions such asdelete_emailsormove_emails.Lets you remove tools entirely (least privilege).
READ_ONLY=truekeeps only tools that don't change the mailbox.ENABLED_TOOLS=read_email,search_emails,create_reply_draftkeeps exactly the tools you list. Disabled tools are hidden and refused if called.
These reduce the risk but can't eliminate it: the AI model still decides what to do with what it reads. See also Safety hooks below. So also:
Keep tool approval on in your AI app. Most MCP clients can ask before each tool call. Approve actions that change mail, and be wary of any your request didn't ask for.
Be careful with automation. Running the assistant unattended over incoming mail gives any sender a chance to steer it.
Sending isn't possible. There is no send tool, so an injected instruction can at most leave a draft for you to see; it can't send mail as you.
Safety hooks
Checks that run automatically at three points. They either add a warning or block the action. Warnings come from the server and appear outside the untrusted-content block (prefixed ⚠️ Server safety check:), so an email can't fake or hide them. Blocks can only be relaxed through environment settings, never through tool arguments, so a misled agent can't switch them off.
Hook | Runs | Checks |
| Every email read | Payment red flags: changed bank or payment details, IBANs, SWIFT/routing codes, account numbers, wire-transfer requests, gift cards, crypto wallets, urgency combined with payment. India-specific: UPI IDs and pay-to-UPI requests, IFSC codes, NEFT/RTGS/IMPS transfer requests, requests for an OTP/UPI PIN/CVV, KYC/PAN/Aadhaar "verification" demands, disconnection threats, courier/customs fees, and ₹/Rs/INR amounts with urgency (written to stay quiet on routine bank and fund emails). Reply-To that differs from the sender. Display-name spoofing such as |
| Before an attachment is written to disk | Blocks programs and scripts: by extension ( |
| After a file is saved (custom hooks only) | Receives |
| Before a draft is saved | Blocks attachments from outside the allowed folders (default: |
Every saved attachment is also:
Private: readable and writable by you only (
0600), never executable; a download folder the server creates is0700.Marked as downloaded: on macOS the file gets the
com.apple.quarantinetag browsers add. Documents such as PDFs still open normally; the tag matters for programs and archives: Gatekeeper checks any app, installer, or script before it runs, and files extracted from a tagged archive (e.g. a RAR or 7z the server can't look inside) inherit the tag. On Windows the file gets the Mark of the Web (Zone.Identifier, internet zone), so SmartScreen applies and Office opens documents in Protected View. If the tag can't be set, the file is deleted rather than left unchecked (fail closed).ATTACHMENT_QUARANTINE=falseturns this off.
The payment checks are warnings, not blocks: they look for patterns, and legitimate invoices trigger them too. The point is to make you verify payment requests through a contact you already trust.
Custom hooks: set SAFETY_HOOKS_MODULE to a JavaScript module exporting any of readEmail(ctx), beforeSaveAttachment(ctx), beforeDraft(ctx), each returning { warnings: [...], block: "reason" } (or a promise of it). Custom hooks run in addition to the built-in ones. If a custom hook throws, the action is blocked (fails closed). Example:
// my-hooks.mjs: never draft anything mentioning a wire transfer
export function beforeDraft(ctx) {
if (/wire transfer/i.test(`${ctx.subject}\n${ctx.body}`)) {
return { block: 'Drafts about wire transfers must be written by hand.' };
}
return { warnings: [] };
}Antivirus example (ClamAV): scan each saved file, and delete it if a threat is found.
// av-hooks.mjs: requires ClamAV (e.g. `brew install clamav`, then set up its database)
import { execFile } from 'node:child_process';
export function afterSaveAttachment(ctx) {
return new Promise((resolve) => {
execFile('clamscan', ['--no-summary', ctx.filePath], (err, stdout) => {
if (!err) return resolve({ warnings: [] }); // exit 0: clean
if (err.code === 1) return resolve({ block: `Antivirus: ${stdout.trim()}` }); // exit 1: threat found
resolve({ block: `Antivirus scan failed for ${ctx.filename}` }); // anything else: fail closed
});
});
}Two different risks, two different controls
Risk | What limits it |
The agent is misled (prompt injection, mistakes) | Untrusted-content marking and hidden-text removal, fewer tools ( |
The app password leaks | Rotation and revocation. A leaked password bypasses this server entirely and gives full mailbox access (including sending) directly, whatever tools the server has |
Limiting tools does not protect a leaked credential, and rotating credentials does not stop a misled agent. You need both.
Logging: the server logs request paths, OAuth events, and connection errors. It is written not to log passwords, tokens, secrets, email addresses, or email content.
Never sent, never shared: the server has no send-mail capability (drafts only), and each deployment serves one mailbox: yours. Nobody else's credentials are involved, and you never need to give yours to anyone else's server.
Architecture
Local mode (default)
Your MCP client starts the server as a background process on your computer and talks to it over a private pipe. Nothing listens on a network port; the only outbound connection is to the mail server.
flowchart LR
subgraph Mac["Your computer"]
Host["Claude Desktop / Codex / Cursor<br/>(MCP host + client)"]
Server["node server.js<br/>yahoo-mail-mcp · Node.js"]
Keychain[("OS keychain<br/>app password")]
Env[".env<br/>address + password command"]
Downloads[("~/Downloads/yahoo-attachments")]
end
Yahoo[("imap.mail.yahoo.com<br/>your mailbox")]
Host <-->|"MCP · JSON-RPC 2.0 over stdio<br/>no network port"| Server
Server -->|"child process<br/>security find-generic-password"| Keychain
Server -->|"read at startup"| Env
Server -->|"file write 0600<br/>+ quarantine tag"| Downloads
Server <-->|"IMAP over TLS 1.2+<br/>TCP 993"| YahooHosted mode (experimental)
For web and mobile clients. The server runs on a host such as Render behind HTTPS and OAuth with a sign-in page.
flowchart LR
Cloud["Claude.ai / ChatGPT<br/>(cloud MCP client)"]
Browser["Your browser<br/>sign-in page"]
TOTP["Authenticator app<br/>(TOTP)"]
subgraph Host["Render (or any host)"]
Proxy["HTTPS proxy<br/>TLS on port 443"]
App["Docker · Node.js 22<br/>Express · server.js + auth.js<br/>PORT (default 3000)"]
Secret[("Secret file<br/>app password")]
end
Yahoo[("imap.mail.yahoo.com")]
Cloud -->|"HTTPS 443<br/>POST /mcp (Streamable HTTP)<br/>Bearer token"| Proxy
Cloud -->|"OAuth 2.0 + PKCE<br/>POST /oauth/token"| Proxy
Browser -->|"HTTPS 443<br/>/oauth/authorize"| Proxy
TOTP -.->|"6-digit code, typed by you"| Browser
Proxy -->|"HTTP"| App
App -->|"password command"| Secret
App <-->|"IMAP over TLS<br/>TCP 993"| YahooConnections
From → To | Protocol | Port | Technology | Mode |
MCP client → server | MCP (JSON-RPC 2.0) over stdio pipes | none |
| Local |
Server → OS keychain | Child process running | none | Node.js | Both |
Server → mail server | IMAP over TLS 1.2+ (LOGIN, EXAMINE/SELECT, UID SEARCH, UID FETCH with BODY.PEEK, APPEND, UID STORE/EXPUNGE/MOVE) | 993 |
| Both |
Server → disk | File writes ( | none | Node.js | Local |
Cloud client → server | HTTPS: Streamable HTTP | 443 → | Express, MCP SDK | Hosted |
Cloud client → server | OAuth 2.0 authorization code + PKCE; signed access (1 h) and refresh (30 days) tokens | 443 | Node.js | Hosted |
Browser → server | HTTPS sign-in page: username, password (scrypt hash), TOTP code | 443 |
| Hosted |
Server → SMTP | Never: the server has no send capability | (unused) | - | - |
Features
Secure OAuth 2.0 Authentication: Protect your remote MCP server with OAuth 2.0 authorization code flow with PKCE
UID-Based Operations: Uses permanent IMAP UIDs that don't change when emails are deleted (v3.0.0+)
Full Email Management: Complete email operations with batch processing support
Fifteen Tools:
list_emails: List recent emails with enriched metadata (size, flags, attachments) and paginationread_email: Read the full content of emails (batch support)search_emails: Advanced search with filters (date ranges, sender, unread status)list_folders: Discover all available IMAP foldersdelete_emails: Move emails to Trash (soft delete, recoverable)archive_emails: Archive emails for long-term storagemark_as_read: Mark emails as readmark_as_unread: Mark emails as unreadflag_emails: Flag emails as important/starredunflag_emails: Remove flag from emailsmove_emails: Move emails to any folderdownload_attachments: Save an email's attachments to diskcreate_draft: Save a new email as a draft (never sent)create_reply_draft: Save a threaded reply as a draft (never sent)update_draft: Revise a draft, changing only the fields you pass
Drafts, Not Sending: The server never sends email. Drafts appear in Yahoo Mail's Drafts folder for you to review and send, and the MCP client can revise them from your feedback
One Shared IMAP Login: Tool calls reuse one Yahoo login (logged out after 5 minutes idle) instead of logging in every time, which avoids Yahoo's login throttling
Enriched Metadata: All emails include UID, size, flags, hasAttachments, and folder information
Advanced Search: Filter by date range, sender, unread status, and search across any folder
Batch Operations: All management operations support processing multiple emails at once with accurate success/failure tracking
Transport Modes:
stdio: For local desktop MCP clients (Claude Desktop, Cursor, VS Code, ...)http: For remote access. Serves stateless Streamable HTTP at/mcp(current MCP standard) and legacy SSE at/mcp/sse
Cross-Platform: Works on both Windows and Linux development environments
Docker Support: Containerized deployment with Docker and Docker Compose
Cloud Ready: Configured for easy deployment to Render.com with OAuth security
Prerequisites
For Local Development
Node.js: Version 18.0.0 or higher
Yahoo Mail Account: With app-specific password enabled
Git: For version control
For Docker Development/Deployment
Docker: Latest version
Docker Compose: Latest version (included with Docker Desktop on Windows/Mac)
For Render.com Deployment
GitHub Account: To host your repository
Render.com Account: Free tier available at https://render.com
Quick Start
1. Clone and Setup
# Clone the repository
git clone https://github.com/praneethpalla/yahoo-mail-mcp.git
cd yahoo-mail-mcp
# Copy environment template
cp .env.example .env2. Get Yahoo Mail App Password
Click "Generate app password" or "Manage app passwords"
Select "Other App" and enter "MCP Server"
Copy the generated 16-character password
3. Configure Environment
Edit .env file with your credentials:
First store the app password in your password store (see Keep the app password in a password store). On a Mac:
security add-generic-password -a your.email@yahoo.com -s yahoo-mail-mcp -T "" -wThen edit .env (it never contains the password itself):
YAHOO_EMAIL=your.email@yahoo.com
YAHOO_APP_PASSWORD_COMMAND=security find-generic-password -a your.email@yahoo.com -s yahoo-mail-mcp -w
TRANSPORT_MODE=stdio # or 'http' for remote access
PORT=30004. Install Dependencies
Windows (PowerShell):
npm installLinux/macOS (Bash):
npm install5. Run Locally
stdio mode (for Claude Desktop):
npm run start:stdioSSE mode (for testing HTTP endpoint):
npm run start:sseDevelopment mode (with auto-reload):
npm run devDocker Usage
Build and Run with Docker
Windows (PowerShell):
# Build the image
npm run docker:build
# Run the container
npm run docker:run
# Or use Docker Compose (recommended)
npm run docker:compose:up
# View logs
npm run docker:compose:logs
# Stop containers
npm run docker:compose:downLinux/macOS (Bash):
# Build the image
npm run docker:build
# Run the container
npm run docker:run
# Or use Docker Compose (recommended)
npm run docker:compose:up
# View logs
npm run docker:compose:logs
# Stop containers
npm run docker:compose:downManual Docker Commands
Put the app password in a secret file first (never in an environment variable):
mkdir -p secrets && printf '%s' 'your-app-password' > secrets/yahoo-app-password && chmod 600 secrets/yahoo-app-passwordsecrets/ is gitignored and excluded from the image. If the container can't read the file (it runs as a non-root user), run it with your own user ID (--user "$(id -u):$(id -g)"). HTTP mode also needs the OAuth and AUTH_* settings in .env (see Environment Variables Reference).
Windows (PowerShell):
# Build
docker build -t yahoo-mail-mcp .
# Run
docker run -p 3000:3000 --env-file .env `
-v ${PWD}/secrets/yahoo-app-password:/run/secrets/yahoo-app-password:ro `
-e YAHOO_APP_PASSWORD_COMMAND="cat /run/secrets/yahoo-app-password" `
-e TRANSPORT_MODE=http `
yahoo-mail-mcp
# Or with Docker Compose
docker-compose up -dLinux/macOS (Bash):
# Build
docker build -t yahoo-mail-mcp .
# Run
docker run -p 3000:3000 --env-file .env \
-v "$PWD/secrets/yahoo-app-password:/run/secrets/yahoo-app-password:ro" \
-e YAHOO_APP_PASSWORD_COMMAND="cat /run/secrets/yahoo-app-password" \
-e TRANSPORT_MODE=http \
yahoo-mail-mcp
# Or with Docker Compose
docker-compose up -dTesting the Server
Test Health Endpoint
Windows (PowerShell):
# Using npm script
npm run test:health
# Using curl (if installed)
curl http://localhost:3000/health
# Using PowerShell
Invoke-WebRequest -Uri http://localhost:3000/health | Select-Object -Expand ContentLinux/macOS (Bash):
# Using npm script
npm run test:health
# Using curl
curl http://localhost:3000/healthTest the Streamable HTTP Endpoint
With OAuth configured, first get a token, then call /mcp:
TOKEN=$(curl -s -X POST http://localhost:3000/oauth/token \
-H 'Content-Type: application/json' \
-d '{"grant_type":"client_credentials","client_id":"YOUR_ID","client_secret":"YOUR_SECRET"}' | node -pe 'JSON.parse(require("fs").readFileSync(0)).access_token')
curl -s -X POST http://localhost:3000/mcp \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'Run the Offline Test Suite
npm testThe tests use fake IMAP servers and a local HTTP server with dummy credentials, so they never log in to Yahoo.
Test SSE Endpoint (legacy)
Windows (PowerShell):
# Using npm script
npm run test:sse
# Using curl
curl http://localhost:3000/mcp/sse
# Using PowerShell
Invoke-WebRequest -Uri http://localhost:3000/mcp/sseLinux/macOS (Bash):
# Using npm script
npm run test:sse
# Using curl
curl http://localhost:3000/mcp/sseDeploying to Render.com
Experimental: hosted mode passes the offline tests but hasn't been tested end-to-end on Render yet. See Project Status.
Before you host: the host will hold your Yahoo app password in readable form. Read Where Your Credentials Live, and use a separate, revocable app password for the host.
Step 1: Prepare Your Repository
Windows (PowerShell):
# Initialize git (if not already done)
git init
# Add all files
git add .
# Commit
git commit -m "Initial commit: Yahoo Mail MCP Server"
# Create GitHub repository at https://github.com/new
# Then push to GitHub
git remote add origin https://github.com/yourusername/yahoo-mail-mcp-server.git
git branch -M main
git push -u origin mainLinux/macOS (Bash):
# Initialize git (if not already done)
git init
# Add all files
git add .
# Commit
git commit -m "Initial commit: Yahoo Mail MCP Server"
# Create GitHub repository at https://github.com/new
# Then push to GitHub
git remote add origin https://github.com/yourusername/yahoo-mail-mcp-server.git
git branch -M main
git push -u origin mainStep 2: Deploy to Render
Sign up/Login to Render.com
Go to https://render.com
Click "Get Started for Free" or "Login"
Connect GitHub Repository
Click "New +" button in top right
Select "Web Service"
Click "Connect GitHub" and authorize Render
Select your
yahoo-mail-mcp-serverrepository
Configure the Service
Name:
yahoo-mail-mcp-server(or your preferred name)Runtime: Docker
Region: Choose closest to you (Oregon, Frankfurt, Singapore, Ohio)
Branch:
mainPlan: Free (or Starter for production)
Set Environment Variables
In the "Environment" section, click "Add Environment Variable" and add:
Key
Value
How to Generate
NODE_ENVproduction-
TRANSPORT_MODEhttp-
TRUST_PROXY1- (Render runs behind one proxy; needed for per-address sign-in lockouts)
YAHOO_EMAILyour.email@yahoo.comYour Yahoo email address
YAHOO_APP_PASSWORD_COMMANDcat /etc/secrets/yahoo-app-passwordAlready set by
render.yamlOAUTH_CLIENT_ID32-char-hex-stringRun:
openssl rand -hex 16OAUTH_CLIENT_SECRET64-char-hex-stringRun:
openssl rand -hex 32AUTH_USERNAMEyour sign-in name
Run:
npm run setup-login -- --out ~/yahoo-mcp-login.txtAUTH_PASSWORD_HASHscrypt$...From the same file (a hash, never the password itself)
AUTH_TOTP_SECRETbase32 secret
From the same file; also add it to your authenticator app
npm run setup-loginasks for a username and password (hidden while typing), creates an authenticator secret, and writes all threeAUTH_*values to the file you choose (readable only by you). Add the secret to Google Authenticator, 1Password, Authy, or similar via "Enter a setup key", then delete the file once everything is copied.Important:
App password: under Environment > Secret Files, add a file named
yahoo-app-passwordcontaining only the app password. Render mounts it at/etc/secrets/yahoo-app-password; it is never an environment variableMark
YAHOO_EMAIL,OAUTH_CLIENT_ID,OAUTH_CLIENT_SECRET,AUTH_PASSWORD_HASH, andAUTH_TOTP_SECRETas "Secret"The server refuses to start in HTTP mode without the OAuth and
AUTH_USERNAME/AUTH_PASSWORD_HASHsettingsPORTis automatically set by Render, don't add it manuallySave the OAuth credentials - you'll need them to configure Claude Desktop
Deploy
Click "Create Web Service"
Render will automatically build and deploy your Docker container
Wait for deployment to complete (first build takes 5-10 minutes)
Get Your Service URL
Once deployed, you'll get a URL like:
https://yahoo-mail-mcp-server.onrender.comTest it by visiting:
https://yahoo-mail-mcp-server.onrender.com/health
Step 3: Connect to Claude Desktop
Note: Remote MCP servers require a Claude Pro, Max, Team, or Enterprise plan.
Open Claude Desktop
Launch the Claude Desktop app on your computer
Add MCP Connector
Click on your profile icon or menu
Select "Settings"
Navigate to "Connectors" section
Click "Add Custom Connector"
Configure the Connector
Name:
Yahoo MailURL:
https://your-service-name.onrender.com/mcp
Example:
https://yahoo-mail-mcp-server.onrender.com/mcpUse
/mcp(Streamable HTTP) for current clients. Older clients that only support SSE can use/mcp/sse.Configure OAuth Authentication
Click "Advanced Settings" ⚙️
Enter the OAuth credentials from Step 4:
OAuth Client ID: The value from
OAUTH_CLIENT_IDenvironment variableOAuth Client Secret: The value from
OAUTH_CLIENT_SECRETenvironment variable
Save and Test
Click "Add" or "Save", then "Connect"
A sign-in page opens: enter your username, password, and the 6-digit code from your authenticator app
Claude receives a token and refreshes it automatically; you only sign in again if the connection goes unused for 30 days
If successful, you'll see the connector active
You can now use Yahoo Mail tools in your conversations!
Other MCP clients (e.g. ChatGPT): add the same /mcp URL and OAuth credentials in the client's connector settings, and add the client's OAuth redirect host to OAUTH_REDIRECT_HOSTS (e.g. claude.ai,claude.com,chatgpt.com). By default only Claude's redirect hosts and localhost are allowed. This server does not support dynamic client registration, so the client must let you enter a client ID and secret.
Step 4: Using the MCP Server
Once connected, you can use these tools in your conversations:
Can you list my recent emails?
Can you read email number 5?
Can you search for emails from john@example.com?
Download the attachments from the latest email from my bank.
Draft a reply to Alice saying I'll review the budget by Friday. Then make it more formal.Troubleshooting
Common Issues
1. "Authentication failed" error
Solution: Verify your app-specific password
Make sure you're using an app-specific password, not your regular Yahoo password
Generate a new app-specific password at https://login.yahoo.com/account/security
Check for typos in your
.envfile or Render environment variables
2. Docker build fails on Windows
Solution: Check Docker Desktop settings
Ensure Docker Desktop is running
Check that WSL2 is enabled (Settings > General > Use WSL2 based engine)
Verify file sharing is enabled (Settings > Resources > File Sharing)
3. Port 3000 already in use
Solution: Change the port
Windows (PowerShell):
$env:PORT=3001; npm run start:sseLinux/macOS (Bash):
PORT=3001 npm run start:sseOr edit .env:
PORT=30014. Render deployment fails
Solution: Check the logs
Go to your Render dashboard
Click on your service
Click "Logs" tab
Look for error messages
Common issues:
Missing environment variables
Incorrect Dockerfile path
Build timeout (increase build timeout in settings)
5. SSE connection drops
Solution: Render free tier limitations
Free tier services sleep after 15 minutes of inactivity
First request after sleep takes 30-60 seconds to wake up
Upgrade to Starter plan ($7/month) for always-on service
6. IMAP connection timeout
Solution: Check Yahoo Mail IMAP settings
Ensure IMAP is enabled in Yahoo Mail settings
Go to Yahoo Mail > Settings > More Settings > Mailboxes
Verify IMAP access is allowed
Check firewall settings aren't blocking port 993
Windows-Specific Issues
Line Ending Problems
If you see errors about line endings:
PowerShell:
# Configure git to handle line endings correctly
git config --global core.autocrlf input
# Re-clone the repository
git clone <your-repo-url>npm Scripts Not Working
If cross-platform scripts fail:
PowerShell:
# Install cross-env globally
npm install -g cross-env
# Or run scripts directly
node server.jsLinux-Specific Issues
Permission Errors with Docker
Bash:
# Add user to docker group
sudo usermod -aG docker $USER
# Logout and login again, or run:
newgrp docker
# Test
docker psEnvironment Variables Reference
Variable | Required | Default | Description |
| Yes | - | Your Yahoo Mail email address |
| Yes | - | Command that prints the app password, e.g. |
| Not supported | - | Plain-text app passwords are refused: the server won't start if this is set |
| Yes (Remote) | - | OAuth 2.0 client ID for MCP server authentication (generate with |
| Yes (Remote) | - | OAuth 2.0 client secret for MCP server authentication (generate with |
| No |
|
|
| No |
| Port for HTTP mode (auto-set by Render) |
| No |
| Access token lifetime in seconds |
| No |
| Refresh token lifetime in seconds (30 days). Each refresh token can be used once and is replaced |
| Yes (Remote) | - | Username for the sign-in page |
| Yes (Remote) | - | scrypt hash of the sign-in password, from |
| Recommended (Remote) | - | Base32 authenticator secret from |
| No | - | Express "trust proxy" setting. Set to |
| No | - | Set to |
| No | - | Set to |
| No |
| Hostnames allowed as OAuth redirect targets (https only; subdomains allowed; localhost is always allowed). Add other clients, e.g. |
| No | auto-detected | Drafts folder name. Normally detected from the server's |
| No | - |
|
| No | all | Comma-separated list of the only tools to expose, e.g. |
| No |
| Comma-separated folders that draft attachments may come from. Anything else is blocked |
| No | - | Comma-separated file types to allow despite the program/script block, e.g. |
| No |
| Largest attachment |
| No |
|
|
| No | on | Set to |
| No | - | Path to a JavaScript module with your own safety hooks (see Safety hooks) |
| No |
| Maximum characters of each email body returned by |
| No |
| Log out of the shared IMAP connection after this many milliseconds without use |
| No |
| If one tool call holds the connection longer than this, the connection is closed (that call fails) and the next call logs in fresh |
| No |
| Env file to load, relative to |
| No |
| Environment: |
Note: The OAuth and AUTH_* settings are only required for remote deployments (Render.com); HTTP mode refuses to start without them. Local stdio mode doesn't use OAuth or a sign-in.
Available npm Scripts
Script | Description | Cross-Platform |
| Start server (stdio mode) | ✅ |
| Start in stdio mode | ✅ |
| Start in SSE mode | ✅ |
| Development mode with auto-reload | ✅ |
| Build Docker image | ✅ |
| Run Docker container | ✅ |
| Start with Docker Compose | ✅ |
| Stop Docker Compose | ✅ |
| View Docker Compose logs | ✅ |
| Test health endpoint | ✅ |
| Test SSE endpoint | ✅ |
| Run the offline test suite (no Yahoo logins) | ✅ |
| Create sign-in settings (username, password hash, authenticator secret) | ✅ |
Project Structure
yahoo-mail-mcp-server/
├── server.js # Main server code
├── package.json # Node.js dependencies and scripts
├── Dockerfile # Docker build configuration
├── docker-compose.yml # Docker Compose configuration
├── render.yaml # Render.com deployment config
├── .env.example # Environment variable template
├── .env # Your local environment variables (gitignored)
├── .dockerignore # Files to exclude from Docker build
├── .gitignore # Files to exclude from git
├── .gitattributes # Git line ending configuration
├── auth.js # Sign-in helpers: password hashing, TOTP, login page
├── untrusted.js # Prompt-injection defenses: sanitizing, hidden-HTML removal, untrusted-content blocks
├── safety.js # Safety hooks: payment red flags, attachment blocking, draft attachment folders
├── scripts/setup-login.js # Creates the AUTH_* sign-in settings
├── scripts/windows-credential.ps1 # Stores/reads the app password in Windows Credential Manager
├── test/ # Offline tests (node --test), fake IMAP servers
└── README.md # This fileSecurity Best Practices
OAuth 2.0 Protection (Remote Deployments)
Server requires OAuth 2.0 authentication for all MCP requests
Uses authorization code flow with PKCE (Proof Key for Code Exchange, S256)
Connecting an app requires signing in on the server's own page: username, password, and (recommended) a 6-digit authenticator code (TOTP, RFC 6238)
The password is stored only as a scrypt hash; authenticator codes are single-use; 5 failed sign-ins from one address lock it out for 15 minutes
The sign-in page can't be framed (clickjacking), isn't cached, loads nothing external, and its hidden fields are signed so they can't be altered
The
client_credentialsgrant (no sign-in) is off unlessALLOW_CLIENT_CREDENTIALS=trueOnly clients with correct credentials can access your emails
Access tokens are signed (HMAC-SHA256, key derived from
OAUTH_CLIENT_SECRET) and expire after 1 hour; clients renew them with single-use refresh tokens (30 days) without asking the user to log in againTokens need no server-side storage, so they keep working across restarts and Render sleep
Emergency logout: changing
OAUTH_CLIENT_SECRETimmediately invalidates every issued tokenAuthorization codes are random, valid for 60 seconds, single-use, and bound to their
redirect_uriHTTP mode refuses to start without OAuth credentials (override with
ALLOW_UNAUTHENTICATED=truefor local testing only)Generate strong random credentials:
openssl rand -hex 16andopenssl rand -hex 32Store credentials securely in Render dashboard (marked as "Secret")
Never commit credentials
.envfile is gitignoredAlways use
.env.exampleas templateSet sensitive values in Render dashboard
Never share OAuth credentials publicly
Use app-specific passwords
Never use your main Yahoo password
Generate new passwords for each service
Revoke unused passwords regularly
App passwords can be revoked without changing your main password
Email management operations
Modification operations are reversible (soft delete, not permanent), except that
update_draftpermanently removes the previous version of the draft it revisesDeleted emails are moved to Trash folder (recoverable within 7 days for free accounts)
Archive, flag, and read status changes are non-destructive
Move operations preserve email content and metadata
No send operations - server cannot send emails on your behalf
HTTPS in production
Render.com provides free SSL certificates
All traffic is encrypted (TLS/SSL)
IMAP connection uses TLS
OAuth tokens transmitted securely
Development Workflow
Making Changes
Windows (PowerShell):
# 1. Make your changes to server.js
# 2. Test locally
npm run dev
# 3. Test with Docker
npm run docker:compose:up
# 4. Commit and push
git add .
git commit -m "Description of changes"
git push origin main
# 5. Render automatically deploys the changesLinux/macOS (Bash):
# 1. Make your changes to server.js
# 2. Test locally
npm run dev
# 3. Test with Docker
npm run docker:compose:up
# 4. Commit and push
git add .
git commit -m "Description of changes"
git push origin main
# 5. Render automatically deploys the changesViewing Logs
Local Development:
# The server logs to stderr
npm run start:sseDocker:
npm run docker:compose:logsRender.com:
Go to your service dashboard
Click "Logs" tab
Real-time logs appear here
API Endpoints
When running in HTTP mode (TRANSPORT_MODE=http), the server exposes these endpoints:
Endpoint | Method | Description |
| GET | API information and available tools |
| GET | Health check (returns status, version, timestamp) |
| POST | Streamable HTTP endpoint for MCP, stateless with JSON replies (requires OAuth token). GET/DELETE return 405 |
| GET | Legacy Server-Sent Events endpoint for MCP (requires OAuth token) |
| POST | Legacy SSE message endpoint (requires OAuth token) |
| GET | OAuth protected resource metadata for |
| GET | OAuth 2.0 server metadata (RFC 8414) |
| GET | OpenID Connect discovery endpoint |
| GET | OAuth 2.0 authorization endpoint |
| POST | OAuth 2.0 token endpoint |
Example Health Check Response
{
"status": "ok",
"service": "yahoo-mail-mcp",
"version": "3.1.0",
"timestamp": "2026-09-26T12:34:56.789Z"
}Breaking Changes & Migration Guide
⚠️ v3.0.0 Breaking Changes
Version 3.0.0 introduces UID-based operations which fundamentally changes how you interact with emails. This is a breaking change that requires updating your code.
What Changed
1. Parameter Rename: sequenceNumbers → uids
All email management tools now use uids (permanent identifiers) instead of sequenceNumbers (temporary positions):
// ❌ v2.x (OLD - sequence numbers)
read_email({ sequenceNumbers: [1, 2, 3] })
delete_emails({ sequenceNumbers: [5] })
// ✅ v3.0.0 (NEW - UIDs)
read_email({ uids: [510867, 510866, 510862] })
delete_emails({ uids: [510867] })2. Response Format: Plain Text → JSON
All tools now return structured JSON instead of plain text:
// ❌ v2.x response
"Email 1 of 10..."
// ✅ v3.0.0 response
{
"emails": [...],
"totalCount": 10,
"returned": 10
}3. New Required Workflow
You must now get UIDs from list_emails or search_emails before performing operations:
// Step 1: Get UIDs
const result = list_emails({ count: 10 });
// Returns: { emails: [{ uid: 510867, ... }, { uid: 510866, ... }] }
// Step 2: Use UIDs for operations
const uidsToDelete = [510867, 510866];
delete_emails({ uids: uidsToDelete });Why UIDs Are Better
Sequence Numbers (v2.x):
❌ Change when emails are deleted
❌ Position-based (email #1, #2, #3)
❌ Can become invalid between operations
❌ Cause confusion and errors
UIDs (v3.0.0):
✅ Permanent identifiers assigned by IMAP server
✅ Never change, even when other emails are deleted
✅ Always valid until email is permanently deleted
✅ Reliable for batch operations
Migration Checklist
Update all tool calls to use
uidsparameter instead ofsequenceNumbersUpdate code to get UIDs from
list_emailsorsearch_emailsfirstUpdate code to handle JSON responses instead of plain text
Test batch operations to ensure all UIDs are processed (v3.0.0 fixes critical batch bug)
Review new features: pagination, enriched metadata, advanced search, folder support
New Features in v3.0.0
Enriched Metadata: All emails include
uid,size,flags,hasAttachmentsPagination:
list_emailssupportsoffsetandlimitparametersAdvanced Search:
search_emailssupports date ranges, sender filter, unread-onlyFolder Support: All tools support
folderparameter (default: INBOX)list_folders: New tool to discover available IMAP folders
Accurate Batch Operations: Fixed critical bug where only first UID was processed
Enhanced Error Handling: Better timeout and connection error messages
MCP Tools
list_emails
List recent emails with enriched metadata (UID, size, flags, attachments) and pagination support.
Parameters:
count(optional): Number of emails to retrieve (default: 10, max: 50)folder(optional): Folder to list from (default: 'INBOX'). Uselist_foldersto see available foldersoffset(optional): Number of emails to skip for pagination (default: 0)
Response: JSON with emails array containing enriched metadata for each email:
uid: Permanent IMAP UID (use this for all operations)sequenceNumber: Position in folder (for reference only, don't use for operations)from: Sender addresssubject: Email subjectdate: Date in RFC 2822 formatsize: Email size in bytesflags: Array of IMAP flags (e.g.,['\\Seen'],['\\Flagged'])hasAttachments: Boolean indicating if email has attachments
Examples:
// List 20 most recent emails
list_emails({ count: 20 })
// List emails with pagination (skip first 10)
list_emails({ count: 10, offset: 10 })
// List emails from Sent folder
list_emails({ count: 15, folder: "Sent" })read_email
Read the full content of emails using UIDs (supports batch reading).
Parameters:
uids(required): Array of UIDs to read (get UIDs fromlist_emailsorsearch_emails)folder(optional): Folder containing the emails (default: 'INBOX')
Response: Email headers, size, flags, attachment list (name, type, size), and body content, returned in the order the UIDs were requested
Examples:
// Read a single email
read_email({ uids: [510867] })
// Read multiple emails
read_email({ uids: [510867, 510866, 510862] })
// Read email from Sent folder
read_email({ uids: [510867], folder: "Sent" })search_emails
Advanced search with filters for date ranges, sender, and unread status.
Parameters:
query(optional): Search term for subject or sender (can be empty for date-only searches)count(optional): Number of results to return (default: 10, max: 50)dateFrom(optional): Filter emails from this date onwards (ISO 8601 or RFC 2822 format)dateTo(optional): Filter emails up to this date (ISO 8601 or RFC 2822 format)sender(optional): Filter by specific sender email address or nameunreadOnly(optional): Only return unread emails (default: false)folder(optional): Folder to search in (default: 'INBOX')
Response: JSON with emails array, totalMatches, returned, query, filters, and folder
Examples:
// Basic search
search_emails({ query: "invoice", count: 15 })
// Search unread emails only
search_emails({ query: "meeting", unreadOnly: true })
// Search by date range
search_emails({ dateFrom: "2025-01-01", dateTo: "2025-01-31" })
// Search by sender
search_emails({ sender: "boss@company.com" })
// Combined filters
search_emails({
query: "report",
sender: "team@company.com",
dateFrom: "2025-01-15",
unreadOnly: true
})list_folders
Discover all available IMAP folders in your Yahoo Mail account.
Parameters: None
Response: JSON with array of folder objects containing name, path, delimiter, and children
Example:
// List all folders
list_folders()
// Example response:
// {
// "folders": [
// { "name": "INBOX", "path": "INBOX" },
// { "name": "Sent", "path": "Sent" },
// { "name": "Trash", "path": "Trash" },
// { "name": "Archive", "path": "Archive" }
// ]
// }delete_emails
Move emails to Trash folder using UIDs (soft delete - emails can be recovered).
Parameters:
uids(required): Array of UIDs to delete (get UIDs fromlist_emailsorsearch_emails)folder(optional): Source folder (default: 'INBOX')
Response: Success/failure message with accurate count of processed emails
Examples:
// Delete a single email
delete_emails({ uids: [510867] })
// Delete multiple emails
delete_emails({ uids: [510867, 510866, 510862, 510856] })
// Delete from Sent folder
delete_emails({ uids: [510867], folder: "Sent" })archive_emails
Move emails to Archive folder using UIDs for long-term storage.
Parameters:
uids(required): Array of UIDs to archivefolder(optional): Source folder (default: 'INBOX')
Response: Success/failure message with accurate count of processed emails
Examples:
// Archive a single email
archive_emails({ uids: [510867] })
// Archive multiple emails
archive_emails({ uids: [510867, 510866, 510862, 510851] })mark_as_read
Mark emails as read using UIDs by adding the Seen flag.
Parameters:
uids(required): Array of UIDs to mark as readfolder(optional): Folder containing the emails (default: 'INBOX')
Response: Success/failure message with accurate count of processed emails
Examples:
// Mark a single email as read
mark_as_read({ uids: [510867] })
// Mark multiple emails as read
mark_as_read({ uids: [510867, 510866, 510862, 510851, 510865] })mark_as_unread
Mark emails as unread using UIDs by removing the Seen flag.
Parameters:
uids(required): Array of UIDs to mark as unreadfolder(optional): Folder containing the emails (default: 'INBOX')
Response: Success/failure message with accurate count of processed emails
Examples:
// Mark a single email as unread
mark_as_unread({ uids: [510867] })
// Mark multiple emails as unread
mark_as_unread({ uids: [510869, 510867, 510866] })flag_emails
Flag emails as important/starred using UIDs by adding the Flagged flag.
Parameters:
uids(required): Array of UIDs to flagfolder(optional): Folder containing the emails (default: 'INBOX')
Response: Success/failure message with accurate count of processed emails
Examples:
// Flag a single email
flag_emails({ uids: [510867] })
// Flag multiple emails
flag_emails({ uids: [510851, 510865, 510864] })unflag_emails
Remove flag/star from emails using UIDs by removing the Flagged flag.
Parameters:
uids(required): Array of UIDs to unflagfolder(optional): Folder containing the emails (default: 'INBOX')
Response: Success/failure message with accurate count of processed emails
Examples:
// Unflag a single email
unflag_emails({ uids: [510867] })
// Unflag multiple emails
unflag_emails({ uids: [510867, 510866, 510862] })move_emails
Move emails to a specified folder using UIDs.
Parameters:
uids(required): Array of UIDs to movefolderName(required): Name of the destination folder (e.g., "Work", "Personal", "Archive")sourceFolder(optional): Source folder (default: 'INBOX')
Response: Success/failure message with accurate count of processed emails
Examples:
// Move a single email to Work folder
move_emails({ uids: [510867], folderName: "Work" })
// Move multiple emails to Personal folder
move_emails({ uids: [510867, 510866, 510862], folderName: "Personal" })
// Move from Sent to Archive
move_emails({ uids: [510867], folderName: "Archive", sourceFolder: "Sent" })Note: mark_as_read, mark_as_unread, flag_emails, unflag_emails, archive_emails, and move_emails first check which UIDs exist, then process them in one IMAP command (falling back to one at a time if that fails). delete_emails always processes one email at a time.
download_attachments
Save an email's attachments to disk. The email is opened read-only, so it isn't marked as read.
Parameters:
uid(required): UID of the emailfolder(optional): Folder containing the email (default: 'INBOX')filenames(optional): Only download attachments with these filenames (default: all)saveDir(optional): Directory to save to (default:~/Downloads/yahoo-attachments)includeInline(optional): Also save embedded images such as signature logos and social icons (default: false, or theATTACHMENT_INCLUDE_INLINEsetting). Embedded images are skipped unless this is true or they're named infilenames
Response: Paths, types, and sizes of the saved files, and how many embedded images were skipped. Existing files are never overwritten; copies get names like report (1).pdf.
Examples:
download_attachments({ uid: 581266 })
download_attachments({ uid: 581266, filenames: ["invoice.pdf"], saveDir: "~/Documents/invoices" })Note: In remote (HTTP) mode, files are saved on the server's disk, not your computer.
create_draft
Save a new email to the Drafts folder. Nothing is sent; review and send it from Yahoo Mail.
Parameters:
to(required): Array of recipient addresses, e.g.["Jane <jane@example.com>"]cc,bcc(optional): Arrays of addresses (Bcc is kept on the draft)subject(required),body(required): Subject and plain-text bodyhtml(optional): HTML version of the body (default: plain text only)attachments(optional): Paths of local files to attach
Response: The full draft (recipients, subject, body, attachment names) and its UID
Example:
create_draft({ to: ["jane@example.com"], subject: "Lunch Friday?", body: "Hi Jane, are you free for lunch on Friday?" })create_reply_draft
Save a reply to an existing email as a draft. Fills in the recipients (Reply-To if set, otherwise the sender), a Re: subject, and the In-Reply-To/References headers so the reply stays in the same thread. Nothing is sent.
Parameters:
uid(required): UID of the email being replied tobody(required): Reply text (written above the quoted original)folder(optional): Folder containing the original (default: 'INBOX')replyAll(optional): Also reply to the original's To and Cc, excluding your own address (default: false)includeQuote(optional): Quote the original below the reply (default: true)html(optional),attachments(optional): As increate_draft
Example:
create_reply_draft({ uid: 581098, body: "Thanks, I'll review it by Friday.", replyAll: true })update_draft
Revise a draft. Only the fields you pass change; recipients, subject, reply threading, and attachments are otherwise kept. IMAP can't edit a message in place, so the new version is saved and the old one is removed (with UID EXPUNGE, which removes only that draft; servers without UIDPLUS move it to Trash instead).
The draft gets a new UID on every update. Always use the UID from the latest create/update result.
Parameters:
uid(required): UID of the draft to reviseto,cc,bcc(optional): Replace the address lists ([]clears Cc/Bcc)subject,body(optional): Replace the subject or the whole bodyhtml(optional): Replace the HTML body (""removes it)addAttachments(optional): Paths of local files to addremoveAttachments(optional): Filenames of attachments to remove
Example (revising from feedback):
// "Make it more formal and cc my manager"
update_draft({ uid: 395417, body: "Dear Jane, ...", cc: ["manager@example.com"] })
// → returns the full revised draft and its new UID (e.g. 395418)Performance Considerations
Render.com Free Tier
Sleep after inactivity: Services sleep after 15 minutes of no requests
Wake-up time: First request takes 30-60 seconds
Monthly hours: 750 hours/month (enough for moderate use)
Upgrade: $7/month for Starter plan (always-on)
IMAP Performance
Shared connection: Tool calls reuse one IMAP login and take turns on it through a lock, and each call selects its own folder, so calls never see each other's selected mailbox. The connection logs out after
IMAP_IDLE_MS(default 5 minutes) without use and reconnects on the next call. A call that holds it longer thanIMAP_LEASE_TIMEOUT_MShas its connection closed rather than shared, and a released handle refuses further useTimeout: 30 seconds for connection and auth
Rate limiting: Yahoo may throttle excessive requests
Recommendation: Cache results on client side when possible
Cross-Platform Compatibility
This project is designed to work seamlessly on:
Windows 10/11 with PowerShell or Command Prompt
Linux (Ubuntu, Debian, Fedora, etc.)
macOS (Intel and Apple Silicon)
Docker Desktop (Windows, Mac, Linux)
WSL2 (Windows Subsystem for Linux)
Line Endings
.gitattributesensures LF line endings in repositoryWorks correctly on Windows (CRLF) and Linux (LF)
Docker uses LF inside containers
Path Handling
All paths use forward slashes in code
path.join()used for cross-platform compatibilityWorks with Windows backslashes and Unix forward slashes
Contributing
Fork the repository
Create a feature branch:
git checkout -b feature-nameMake your changes
Test on both Windows and Linux (if possible)
Commit:
git commit -am "Add feature"Push:
git push origin feature-nameCreate a Pull Request
License
MIT License. See the LICENSE file. Original work © jtokib; modifications © praneethpalla.
Support
Issues: Report bugs at https://github.com/praneethpalla/yahoo-mail-mcp/issues
Discussions: Ask questions in GitHub Discussions
MCP Docs: https://modelcontextprotocol.io
Changelog
v3.1.0 (2026-09-26) - Drafts, Attachments, Streamable HTTP
New Features:
New tools:
download_attachments,create_draft,create_reply_draft,update_draft(drafts are never sent)read_emaillists attachments (name, type, size) and returns emails in the requested orderStreamable HTTP transport at
POST /mcp(stateless, JSON replies) for current MCP clients; legacy SSE kept at/mcp/sse;TRANSPORT_MODE=httpOne shared IMAP login across tool calls, with idle logout (
IMAP_IDLE_MS)Bulk flag/read/archive/move run as one IMAP command after checking which UIDs exist
ENV_FILE,DRAFTS_FOLDER, andOAUTH_REDIRECT_HOSTSsettings;.envis loaded from next toserver.jsOffline test suite (
npm test)
Bug Fixes:
read_emailreturned empty text for large emails, and returned only the first of several UIDssizewas always 0 in list/search/read resultsInvalid search dates were silently ignored; they now return an error before logging in
Error results now set
isErrorMessage bodies are buffered as bytes, so binary content isn't corrupted
Security:
Sign-in page for OAuth authorization: previously
/oauth/authorizeissued a code to anyone without asking. It now requires a username, password (scrypt hash), and optional authenticator code (TOTP), with single-use codes and a 15-minute lockout after 5 failuresclient_credentialsgrant is off by default (ALLOW_CLIENT_CREDENTIALS=trueto enable)OAuth
redirect_uriis checked by exact hostname over https. The previous substring check accepted URLs likehttps://evil.example/?claude.aiAccess tokens were never expired (despite
expires_in: 3600), were stored only in memory (lost on every restart), and were built fromMath.random()with the client ID in plain base64. They are now signed, expiring tokens with random IDs, plus single-use refresh tokens (refresh_tokengrant)Authorization codes are random (
crypto.randomBytes), expire after 60 seconds, and must match the originalredirect_uri; a missing PKCE verifier returns an error instead of crashingClient secrets are compared in constant time, and secrets containing
:work with Basic authHTTP mode refuses to start without OAuth credentials
v3.0.0 (2025-01-18) - UID Migration
BREAKING CHANGES:
All tools now use
uidsparameter instead ofsequenceNumbersResponse format changed from plain text to structured JSON
UIDs are permanent identifiers that don't change when emails are deleted
New Features:
Enriched metadata: All emails include
uid,size,flags,hasAttachmentsPagination support:
list_emailsacceptsoffsetandlimitparametersAdvanced search filters:
dateFrom,dateTo,sender,unreadOnlyparametersFolder support: All tools accept
folderparameter (default: INBOX)New tool:
list_foldersto discover available IMAP foldersEnhanced error handling: Better timeout and connection error messages with Render spindown detection
Bug Fixes:
CRITICAL: Fixed batch operations bug where only first UID was processed
All batch operations now accurately process every UID in the array
Success/failure messages now report exact counts of processed emails
Migration Guide:
Replace
sequenceNumberswithuidsin all tool callsGet UIDs from
list_emailsorsearch_emailsresponsesUpdate code to handle JSON responses instead of plain text
See "Breaking Changes & Migration Guide" section above for details
v2.0.1 (2025-01-17)
Fixed: Enhanced input validation for all email operations
Added shared validation helper to prevent IMAP errors with invalid sequence numbers
Improved error messages for better debugging
v2.0.0 (2025-01-16)
Breaking Change:
read_emailnow usessequenceNumbers(array) instead ofsequenceNumber(single number)Added full email management with batch operations support
Seven new tools: delete_emails, archive_emails, mark_as_read, mark_as_unread, flag_emails, unflag_emails, move_emails
All modification operations support batch processing
Enhanced security with reversible operations (soft delete, no permanent deletion)
v1.0.0 (2025-01-11)
Initial release
Support for stdio and SSE transports
Docker and Docker Compose support
Render.com deployment configuration
Cross-platform compatibility (Windows/Linux)
Three core tools: list_emails, read_email, search_emails
Acknowledgments
Based on jtokib/yahoo-mail-mcp-server (MIT), extended with draft and attachment tools, Streamable HTTP, connection reuse, and bug fixes (see Changelog v3.1.0)
Built with @modelcontextprotocol/sdk
Uses imap for IMAP access
Uses mailparser for email parsing
Deployed on Render.com
FAQ
Q: Can I use this with Gmail or other email providers?
A: Currently, this server is configured for Yahoo Mail. To support other providers, change the IMAP host settings in openImapConnection() in server.js.
Q: Is this safe to use with my email account?
A: See Where Your Credentials Live for exactly who can see what in local and hosted setups. In short: The server uses app-specific passwords (not your main password) and never sends email on your behalf; it only saves drafts. Delete operations move emails to Trash (recoverable). The one permanent removal is in update_draft, which removes the previous version of the draft being revised (and only that draft).
Q: How much does it cost to run on Render?
A: The free tier provides 750 hours/month, which is enough for moderate use. For always-on service, the Starter plan is $7/month.
Q: Can I run this on other cloud platforms?
A: Yes! The Docker configuration works on any platform that supports Docker containers (AWS ECS, Google Cloud Run, Azure Container Instances, Heroku, Fly.io, etc.).
Q: Do I need to keep my computer running?
A: No! Once deployed to Render.com (or another cloud platform), the server runs independently in the cloud.
Q: How do I update the server after deployment?
A: Simply push your changes to GitHub. Render automatically detects the push and redeploys the service.
Q: Can multiple people use the same deployed server?
A: The server connects to a single Yahoo Mail account (the one configured in environment variables). Each user would need their own deployment for their own email account.
Q: What if I forget my app-specific password?
A: You can generate a new one at https://login.yahoo.com/account/security/app-passwords and update it in your Render environment variables (Settings > Environment).
Next Steps
After successful deployment:
✅ Test the health endpoint
✅ Connect to Claude.ai
✅ Try listing your emails
✅ Read a few emails
✅ Search your inbox
🎉 Enjoy your Yahoo Mail MCP server!
Happy Coding! If you have questions or issues, please open an issue on GitHub.
Available Tools
15 toolsarchive_emailsC
Move emails to Archive folder using UIDs for long-term storage. UIDs are permanent identifiers.
Inputs: uids (required), folder.
create_draftA
Create a new email draft and save it to the Yahoo Mail Drafts folder. The email is NOT sent; the user reviews and sends it from Yahoo Mail. Returns the full draft and its UID. To revise the draft later, call update_draft with that UID.
Inputs: to (required), body (required), subject (required), cc, +3 more.
create_reply_draftA
Create a reply to an existing email (by UID) and save it as a draft. Fills in the recipients, "Re:" subject, and threading headers so the reply stays in the same conversation. The email is NOT sent. Returns the full draft and its UID; use update_draft with that UID to revise it.
Inputs: uid (required), body (required), html, folder, +3 more.
delete_emailsA
Move emails to Trash folder using UIDs (soft delete, recoverable). UIDs are permanent identifiers.
Inputs: uids (required), folder.
download_attachmentsA
Download attachments from an email (by UID) and save them to disk. Returns the saved file paths. Use read_email to see attachment names first.
Inputs: uid (required), folder, saveDir, filenames.
flag_emailsB
Flag emails as important/starred using UIDs. UIDs are permanent identifiers.
Inputs: uids (required), folder.
list_emailsA
List recent emails from a Yahoo Mail folder. Returns UIDs (permanent identifiers) and enriched metadata including size, flags, and attachment status.
Inputs: count, folder, offset.
list_foldersA
List all available IMAP folders/mailboxes in your Yahoo Mail account
Inputs: no named parameters.
mark_as_readB
Mark emails as read using UIDs. UIDs are permanent identifiers.
Inputs: uids (required), folder.
mark_as_unreadC
Mark emails as unread using UIDs. UIDs are permanent identifiers.
Inputs: uids (required), folder.
move_emailsA
Move emails to a specified folder using UIDs. UIDs are permanent identifiers. Use list_folders to see available folders.
Inputs: uids (required), folderName (required), sourceFolder.
read_emailA
Read email content using UIDs (permanent identifiers). UIDs don't change when emails are deleted. Get UIDs from list_emails or search_emails.
Inputs: uids (required), folder.
search_emailsA
Search emails using UIDs with advanced filters. Returns UIDs which are permanent identifiers that don't change when emails are deleted. Get UIDs from results for subsequent operations.
Inputs: count, query, dateTo, folder, +3 more.
unflag_emailsB
Remove flag/star from emails using UIDs. UIDs are permanent identifiers.
Inputs: uids (required), folder.
update_draftA
Revise an existing draft in the Drafts folder. Only the fields you pass are changed; everything else (recipients, subject, reply threading, attachments) is kept. IMPORTANT: the draft gets a NEW UID on every update. Always use the UID returned by the most recent create/update call. The old version is removed. The email is NOT sent.
Inputs: uid (required), cc, to, bcc, +5 more.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 15 tools
Tools target distinct actions on UIDs or drafts; delete/archive/move overlap slightly because delete and archive are specialized moves, but descriptions clarify intent. No two tools are indistinguishable.
Most tools follow verb_noun (list_emails, create_draft), but mark_as_read/mark_as_unread use a different pattern and read_email is singular while list/search use plural. Still readable and predictable overall.
15 tools is well-scoped for an email management server, covering message retrieval, flags, folders, attachments, and drafts without excessive surface.
Core read/manage/draft workflows are covered, but there is no send operation—agents can only create drafts—and no folder creation/deletion. This is a notable gap for an email client, though the draft-only design may be intentional.
Maintenance
Related MCP Connectors
Your IMAP mailbox as an MCP server: read, search and (if you allow it) organize mail. Open source.
Your own IMAP mailbox in any MCP client: search, read, flag, move; send and delete need confirm.
Hosted email MCP for your own Gmail, Outlook.com, Microsoft 365, iCloud or IMAP inbox: read, search, draft, reply in thread, forward and file mail. It moves or flags up to 500 messages in one call, and a send leaves exactly one copy in Sent. A calendar is a separate connection, and connecting one adds diary and scheduling tools.
Read, search, reply to and organize Naver Mail with the same tools as the Gmail MCP (IMAP/SMTP).
Related MCP Servers
- AlicenseAqualityDmaintenanceProvides full email management for Yahoo Mail via IMAP, including listing, reading, searching, deleting, archiving, and flagging emails.1130MIT
- AlicenseNot gradedqualityDmaintenanceEnables reading, searching, composing, and managing Yahoo Mail emails via IMAP with OAuth support for both local and remote MCP clients.31 npmISC
- AlicenseAqualityBmaintenanceAn MCP server that provides full email management for Yahoo Mail via IMAP, including tools to list, read, search, delete, archive, flag, and move emails, with secure OAuth authentication and support for local and remote deployments.111MIT
- AlicenseNot gradedqualityBmaintenanceMCP server for Yahoo Mail over IMAP+SMTP that enables AI assistants to manage emails fully—listing, reading, searching, threading, flagging, moving, archiving, handling attachments, and sending/reply/forward/draft—using an app-specific password.MIT