emperor-mail
Provides Gmail mailbox integration over IMAP/SMTP using app passwords or OAuth 2.0 XOAUTH2, enabling agents to search and read messages, extract attachments, draft replies, and send email only when explicitly allowed.
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., "@emperor-mailsearch my inbox for the invoice from last week and draft a reply"
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.
emperor-mail
Your mailbox as six MCP tools: search, read, draft, and send only when you allow it.
emperor-mail is an MCP server that reads and writes email over IMAP and SMTP, so an agent can find a message, read it and its attachments (PDF, Word, Excel and OpenDocument spreadsheets included), and draft a reply for you to send.
Built on emperor-mcp: stateless Streamable HTTP, framed tool output, forwarded credentials (Emperor Profile P1).
Two ways to run: one mailbox (credentials in the environment, stdio or HTTP) or a shared server (each request brings its own credentials in headers; nothing is stored).
Draft by default.
sendis off unless you start the server with--allow-send, and even then only to the recipients you list. Sent mail is copied to your Sent folder.Markdown bodies go out as
text/plainplus renderedtext/html.Password, app password or OAuth 2.0 (XOAUTH2, for Gmail and Microsoft 365).
Single binary, pure Rust: IMAP via async-imap, parsing via mail-parser, SMTP via lettre, PDF text via lopdf, spreadsheets via calamine, Markdown via pulldown-cmark, TLS via rustls and the operating system's trust store.
Install
Prebuilt binaries (macOS arm64/x86_64, Linux x86_64, Windows x86_64) are attached to GitHub releases. From source:
cargo install emperor-mailOr as a container (stdio, one mailbox from the environment):
docker build -t emperor-mail https://github.com/yarenty/emperor-mail.git
docker run -i --rm -e MAIL_USER -e MAIL_PASSWORD -e IMAP_HOST -e SMTP_HOST emperor-mailThe image runs emperor-mail serve --stdio, so sending is switched with variables rather than
flags: add -e MAIL_ALLOW_SEND=true -e MAIL_SEND_ALLOW=@example.org to let send write to that
domain (see Flags).
Related MCP server: mcpmail
Run for one mailbox
Credentials come from the environment. Use an app password, never your account password (see Provider notes).
export MAIL_USER="you@example.com"
export MAIL_PASSWORD="<app password>"
export IMAP_HOST="imap.example.com" # port 993, implicit TLS by default
export SMTP_HOST="smtp.example.com" # only needed for send; port 465 by default
emperor-mail doctor # check everything before you connect a client
emperor-mail serve --stdio # for desktop clients that spawn the server
emperor-mail serve # or Streamable HTTP on 127.0.0.1:8033/mcpVariable | Meaning | Default |
| Login name | — |
| Password or app password (never printed, never logged) | — |
| OAuth 2.0 access token instead of a password (see OAuth) | — |
|
| the login name |
| IMAP server | port 993 (143 with |
|
|
|
| SMTP server | port 465 (587 with |
|
|
|
| Same as | off (unset, empty, |
| Same as | none |
| Same as | any |
plain (no encryption) is accepted only from the environment and only for a loopback host,
such as a local mail bridge. Certificates are always verified against the operating system's
trust store.
Flags
serve and doctor take the same flags.
Flag | Meaning |
| Speak MCP over stdin/stdout (one mailbox from the environment) |
| HTTP listen address (default |
| Turn |
| Recipients |
| From addresses |
| Do not copy sent mail to the Sent folder (for providers that file it themselves) |
| Always use the environment's IMAP/SMTP servers; ignore server headers |
The three send switches can also come from the environment, for containers and catalogs that
cannot pass flags: MAIL_ALLOW_SEND for --allow-send (unset, empty, false, 0 or no mean
off), MAIL_SEND_ALLOW for --send-allow and MAIL_FROM_ALLOW for --from-allow
(comma-separated; empty means no pattern). A flag on the command line wins over its variable.
The safety rule is the same either way: with sending on and no recipient pattern, every
recipient is refused, and patterns without sending on are a start-up error.
Run as a shared server
On a shared server every request carries the caller's own credentials in headers. They are used for that one tool call and dropped with the request: never stored, never logged.
emperor-mail serve --bind 127.0.0.1:8033Header | Same as | Notes |
|
| Login name |
|
| Secret; with |
|
| Secret; instead of the password (XOAUTH2) |
|
| Checked against |
|
|
|
|
|
|
These ten names are the only headers the server captures.
Rules:
The login is one source. If a request carries any of
X-Mail-User,X-Mail-PasswordorX-Mail-Oauth-Token, the whole login comes from headers: the user plus exactly one of password and token. A header user is never combined with a secret from the environment.Servers follow the login. A caller with its own login may name its own servers in
X-Mail-Imap-*/X-Mail-Smtp-*; what it leaves out falls back to the environment (IMAP_HOST,SMTP_HOST, ...). With a login from the environment, server headers are ignored, so the operator's password never goes to a host a caller picked.No plaintext from headers. A server named in a header is reached over TLS or STARTTLS only;
plainis refused.--pin-serversmakes the environment's servers win for every caller (a company server that should only ever reachimap.corp.example).When the mail server rejects the credentials, the HTTP response is
401with a JSON-RPC error (code-32040), so a client can ask the user to sign in again.Put the server behind an ingress that terminates TLS and authenticates callers; the headers carry passwords. Without one, keep it on
127.0.0.1.
Provider notes
Gmail: turn on 2-Step Verification, then create an app password at https://myaccount.google.com/apppasswords.
IMAP_HOST=imap.gmail.com,SMTP_HOST=smtp.gmail.com. IMAP must be enabled in Gmail settings for some accounts. Gmail files sent mail in "[Gmail]/Sent Mail" by itself: run with--no-sent-copyto avoid a second copy.Fastmail: create an app password under Settings → Privacy & Security → Integrations → App passwords, with IMAP and SMTP access.
IMAP_HOST=imap.fastmail.com,SMTP_HOST=smtp.fastmail.com.Outlook / Microsoft 365 accounts require OAuth: see below. Exchange Online files sent mail by itself; use
--no-sent-copy.
OAuth (Gmail, Microsoft 365)
emperor-mail speaks SASL XOAUTH2 on IMAP and SMTP. It does not run an OAuth flow and holds
no client secret: the client obtains and refreshes the access token and passes it per
request in X-Mail-Oauth-Token (or MAIL_OAUTH_TOKEN for stdio), with the mailbox address in
X-Mail-User / MAIL_USER. The token is forwarded for that one call and never stored or
logged. An expired token gives a 401, the cue for the client to refresh it.
Gmail: scope
https://mail.google.com/;IMAP_HOST=imap.gmail.com,SMTP_HOST=smtp.gmail.com.Microsoft 365: scopes
https://outlook.office.com/IMAP.AccessAsUser.Allandhttps://outlook.office.com/SMTP.Send(plusoffline_accessfor refresh);IMAP_HOST=outlook.office365.com,SMTP_HOST=smtp.office365.com,SMTP_PORT=587. SMTP AUTH must be enabled for the mailbox.
Other servers: if yours offers only STARTTLS, set IMAP_PORT=143 / SMTP_PORT=587 (the
security mode follows the port) or set *_SECURITY=starttls explicitly.
Connect a client
Claude Desktop (claude_desktop_config.json), one mailbox over stdio:
{
"mcpServers": {
"mail": {
"command": "emperor-mail",
"args": ["serve", "--stdio"],
"env": {
"MAIL_USER": "you@example.com",
"MAIL_PASSWORD": "<app password>",
"IMAP_HOST": "imap.example.com",
"SMTP_HOST": "smtp.example.com"
}
}
}
}Claude Code, stdio or HTTP:
claude mcp add mail -e MAIL_USER=you@example.com -e MAIL_PASSWORD='<app password>' \
-e IMAP_HOST=imap.example.com -- emperor-mail serve --stdio
# or against a running HTTP server, credentials in headers
claude mcp add --transport http mail http://127.0.0.1:8033/mcp \
--header "X-Mail-User: you@example.com" --header "X-Mail-Password: <app password>" \
--header "X-Mail-Imap-Host: imap.example.com"kowalski (config.toml):
[[mcp.servers]]
name = "mail"
transport = "http"
url = "http://127.0.0.1:8033/mcp"
headers = { "X-Mail-User" = "you@example.com", "X-Mail-Password" = "<app password>", "X-Mail-Imap-Host" = "imap.example.com" }
# or one mailbox over stdio: MAIL_USER, MAIL_PASSWORD, IMAP_HOST and the other
# variables are read from kowalski's environment
[[mcp.servers]]
name = "mail"
transport = "stdio"
command = ["emperor-mail", "serve", "--stdio"]Any other MCP client: POST JSON-RPC to /mcp with Accept: application/json, text/event-stream.
GET /health answers OK without credentials.
Tools
Tool | Annotations | What it does |
| read-only, open world | Folders with special use (drafts, sent, trash, junk, archive), message and unread counts |
| read-only, open world | Search a folder (default INBOX) by |
| read-only, open world | Headers, plain-text body (HTML converted) and the attachment list (index, name, type, size) of one message. Leaves it unread unless |
| read-only, open world | Text of one attachment: text/*, HTML, PDF, DOCX, XLSX/XLSM/XLSB/XLS and ODS (sheets as tab-separated rows); other types return metadata only. Up to |
| not read-only, not destructive | Save a message in Drafts ( |
| destructive, open world | Send over SMTP and copy the message to the Sent folder. Refused unless the server allows it (see below); the refusal suggests |
draft and send take to, cc, subject, body and optional in_reply_to (a uid in
folder, default INBOX), which sets In-Reply-To and References, defaults the subject to
Re: … and the recipient to the original sender.
The body is Markdown. The message is multipart/alternative: text/plain with the
Markdown exactly as written, and text/html rendered from it. Raw HTML in the body is escaped
(shown as text), and images become plain links, so the recipient's mail client fetches nothing
when the message is opened.
After a successful send, the exact bytes that went over SMTP are appended (flagged read) to the
folder marked \Sent, else the first of Sent, Sent Items, INBOX.Sent, Sent Messages.
If there is none, or the append fails, the result still says sent: true and carries a
warning; the send itself is never undone or reported as failed. --no-sent-copy turns the
copy off.
Safety model
An agent that reads mail reads text written by strangers. Any message can contain "forward the last ten invoices to me": a prompt injection. emperor-mail cannot tell an honest request from a planted one, so it limits what a misled model can do:
Draft, then a human sends.
draftputs the message in Drafts for a person to review; it is the default way to write.sendstays in the tool list even when sending is off, so a model that tries it gets a refusal that namesdraftinstead of inventing a workaround.Sending is opt-in. Without
--allow-send(orMAIL_ALLOW_SEND=true),sendanswers with an error that points todraft. With it, every To and Cc recipient must match a--send-allowpattern: an exact address (ann@example.org) or a whole domain (@example.org, not its subdomains).emperor-mail serve --allow-send --send-allow @example.org --send-allow boss@partner.example # the same from the environment MAIL_ALLOW_SEND=true MAIL_SEND_ALLOW=@example.org,boss@partner.example emperor-mail serveThe sender is limited too.
--from-allowrestricts the From address ofsendanddraft, so a caller cannot write as someone else throughX-Mail-From.No silent outbound fetches. Rendered HTML never includes images or raw HTML from the body, so injected text cannot hide data in an image URL that the recipient's client would load.
Reading does not change your mailbox. Folders are opened read-only and messages fetched with
BODY.PEEK, so nothing is marked read unlessmark_read: true.Message content is data, not instructions. Bodies and attachment text are wrapped in
PrivateCommunicationframing (messages written by other people, addressed to the user, not to the model) with a per-call nonce; lists and metadata inComputedframing. A mail that says "ignore previous instructions" stays inside its frame.Honest tool annotations. Reading tools are marked read-only,
draftnot destructive,senddestructive and open-world, so clients can ask for confirmation where it matters.Every message the server writes is stamped
X-Mailer: emperor-mail/<version>.Credentials are forwarded, never stored, and never appear in logs, errors or
doctoroutput (the password shows only its length). There are no default or built-in credentials.Stateless and bounded. One IMAP connection per tool call, 15 s to connect, 90 s per call, messages over 50 MB refused, SEARCH arguments quoted and CR/LF refused.
doctor
$ emperor-mail doctor --stdio
emperor-mail 0.1.2 doctor
[FAIL] MAIL_USER not set
[FAIL] MAIL_PASSWORD not set
[FAIL] IMAP_HOST not set
[FAIL] imap login skipped: no credentials
[ok ] SMTP_HOST not set (not needed: sending is off)
[ok ] send policy send off: draft only
[ok ] from policy any From address
[ok ] sent copy sent messages are copied to the sent folder (--no-sent-copy to turn off)
[ok ] tool budget 6 of at most 8 tools
4 check(s) failed, 0 warning(s)doctor takes the same flags as serve. It checks the environment (the password or token
only by length), that the IMAP server is reachable and the login works with INBOX selectable, that the
SMTP server is reachable and (with --allow-send) accepts EHLO and AUTH without sending
anything, summarises the send and From policies (whether send is on and how many recipient
patterns there are), the sent copy and whether servers are
pinned, checks the listen address is free (not with --stdio)
and the tool budget. On a shared HTTP server with no credentials in the environment the login
checks are warnings: credentials come per request. Exit code 1 when a check fails.
Development
cargo fmt --all --check
cargo clippy --all-targets -- -D warnings
cargo test # unit, fixture parsing, MIME building, HTTP end to end
cargo deny check
cargo test --test greenmail -- --ignored # live IMAP/SMTP against GreenMail in DockerSee AGENTS.md for the layout and conventions and CONTRIBUTING.md before opening a pull request.
Links
Official MCP registry: mcp-name: io.github.yarenty/emperor-mail
How it compares with other mail MCP servers: docs/COMPARISON.md
License
Dual licensed under MIT OR Apache-2.0, at your option.
Available Tools
6 toolsdraftA
Save a message in the Drafts folder for a person to review and send. Always allowed. With in_reply_to (a uid) it threads as a reply and defaults to the original sender.
| Name | Required | Description | Default |
|---|---|---|---|
| cc | No | Cc recipients. | |
| to | No | Recipients, `ann@example.org` or `Ann <ann@example.org>`. May be empty for a reply (goes to the original sender). | |
| body | Yes | Message body in Markdown (plain text is fine). Sent as text/plain (as written) plus text/html rendered from it; raw HTML is shown as text, images become links. | |
| folder | No | Folder of the in_reply_to message (default INBOX). | |
| subject | No | Subject (a reply defaults to "Re: " plus the original subject). | |
| in_reply_to | No | UID of the message this replies to; sets In-Reply-To and References. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
It adds useful policy information ('Always allowed') and clarifies a key behavioral quirk: how the `in_reply_to` parameter handles threading and defaults to the original sender, which is behavioral context beyond the schema.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is highly concise, utilizing only two sentences to capture the core functionality, the policy, and the complexity of reply threading. Every word contributes to clarity.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the rich schema and annotations, the description completes the picture by explaining the operational context of drafting, especially regarding how reply threads are established and the default behavior of the tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Although schema coverage is 100%, the description adds semantic value by explaining formatting (Markdown vs HTML rendering) and crucial usage details for parameters like `to` (recipient format and empty list default) and `subject` (automatic prefix when replying).
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the primary function as saving a message to the Drafts folder. It differentiates this action (draft creation) from immediate actions like sending, implicitly setting it apart from the send sibling.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It defines the use case (saving for later review) which is the primary differentiator from sending, but it does not explicitly state when *not* to use it or compare its usage requirements against alternatives like `send`.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_foldersARead-only
List mail folders with their special use (drafts, sent, trash, ...) and message and unread counts.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=true, so the safety profile is covered. The description adds useful returned-data context (special-use labels, message/unread counts), but it doesn't say whether folders are returned flat or nested, or whether system folders are included by default.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single front-loaded sentence with no filler; the resource comes first and the return content follows. Every clause carries information an agent cannot get from the schema or annotations.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a zero-parameter, read-only enumeration with no output schema, the description covers what the tool returns well enough to invoke confidently. The main remaining gap is folder hierarchy and whether ordering or nesting is guaranteed.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, which is the baseline-4 case; there is no parameter semantics to explain. The description correctly signals that the call is unconditional and returns the entire folder set rather than a filtered subset.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description gives a specific verb (List) and resource (mail folders) and even previews the returned fields: special-use flags plus message and unread counts. Nothing among the siblings (draft, read_attachment, read_message, search, send) overlaps with folder enumeration, so an agent can distinguish it, though the description never states that differentiation explicitly.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no explicit statement of when to call this versus alternatives, but usage is strongly implied: an agent that needs folder identifiers before searching or reading messages would call this first. No exclusions or prerequisites are given.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
read_attachmentARead-only
Read the text of one attachment of a message (text, HTML, PDF, DOCX, XLSX/XLS/ODS; other types return metadata only). Index comes from read_message.
| Name | Required | Description | Default |
|---|---|---|---|
| uid | Yes | UID from search. | |
| index | Yes | Attachment index from read_message (starts at 0). | |
| folder | No | Folder holding the message (default INBOX). | |
| max_chars | No | Most characters to return (default 20000, at most 200000). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint and openWorldHint, so the lower bar applies; the description still adds real value by disclosing that only certain MIME types yield text and that others return metadata only — a behavioral outcome the agent cannot infer from annotations. It omits that output is truncated at max_chars (default 20k), though the schema carries that.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, zero waste, with the core action and the format caveat front-loaded before the short provenance note. The parenthetical format list earns its space by defining the tool's actual capability boundary.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema exists, and the description does cover the key return-shape distinction (text vs. metadata-only), which is the main thing an agent needs to interpret results. It could have mentioned that long extractions are truncated per max_chars, but overall it is sufficient for correctly invoking and reading this tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3. The description's note that the index originates from read_message duplicates the schema's own wording, and it says nothing about uid, folder, or max_chars, so it adds no meaning beyond the structured field definitions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ("Read the text of one attachment of a message") and immediately bounds the scope with the supported format list plus the fallback behavior for other types. An agent can distinguish this from read_message (message body) and from list_folders/search without opening a schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
"Index comes from read_message" supplies the prerequisite workflow, making clear this is a follow-up call to a message read rather than an entry point. It stops short of naming exclusions (e.g., 'do not use for message bodies, use read_message'), so it is clear context but not a full when/when-not routing statement.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
read_messageCRead-only
Read one message: headers, plain-text body (HTML converted) and the attachment list (index, name, type, size). Leaves the message unread unless mark_read is true.
| Name | Required | Description | Default |
|---|---|---|---|
| uid | Yes | UID from search. | |
| folder | No | Folder holding the message (default INBOX). | |
| mark_read | No | Set the message's read flag (default false: reading leaves it unread). | |
| max_chars | No | Most body characters to return (default 20000, at most 200000). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The annotations declare readOnlyHint=true, but the description and schema state that mark_read=true sets the message's read flag, which is a state mutation. This directly contradicts the read-only annotation, even though the default behavior leaves the message unread.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single well-structured sentence with the core action and returned data front-loaded. Every clause adds useful information without excess.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a low-complexity read tool, the description adequately explains what is returned and the default mark_read behavior. It omits some edge details such as truncation and error cases, but those are partly covered by the schema.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so parameter meanings are already documented. The description adds some context about mark_read's default effect and the returned body/attachment structure, but it does not add parameter-level detail beyond what the schema provides.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: read one message, and it enumerates the returned content: headers, converted body, and attachment list. It is clear but does not explicitly distinguish itself from sibling tools such as read_attachment or search.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description does not say when to use this tool versus search, read_attachment, or other siblings. The schema mentions that uid comes from search, but the description itself provides no explicit usage context or exclusions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
searchARead-only
Search a folder (default INBOX) by from, to, subject, text, date range (since/before, YYYY-MM-DD) and unread state. Returns uid, date, from, subject, size and has_attachments, newest first.
| Name | Required | Description | Default |
|---|---|---|---|
| to | No | Substring of the To header. | |
| from | No | Substring of the From header. | |
| text | No | Substring anywhere in the headers or the body. | |
| limit | No | Most messages to return, newest first (default 20, at most 100). | |
| since | No | Received on or after this date (YYYY-MM-DD). | |
| before | No | Received before this date (YYYY-MM-DD). | |
| folder | No | Folder to search (default INBOX). Names as list_folders returns them. | |
| unseen | No | true: only unread messages; false: only read messages. | |
| subject | No | Substring of the subject. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=true, so the safety profile is covered. The description adds genuinely useful behavior beyond that: the exact return fields, newest-first ordering, and the INBOX default — valuable because there is no output schema. It stops short of describing pagination behavior or what happens when results exceed the limit.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two dense sentences with zero waste: the operation and its filter axes come first, then the return shape and ordering. Nothing is repeated or padded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 9-parameter, zero-required search tool with no output schema, the description covers the important context: default folder, date format, return fields, and sort order. The one remaining gap is how to page past the 100-message ceiling, which an agent would need to know for broad queries.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so every parameter is already documented with its semantics and defaults. The description restates the filter set and date format but adds no syntax or edge-case detail beyond the schema, so the baseline of 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (search) and resource (a folder of mail) plus the exact filter axes and returned fields. It implicitly separates itself from read_message by returning only headers/metadata (uid, date, from, subject, size, has_attachments) rather than bodies, but it never names the sibling it is not, so differentiation is inferred rather than stated.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is implied: search mail in a folder, defaulting to INBOX, with since/before in YYYY-MM-DD. There is no explicit when-to-use guidance versus read_message, list_folders, or read_attachment, and no guidance on how to retrieve more than the 100-result cap.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
sendADestructive
Send a message over SMTP and copy it to the Sent folder. Only works when the server allows sending, and only to allowed recipients; otherwise use draft.
| Name | Required | Description | Default |
|---|---|---|---|
| cc | No | Cc recipients. | |
| to | No | Recipients, `ann@example.org` or `Ann <ann@example.org>`. May be empty for a reply (goes to the original sender). | |
| body | Yes | Message body in Markdown (plain text is fine). Sent as text/plain (as written) plus text/html rendered from it; raw HTML is shown as text, images become links. | |
| folder | No | Folder of the in_reply_to message (default INBOX). | |
| subject | No | Subject (a reply defaults to "Re: " plus the original subject). | |
| in_reply_to | No | UID of the message this replies to; sets In-Reply-To and References. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Adds meaningful behavior beyond annotations: the message is copied to Sent, sending is conditional on server permissions and allowed recipients, and draft is the fallback. Annotations already cover the destructive/non-idempotent/open-world profile, so the description need not repeat those traits.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two compact sentences with the main action and constraints front-loaded. Every clause contributes useful routing or behavioral information without repetition.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a destructive send operation with rich annotations and fully documented inputs, the description covers the key operational constraints, side effect, and alternative tool. No output schema exists, and the agent has enough context to call it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already documents all six parameters, including reply metadata, recipients, and body formatting. The description adds no additional parameter semantics beyond what the schema provides, so baseline 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific action and resource: sending a message over SMTP and copying it to Sent. It also distinguishes itself from the sibling draft tool by naming draft as the fallback when sending is not allowed.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly gives the conditions for use: the server must allow sending and recipients must be allowed. It also names the alternative tool, draft, for the otherwise case, leaving little inference needed.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
6 tool updates
v0.1.3- First observed
draft - First observed
list_folders - First observed
read_attachment - First observed
read_message - First observed
search - First observed
send
TDQS
Scored across 6 tools
Each tool targets a clearly distinct operation: folder listing, searching, reading a message, reading an attachment, drafting, and sending. The draft vs send boundary is explicitly explained in the descriptions, and read_attachment is unambiguously tied to read_message output.
All names begin with a verb and are lowercase snake_case, giving a predictable verb-first convention (list_folders, read_message, read_attachment). Minor inconsistency: draft, search, and send omit the noun object, though the context makes them clear.
Six tools is well-scoped for an email server covering the essential read/search/draft/send lifecycle. Each tool earns its place with no redundant operations.
Core workflows are covered (list folders, search, read message/attachment, draft, send), and mark_read is handled via a parameter. However, notable lifecycle operations are missing: no delete, move/archive to another folder, flag, or mark-unread-after-reading, which are common email agent tasks.
Maintenance
Related MCP Connectors
Governed email for agents: Gmail, M365 and IMAP; per-mailbox permissions, send allowlist, audit log
Gmail, Outlook, iCloud, Fastmail and any IMAP mailbox for your agent. Scoped OAuth, approved sends.
Email inboxes for recurring agent work. Read, search, reply, draft and organize correspondence.
Email for AI agents: read, search and send from your Gmail, Outlook, Yahoo, iCloud or IMAP inbox.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to send and receive email with enforced security policies, scoped mailboxes, and human approval for external sending.264 npm1MIT
- AlicenseNot gradedqualityBmaintenanceEnables read-only access to multiple email accounts, including Gmail via App Password and generic IMAP providers, so an agent can list accounts and folders, search messages, and fetch messages or attachments. All operations are strictly read-only, with no sending, writing, deleting, or moving.1MIT
- AlicenseNot gradedqualityBmaintenanceEnables an MCP client to read, draft, organize, send, and delete email across multiple Gmail and Exchange Online/Outlook.com accounts, with per-account capability policies, recipient allowlists, confirmations, minimum OAuth scopes, and local audit logging.MIT
- AlicenseAqualityCmaintenanceEnables agents to read, search, organize, draft, and optionally send email from a cPanel mail account over IMAP and SMTP, with configurable safeguards for sending and permanent deletion.16MIT