Install
$ agentstack add mcp-usejunior-email-agent-mcp ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.3 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.3. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Agent Email
[](https://www.npmjs.com/package/email-agent-mcp) [](https://npmjs.org/package/email-agent-mcp) [](https://opensource.org/licenses/Apache-2.0) [](https://github.com/UseJunior/email-agent-mcp/actions/workflows/ci.yml) [](https://app.codecov.io/gh/UseJunior/email-agent-mcp) [](https://github.com/UseJunior/email-agent-mcp/stargazers) [](https://vitest.dev/) [](./scripts/check-spec-coverage.mjs) [](https://socket.dev/npm/package/email-agent-mcp) [](https://www.npmjs.com/package/email-agent-mcp)
[English](./README.md) | [Español](./README.es.md) | [简体中文](./README.zh.md) | [Português (Brasil)](./README.pt-br.md) | [Deutsch](./README.de.md)
email-agent-mcp by UseJunior -- local email connectivity for AI agents.
Agent Email is an open-source TypeScript MCP server that lets Claude Code, Cursor, Gemini CLI, OpenClaw, and other MCP-compatible runtimes read email, search threads, draft replies, label messages, change read state, move messages, and send mail through your own mailbox. Microsoft 365 / Outlook and Gmail are supported today. Security-first defaults mean agents cannot send email until you explicitly configure an allowlist.
Quick Start
npx -y email-agent-mcp
The interactive setup wizard walks you through OAuth configuration and mailbox selection.
What Works Today
- Microsoft 365 / Outlook mailbox access through MCP stdio
list_emails,read_email,search_emails, andget_threadcreate_draft,update_draft,send_draft,send_email, andreply_to_emaillabel_email,mark_read, andmove_to_folder- send allowlists, delete disabled by default, and sanitized errors
The current launch-prep pass was validated against a real Outlook mailbox for read, draft, send, categorize, move, and read-state flows.
Why This Exists
AI agents need to read, reply to, and act on email, but email APIs are complex. OAuth flows, Graph delta queries, Gmail push subscriptions, HTML-to-markdown conversion, threading semantics -- each provider has its own quirks.
Agent Email wraps this complexity into deterministic MCP tools with security guardrails:
- send and receive allowlists that control who agents can contact
- delete disabled by default (requires explicit opt-in)
- error sanitization that strips API keys, file paths, and stack traces
- body file sandboxing with path traversal protection
Use with Claude Code
Add to ~/.claude/settings.json or your project .claude/settings.json:
{
"mcpServers": {
"email-agent-mcp": {
"type": "stdio",
"command": "npx",
"args": ["-y", "email-agent-mcp"]
}
}
}
Use with Cursor
// .cursor/mcp.json
{
"mcpServers": {
"email-agent-mcp": {
"command": "npx",
"args": ["-y", "email-agent-mcp"]
}
}
}
Use with Gemini CLI
gemini extensions install https://github.com/UseJunior/email-agent-mcp
Use with OpenClaw
Add an mcp block to ~/.openclaw/openclaw.json:
{
// ... existing config ...
mcp: {
servers: {
email: {
command: "npx",
args: ["tsx", "/path/to/email-agent-mcp/packages/email-mcp/src/serve-entry.ts"],
transport: "stdio"
}
}
}
}
> Version note: The mcp config key requires OpenClaw app >= 2026.3.24. If the CLI is older than the app, it may reject this key during validation even though the gateway accepts it. Update the CLI with npm install openclaw@latest in your NemoClaw directory, or restart the gateway directly with launchctl kickstart -k gui/501/ai.openclaw.gateway.
Email watcher
The watcher polls your mailbox and sends wake signals to OpenClaw when new email arrives:
# Set the hooks token (must match hooks.token in openclaw.json)
export OPENCLAW_HOOKS_TOKEN="your-hooks-token"
# Start the watcher (defaults to http://localhost:18789/hooks/wake)
npm run dev:watch
The watcher requires at least one configured mailbox. Run npx email-agent-mcp or npm run dev:configure first to complete the OAuth flow.
Launch Prep Smoke Test
Before recording a demo, run the live smoke script against a real mailbox and a safe send allowlist. The script exercises:
get_mailbox_statuslist_emails+read_emailmark_readunread -> read -> unreadlabel_emailon a safe inbox candidatecreate_draft- draft-only
reply_to_email - optional
send_email
Example:
EMAIL_AGENT_MCP_HOME=/tmp/email-agent-mcp-live \
AGENT_EMAIL_SEND_ALLOWLIST=/tmp/email-agent-mcp-live/send-allowlist.json \
npm run launch:prep:smoke -- --live-write --send-to beta@usejunior.com
Default safe-candidate selection looks for notifications@github.com in the inbox so you can rehearse the recording flow on a public-safe message instead of customer mail. If your mailbox status name is not an email address, pass --reply-sender or set EMAIL_AGENT_MCP_REPLY_SENDER so the script can find a self-sent message for the draft-reply check.
Tool Reference
Scope profiles
Set EMAIL_AGENT_MCP_SCOPE_PROFILE before configuring and starting the server to choose the permissions exposed to an agent. The default is full for backward compatibility.
| Profile | Microsoft delegated scopes | Exposed tools | |---------|-----------------------------|---------------| | observe | Mail.Read, User.Read, offline_access | Read-only email tools, minus list_inbox_rules (see below) | | full (default) | Mail.Read, Mail.ReadWrite, Mail.Send, MailboxSettings.ReadWrite, User.Read, offline_access | All tools |
For an observation-only deployment, set the profile for both configuration and runtime so the OAuth consent and MCP tool list agree:
export EMAIL_AGENT_MCP_SCOPE_PROFILE=observe
npx email-agent-mcp configure
npx email-agent-mcp serve
observe's scopes are a deliberate strict subset of full's. That is what makes the profile adoptable: a tenant that has already consented to the full set grants observe silently, so switching full → observe needs no new consent. Switching observe → full does require a new interactive consent, because it asks for scopes that were never granted.
For the same reason observe does not expose list_inbox_rules. Graph gates /mailFolders/inbox/messageRules behind MailboxSettings, and Entra treats MailboxSettings.Read as a distinct scope from full's MailboxSettings.ReadWrite rather than implied by it. Requesting it would break the subset property and force a fresh consent on every observe deployment — an admin-approval request in tenants that restrict user consent. Use full if you need inbox-rule visibility.
An invalid profile value stops startup instead of silently granting broader access. If cached credentials do not cover the profile's scopes, the server fails fast with an actionable error rather than blocking on an interactive sign-in.
What observe does and does not guarantee
observe always removes the write tools from the MCP tool surface, so an agent cannot invoke them. That part holds everywhere.
It only narrows the OAuth token on a mailbox that has not already consented to the write scopes. Entra issues an access token carrying every scope the user or tenant has already consented to for that application — not just the subset requested at token-acquisition time. So if you point observe at a mailbox previously configured as full, the underlying token still carries Mail.ReadWrite and Mail.Send; only the tool surface is reduced.
For a genuine least-privilege token, consent to observe from a mailbox that has never been granted the write scopes — a fresh configure against an app registration whose delegated permissions stop at Mail.Read/User.Read. Treat the tool-surface reduction as defense in depth, not as an OAuth boundary, unless you control the grant.
Tools
The full profile exposes 26 MCP tools; observe omits every tool whose action is marked as mailbox-mutating:
| Tool | Description | Type | |------|-------------|------| | list_emails | List recent emails with filtering | read | | read_email | Read full email content as markdown, or raw HTML with format: "html" | read | | search_emails | Full-text search across mailboxes | read | | list_mailboxes | Enumerate configured mailboxes, their status, and the default | read | | get_mailbox_status | Connection status and warnings | read | | get_thread | Full conversation context | read | | list_attachments | List attachment metadata for an email | read | | download_attachment | Download a file attachment as base64 | read | | send_email | Send new email (allowlist-gated) | write | | reply_to_email | Reply within thread (allowlist-gated on send) | write | | create_draft | Create email draft | write | | update_draft | Update draft content | write | | send_draft | Send a saved draft | write | | list_scheduled_sends | List pending provider-held scheduled sends (Microsoft 365) | read | | cancel_scheduled_send | Cancel a pending scheduled send (Microsoft 365) | destructive | | label_email | Apply labels/categories | write | | flag_email | Flag/unflag emails | write | | mark_read | Mark as read/unread | write | | move_to_folder | Move between folders | write | | delete_email | Delete (requires operator env + caller flag) | destructive | | list_folders | Recursively list folders and computed paths (Microsoft 365) | read | | create_folder | Create a custom child folder (Microsoft 365) | write | | delete_folder | Delete a custom folder (requires operator env + caller flag); system folders are protected (Microsoft 365) | destructive | | list_inbox_rules | List server-side inbox rules (Microsoft 365) | read | | create_inbox_rule | Create a persistent safe inbox rule; forwarding, redirection, deletion, and discarding to Deleted Items are blocked (Microsoft 365) | write | | delete_inbox_rule | Delete a server-side inbox rule (requires operator env + caller flag) (Microsoft 365) | destructive |
Every row returned by list_emails, search_emails, and get_thread, and the read_email response, carries an always-present isDraft boolean. isDraft: true means the message is an unsent draft: it has not been sent, and its receivedAt is provider-supplied metadata rather than evidence of delivery. The field is never omitted, so a consuming agent can distinguish "not a draft" from "draft status not reported" — but note false asserts only that the provider did not mark the message as an unsent draft, not that the mailbox owner sent it (received mail is also false). Drafts otherwise appear in listings and search results as before.
Folder and inbox-rule management requires Microsoft Graph MailboxSettings.ReadWrite consent. Existing Microsoft mailbox connections must re-consent after upgrading. Gmail uses labels rather than hierarchical folders/server-side Exchange rules, so these six tools return NOT_SUPPORTED for Gmail mailboxes.
Body formats
Bodies cross the wire as markdown by default in both directions. That default is deliberate — markdown is token-efficient, and a routine read should stay cheap. Both directions can opt out of it.
Writing — send_email, reply_to_email, create_draft, and update_draft accept an optional format:
| format | Behavior | |----------|----------| | markdown (default) | Rendered to HTML via marked (GFM, breaks: true). Raw HTML embedded in the markdown is preserved. | | html | Passthrough — your HTML is sent as-is. | | text | No rendering; sent as plain text. |
html is unsanitised passthrough: no sanitiser, no allowlist, no rewriting of your markup. Inline CSS and arbitrary tags survive to the wire — that is what makes styled mail possible, and it means you own whatever you send. The one default modification is the force-black wrapper below; with force_black: false the body passes through byte-for-byte.
For markdown and html the rendered HTML is wrapped in a `` so Outlook's dark mode does not turn the text white-on-white.
Body files — send_email, create_draft, and update_draft also accept body_file: a path to a .md, .html, or .txt file (read relative to EMAIL_MCP_SAFE_DIR, default the process working directory, plus any AGENT_EMAIL_ALLOWED_DIRS roots) used as the body instead of body. A .md body file may open with a YAML frontmatter block:
For update_draft, body and body_file are permitted only on a non-reply draft and require replace_body: true; the stored body is then replaced wholesale. Reply draft bodies cannot be edited safely because they include provider-assembled quoted history, so create a new draft instead. Subject, recipient, and attachment updates remain available on reply drafts.
---
to: alex@example.com
subject: Quarterly summary
format: html
force_black: false
---
The body starts after the closing delimiter.
Recognized keys are to, cc, subject, reply_to, draft, format, and force_black (draft: true turns a send_email call into a draft save). A frontmatter value overrides the same-named tool parameter. Frontmatter is parsed only from .md files, and the file extension never selects the format — without an explicit format tool parameter, even a .html body file gets the default markdown rendering; pass format: "html" for passthrough.
Reading — read_email accepts an optional format of markdown (default) or html:
{ "id": "AAMkAD...", "format": "html" }
format: "html" returns the message's raw body HTML verbatim. Reach for it when you need styling that markdown cannot carry — color, background-color, text-decoration, `` — for example to change one sentence of a formatted body and leave the rest alone. Through the markdown path that styling is silently destroyed on the way out.
Two fields come back with it:
bodyFormat— always present:markdown,html, ortext. You gettext
when you asked for html but the message has no HTML part, in which case the plain-text body is returned. Check this before writing a body back.
bodyTruncated— present andtrueonly if the body exceeded the 256 KB
response budget for format: "html" (the markdown path is unbounded, as before). Do not write a truncated body back to a draft.
Raw HTML costs far more tokens than its markdown reduction, so leave the default alone unless you need the styling. strip_quoted_history and strip_signatures are markdown-shaped text transforms and are not applied when format is html — the raw HTML is returned untouched.
Writing it back. Pass format: "html" and force_black: false:
{ "draft_id": "AAMkAD...", "body": "", "format": "html", "force_black": false }
force_black defaults to true, which wraps whatever HTML you send in a `. That is right for HTML you authored yourself, but on a body you just read back it nests one more wrapper on every cycle — after fifteen revisions you have fifteen nested divs. With force_black: false` the bytes survive the round trip untouched.
Scheduled send
send_email and send_draft accept scheduled_send_at, an ISO 8601 future timestamp with an explicit timezone. Microsoft 365 holds the message server-side, so delivery survives this process exiting. Use the returned messageId with cancel_scheduled_send while it is pending, or inspect pending items with list_scheduled_sends.
Microsoft Graph changes the message ID when the held draft moves to Sent Items, so the returned ID is a pre-delivery management handle, not a permanent sent message ID. Gmail's public API does not expose scheduled send; all scheduled send surfaces return NOT_SUPPORTED for Gmail while immediate sends remain unchanged.
Outbound attachments
send_email, reply_to_email, create_draft, an
…
Source & license
This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: UseJunior
- Source: UseJunior/email-agent-mcp
- License: Apache-2.0
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.3 Imported from the upstream source.