Skip to main content
Glama
igorolv

mail-mcp-server

Mail MCP Server

CI Release License Java 25 MCP Glama score

Give your AI agent your whole mailbox, including on-premises Exchange, with no way to change it.

MCP server (stdio) for on-premises Microsoft Exchange over EWS and any IMAP mailbox with password sign-in, tested on Mail.ru and Gmail. It downloads every message once, converts bodies and attachments to Markdown on your machine, builds a Lucene search index, and answers the agent from that copy, so text inside PDF, Office and ZIP attachments is searchable in Russian and English.

Status: version 0.1.0 is published. Checked end to end on Exchange 2013 (about 8 thousand messages), Mail.ru (about 34 thousand) and Gmail with an app password. Developed and tested on Windows.

Contents: Features · How it works · Typical Scenarios · Quickstart · MCP Tools · Commands · Build · Stack · Documentation · License

Features

  • Works with on-premises Exchange. EWS with NTLM and your normal domain password, tested on Exchange 2013. No Microsoft Graph needed. Other mailboxes work over IMAP, tested on Mail.ru and Gmail.

  • Attachments are searchable too. Text from PDF, Word, Excel, PowerPoint, ZIP archives, attached emails, Outlook .msg and winmail.dat is extracted and indexed, so the agent can find the contract inside the attachment, not just the message that carried it.

  • Search that understands word forms. Russian and English, across subject, body, addresses and attachments. Copies of one message appear once, and conversations are followed across folders (search).

  • Cannot change your mailbox. Read-only is enforced at the protocol level: write commands are rejected before they are sent, and downloading a message does not mark it as read (security).

  • Careful with your account. One sign-in per sync and no retries. After a rejected password, it stops trying until you fix it, which reduces the risk of a domain lockout.

  • Fast, and works offline. The agent reads a local copy, so answers don't depend on the mail server. Sync runs only when you ask for it.

  • Your mail stays on your machine. No cloud service of its own: conversion and indexing are local, and the local copy is unfiltered. Your AI client still sees what the tools return, and an optional secret filter masks passwords and codes in responses.

  • Several mailboxes, one server. Passwords can stay out of the config, in Windows Credential Manager, a password manager or an environment variable (connections).

Related MCP server: work-assistant

How it works

  1. Download all mail. The sync tool copies the included folders of each mailbox as original .eml files, then only changes on later runs.

  2. Convert it locally to Markdown. Bodies and attachments — PDF, Office documents, archives, attached messages — are converted with pandoc (required), PDFBox and Apache POI.

  3. Index it. A Lucene index per mailbox with Russian and English word forms.

  4. Use it from MCP. Tools search and read the local copy only; they never contact a mail server.

 MCP client (Claude Code, Codex, OpenCode, VS Code, ...)
      |  JSON-RPC over stdin / stdout
      v
 mail-mcp-server --- search, read ---> Lucene index + <data-dir>/<connection>/messages/
      |                                       ^
      +--- sync (on request) --- EWS / IMAP --+--> .eml -> Markdown -> index

Typical Scenarios

The agent picks the tools itself; these are the chains it usually follows.

Scenario

Example request

Tools

Find mail on a topic

"Find the letters about the supply contract in work and show the attachments."

search → getMessage → getAttachment

Mail of a period

"What came to my work mail yesterday?"

search with since/until and no query → getMessage

Follow a conversation

"Show the whole discussion around this letter."

search → getThread → getMessage

Read an attachment

"What does the price list in the last letter from the supplier say?"

search (from, attachmentName) → getAttachment

Bring mail up to date

"Update my work mail and tell me what is new."

sync → syncStatus → search with since

Browse a folder

"List the latest messages in my inbox."

listFolders → listMessages

Quickstart

1. Install JDK 25 and pandoc 3.x. Pandoc is required and must be on PATH. Check in a new terminal:

java -version
pandoc --version

2. Get the jar: download mail-mcp-server.jar from the latest release, or build it yourself:

.\gradlew.bat bootJar      # -> build\libs\mail-mcp-server.jar

3. Describe your mailboxes in %USERPROFILE%\.mail-mcp-server\connections.json (~/.mail-mcp-server/connections.json elsewhere), one entry per mailbox. Keep only the ones you need and replace the example addresses:

{
  "connections": {
    "work": {
      "type": "ews",
      "url": "https://webmail.example.com/EWS/Exchange.asmx",
      "auth": "ntlm",
      "username": "user",
      "windowsCredential": "mail-mcp/work",
      "description": "Work mail, Exchange 2013"
    },
    "mailru": {
      "type": "imap",
      "host": "imap.mail.ru",
      "port": 993,
      "tls": true,
      "auth": "app-password",
      "username": "user@mail.ru",
      "passwordCommand": ["security", "find-generic-password", "-s", "mail-mcp", "-a", "mailru", "-w"],
      "description": "Personal Mail.ru"
    },
    "gmail": {
      "type": "imap",
      "host": "imap.gmail.com",
      "port": 993,
      "tls": true,
      "auth": "app-password",
      "username": "user@gmail.com",
      "password": "${MAIL_GMAIL_PASSWORD}",
      "description": "Personal Gmail"
    }
  }
}

Each entry shows a different password source: work reads Windows Credential Manager, mailru runs a command that prints the password (here the macOS Keychain; 1Password, pass and others work the same way), and gmail reads an environment variable, which must be set wherever the server starts. A literal password works too. Which password each provider needs (domain password, Mail.ru or Gmail app password, Gmail OAuth2) and how to store it: docs/connections.md.

4. Register the server with your MCP client, with an absolute path to the jar. Claude Code:

claude mcp add --scope user mail -- java -jar C:/path/to/mail-mcp-server.jar

Other clients and environment variables: docs/clients.md. If you installed pandoc or changed PATH while the client was running, fully restart the client.

5. Download your mail. Ask the agent: "Sync my work mailbox and tell me when it's done."

The first sync downloads the whole mailbox and can take a long time, so keep the client open. If it stops, ask again and it continues. A password manager must be unlocked or ask in its own window, because the server has no terminal to ask in. For a large mailbox, or to see progress in a terminal, use mail-sync.

6. Try it. Ask the agent: "Which mailboxes can you read?" or "Find the letters about the supply contract in work from last week."

Something went wrong: docs/troubleshooting.md.

MCP Tools

Every tool except listConnections takes a required connection argument.

Tool

What it does

listConnections

Configured mailboxes with type, sign-in method, description and configuration errors

sync

Starts a background synchronization and returns at once

syncStatus

Progress and result of the current or last run

listFolders

Local folders with message counts

listMessages

Message cards of one folder, newest first, in pages

getMessage

Headers, the body in chunks, attachments with file paths

getAttachment

The text of one attachment in chunks, and the file path

search

Full-text search with Russian and English word forms, fields, folder and date filters

getThread

The conversation of a message across folders, oldest first

Query syntax and results: docs/search.md. Arguments and limits: docs/tools-agent.md.

Commands

Offline maintenance, sign-in recovery and Gmail OAuth2 consent run in a terminal: docs/commands.md.

Build

.\gradlew.bat build        # compile, all tests, bootJar -> build\libs\mail-mcp-server.jar

The build needs a JDK 25 toolchain; Gradle can download one. Tests never contact a mail server: they use loopback fakes and the synthetic data sets in testdata/. Tests that need pandoc are skipped when it is not installed.

Stack

  • Java 25, Gradle 9 with a version catalog

  • Spring Boot 4.2.0-RC1, Spring AI 2.1.0-RC1 MCP server (stdio)

  • Apache HttpClient 5.6.5 (EWS with NTLM), StAX for SOAP

  • Eclipse Angus Mail 2.0.5 (IMAP and MIME parsing)

  • Apache Lucene 10.5.2 (index, Russian and English analysis, highlighting)

  • pandoc 3.x (external process), Apache PDFBox 3.0.5, Apache POI 5.4.1

Documentation

Document

Contents

docs/connections.md

The connections file, passwords, Exchange, Mail.ru and Gmail setup

docs/clients.md

Registering the server with MCP clients, environment variables

docs/commands.md

User-run commands, their output and exit codes

docs/search.md

Search syntax, dates, copies, conversations, word forms

docs/security.md

Read-only protection, sign-in policy, credentials, local data, secret filter

docs/troubleshooting.md

Common problems and what to check first

AGENTS.md

Rules for AI agents changing this repository, and the map of engineering documents (docs/*-agent.md)

docs/plan.md, docs/decisions.md, docs/results.md

Plan, decisions and real-mailbox results

License

This project is licensed under the Apache License, Version 2.0. Dependencies keep their own licenses: THIRD_PARTY_NOTICES.md.

Available Tools

9 tools
getAttachmentgetAttachmentA
Read-onlyIdempotent

Read an attachment's saved extracted text as Markdown, in chunks, with its metadata and local file paths. This reads existing text; it does not convert the file on demand. If text is absent, extracted text is unavailable, not proof that the original attachment is empty. The original may still be available at path; path is absent if the file is missing.

Get file from getMessage's attachments[].file or a search hit's snippetAttachmentFile, keeping that message's connection and key. To continue a long text, call this tool again with the same connection, key and file and offset=nextOffset. Stop when there is no nextOffset.

NameRequiredDescriptionDefault
keyYesLocal key of the message containing this attachment, from getMessage or the search hit; use the same connection
fileYesExact attachments[].file from getMessage or snippetAttachmentFile from search for this message. Use this value unchanged, not name, path or textPath
offsetNoText character offset, default 0. Use nextOffset from this tool to continue this attachment, not an offset from message-body or search results
maxCharsNoText characters per chunk: 1 to 100000, default 20000; larger values are capped at 100000
connectionYesConnection name from listConnections

Output Schema

NameRequiredDescription
keyYesMessage key.
fileYesAttachment file, as requested.
nameNoOriginal file name.
pathNoAbsolute path of the original file; absent when it is not saved.
sizeYesSize in bytes.
textNoChunk of the saved extracted text as Markdown; [redacted:<kind>] marks a hidden secret. Absent when extracted text is unavailable; the original attachment may still have content and be available at path.
offsetYesCharacter position of this chunk.
textPathNoAbsolute path of the extracted Markdown text; absent when there is no text.
nextOffsetNoPass as offset to get the next chunk; absent at the end.
textLengthYesLength of the whole text in characters; 0 without text.
contentTypeYesMIME type, e.g. application/pdf.

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the read-only and idempotent annotations, it discloses important behavior: chunked reads, metadata and local paths, continuation rules, and the distinction between unavailable extracted text and an empty original attachment. These are meaningful operational details that help the agent avoid misinterpreting absence.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with purpose and then gives usage and caveats in short, purposeful sentences. There is little redundancy, and the continuation guidance is placed where an agent needs it.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The tool is moderately complex with a full schema, complete schema descriptions, annotations, and an output schema. The description fills the remaining gaps around chunked reading, continuation, missing text, and missing files without needing to repeat return-value details.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is complete, so the schema already documents all parameters. The description still adds useful cross-parameter semantics by explaining where file, key, and connection come from and that continuation requires the same connection, key, and file with offset=nextOffset.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: reading an attachment's saved extracted text as Markdown in chunks. It also explicitly distinguishes this from on-demand conversion, so the agent understands the tool reads existing extraction rather than creating it.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives clear sourcing guidance: obtain file from getMessage attachments or a search hit's snippetAttachmentFile, and reuse the same connection and key. It also explains continuation via offset=nextOffset and when to stop, though it does not name a specific alternative tool for on-demand conversion.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

getMessagegetMessageA
Read-onlyIdempotent

Read one locally stored message: headers, a body chunk as Markdown, and an attachment list. nextOffset continues only the body; headers and the attachment list are returned on each call. If body is absent, saved body text is unavailable; inspect state and error rather than assuming the original message was empty.

To read an attachment, call getAttachment with the same connection and key, and file=attachments[].file. Use file, not name or path. The list includes files extracted from attached archives and messages; parent is the containing attachment's file. For the conversation, call getThread with the same connection and key.

NameRequiredDescriptionDefault
keyYesLocal message key from listMessages, search or getThread in this connection; use key, not the Message-ID header
offsetNoBody character offset, default 0. To continue, keep the same connection and key and use nextOffset from this tool. Stop when there is no nextOffset
maxCharsNoBody characters per chunk: 1 to 100000, default 20000; larger values are capped at 100000
connectionYesConnection name from listConnections

Output Schema

NameRequiredDescription
ccNoCc recipients as Name <address>.
toNoTo recipients as Name <address>.
keyYesMessage key.
bodyNoChunk of the saved body as Markdown; [redacted:<kind>] marks a hidden secret. Absent when saved body text is unavailable; this does not prove the original body was empty.
dateNoDate from the message header; absent when missing.
fromNoSenders as Name <address>.
readNoRead in the mailbox as of the last sync; absent when unknown.
errorNoWhy the message failed; present when state is failed.
stateYesconverted (complete), downloaded (saved, text not processed yet) or failed (see error).
folderYesFolder path in the mailbox; not the folder key used by listMessages/search.
labelsNoMailbox labels of this message as of the last sync, such as Gmail's Inbox, Sent or user labels; empty when it has none. Absent when unknown or when the mailbox has no labels.
offsetYesCharacter position of this chunk.
flaggedNoFlagged or starred in the mailbox as of the last sync (not sender importance); absent when unknown.
subjectNoSubject.
inReplyToNoIn-Reply-To header.
localPathYesAbsolute message directory, readable directly: message.eml (original), body.md, attachments/.
messageIdNoMessage-ID header.
bodyLengthYesLength of the whole body in characters; 0 without a body.
nextOffsetNoPass as offset to get the next chunk; absent at the end.
referencesNoReferences header message IDs.
attachmentsYesAttachments, including files unpacked from attached archives and messages. Empty when none are found or the saved MIME cannot be read. Pass an entry's file to getAttachment with this message's key and connection.

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations cover the safety profile (readOnly, idempotent, non-destructive), and the description adds behavior the annotations can't: pagination continues only the body while headers and the attachment list are re-returned each call, and an absent body means saved body text is unavailable rather than the original being empty. That last point is a genuine anti-misinterpretation warning.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two tight paragraphs with the return shape front-loaded and the escape hatches second. Minor redundancy with the offset/nextOffset schema text, but nothing is padding.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Output schema exists so return values need no prose, and the description still covers the edge cases an agent would otherwise get wrong: body-only pagination, empty-body interpretation, archive-extracted attachments and the parent field. Nothing needed to call it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so key, offset, maxChars and connection are already documented, including the nextOffset continuation rule. The description restates that rule and adds only the `file` vs `name`/`path` disambiguation, which applies to the sibling tool rather than this one's parameters.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific verb and resource plus the exact payload ('headers, a body chunk as Markdown, and an attachment list'), which no sibling tool claims. It also implicitly separates itself from getThread and getAttachment by naming them for those respective jobs.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly routes adjacent needs: attachments go to getAttachment with the same connection/key and file=attachments[].file, the conversation goes to getThread. It also gives the continuation rule (keep connection and key, follow nextOffset, stop when absent), so an agent knows when to call again versus call elsewhere.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

getThreadgetThreadA
Read-onlyIdempotent

List the locally indexed conversation containing a message, across all folders of one connection. Returns cards without bodies, oldest first, with undated messages last. Only locally indexed messages can appear. Messages are linked by reply headers (Message-ID, In-Reply-To, References), not by subject or participants.

Read each message with getMessage using the same connection and that card's key. Recognized copies are returned once, with other keys and folders under copies; total and paging count groups.

NameRequiredDescriptionDefault
keyYesLocal message key from listMessages or search in this connection. Any member of the conversation works; use key, not the Message-ID header
limitNoResults per page: 1 to 200, default 50; larger values are capped at 200
offsetNoDefault 0. For the next page, keep the same connection and original key and use the previous nextOffset. Stop when there is no nextOffset
connectionYesConnection name from listConnections

Output Schema

NameRequiredDescription
keyYesMessage key the conversation was requested for.
totalYesMessages in the conversation; copies of one message count once.
offsetYesPosition of this page.
messagesYesMessages, oldest first; undated last.
connectionYesConnection name.
nextOffsetNoPass as offset to get the next page; absent at the end.

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare read-only, idempotent, non-destructive, but the description adds substantial substance beyond them: cards come back without bodies, ordered oldest first with undated last, only locally indexed messages appear, messages are linked by Message-ID/In-Reply-To/References rather than subject or participants, and recognized duplicates are collapsed into a copies field with totals counting groups.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two tight paragraphs, front-loaded with the scope and ordering before the follow-up instructions. Nearly every sentence carries load, though the trailing clause 'total and paging count groups' is terse enough to be slightly opaque.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return shape needn't be spelled out, yet the description still covers the semantics an agent needs: body-less cards, ordering, duplicate collapsing, and header-based threading. Combined with annotations covering the safety profile, nothing needed to call it correctly is absent.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and already documents key, limit range and cap, and the offset/nextOffset continuation protocol, so the baseline applies. The description reinforces that the 'key' is a card key from the returned list rather than a Message-ID header, but adds no new syntax or constraints beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource — 'List the locally indexed conversation containing a message, across all folders of one connection' — which cannot be confused with listMessages or search. It also distinguishes itself from getMessage by positioning that sibling as the follow-up for reading bodies.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly routes the agent: use this to enumerate the conversation, then call getMessage with the same connection and each card's key. It gives a paging stopping rule ('Stop when there is no nextOffset'). What's missing is an explicit statement of when NOT to use it (e.g., a single-message read or a subject-based grouping), though the linking-by-headers note implies the latter.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

listConnectionslistConnectionsA
Read-onlyIdempotent

List the configured mailboxes. Call first when the mailbox to use is not known yet: name is the value for the 'connection' argument of every other tool, description says what the mailbox is for, configError explains why an entry cannot be used. This reads local configuration without signing in. localDataAvailable indicates a local data directory, not complete or up-to-date mail; use listFolders and syncStatus to inspect coverage.

NameRequiredDescriptionDefault

No parameters

Output Schema

NameRequiredDescription
connectionsYesConfigured mailboxes; empty when none is configured.

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description adds meaningful behavior beyond the annotations: it reads local configuration without signing in, and it warns that localDataAvailable does not imply complete or up-to-date mail. Those details help an agent interpret and trust the result correctly.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The purpose is front-loaded in the first sentence, followed by usage and field semantics in a compact sequence. Every sentence carries actionable information, with no redundant restatement of the name or title.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a zero-parameter read-only listing tool with an output schema and annotations covering safety, the description is complete enough. It explains when to call it, how to use its output, and an important caveat about local data coverage.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

There are no input parameters, so parameter semantics are inherently minimal and the schema has nothing to document. The description instead explains key output field meanings, which is useful but outside the parameter dimension.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: 'List the configured mailboxes.' It also distinguishes the tool from siblings by explaining that it should be called first when the mailbox is unknown and that its name field feeds the 'connection' argument of every other tool.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives an explicit trigger: 'Call first when the mailbox to use is not known yet.' It also redirects agents to listFolders and syncStatus for coverage inspection, which clarifies an important alternative path.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

listFolderslistFoldersA
Read-onlyIdempotent

List locally known folders in one connection, with folder keys, paths and local indexed message counts. Pass a folder's key as folder to listMessages or search; keep the same connection. synced=false means a completed folder sync has not been recorded and messages may be missing. synced=true does not guarantee current mail; use syncStatus for the latest sync's time and outcome. This tool does not discover folders on the server.

NameRequiredDescriptionDefault
connectionYesConnection name from listConnections

Output Schema

NameRequiredDescription
foldersYesFolders.
connectionYesConnection name.

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly/idempotent/non-destructive, and the description goes well beyond them by disclosing the meaning of synced=false (a completed folder sync not recorded, messages may be missing) and the subtle caveat that synced=true does not guarantee current mail. That is exactly the non-obvious data-freshness behavior an agent needs before trusting results.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Five short sentences, front-loaded with what the tool returns, then downstream usage, then freshness caveats, then negative scope. No sentence is redundant with another or with the structured fields.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists so return shape needn't be described; the description instead fills in the interpretive context the schema cannot carry (sync semantics, cross-tool key usage, non-discovery). Nothing an agent needs to call this correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The single parameter has 100% schema coverage and already points to listConnections, so baseline would be 3. The description adds real meaning by stressing one connection and 'keep the same connection', signalling that folder keys are scoped to a connection and misusing this yields wrong lookups.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource (list locally known folders) plus exactly what is returned (folder keys, paths, local indexed message counts). It also scopes itself negatively against a hypothetical discovery tool ("does not discover folders on the server") and routes the folder key to listMessages/search, so an agent can place it among siblings without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly tells the agent what to do with the output ("Pass a folder's key as folder to listMessages or search; keep the same connection") and what to consult when freshness matters (syncStatus). It lacks an explicit when-not-to-call statement, though the non-discovery scoping sentence covers most of that gap.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

listMessageslistMessagesA
Read-onlyIdempotent

Browse locally indexed messages in one folder, newest first, optionally within a date range. Undated messages come last without date bounds and are excluded with bounds. For keyword search use search; for a date range across all folders use search without query.

Returns cards without bodies. Pass a card's key and the same connection to getMessage to read it, or to getThread for its conversation. Every stored copy counts separately here; search and getThread group recognized copies. read/flagged describe the last sync observation.

NameRequiredDescriptionDefault
limitNoResults per page: 1 to 200, default 50; larger values are capped at 200
sinceNoInclude messages at or after this date/time, using the message Date header, not receipt or download time. Format: YYYY-MM-DD (UTC midnight), or ISO-8601 with Z or an offset, e.g. 2026-10-01T09:00:00+03:00; at most 3 fractional second digits. Messages without a date are excluded
untilNoInclude messages strictly before this date/time; same header and format as since. To include a whole day, use the following day's midnight. Must be later than since when both are given. Messages without a date are excluded
folderYesOne folder key from listFolders in this connection, not its display name
offsetNoDefault 0. For the next page, repeat the same arguments and use the previous nextOffset. Stop when there is no nextOffset
connectionYesConnection name from listConnections

Output Schema

NameRequiredDescription
sinceNoApplied since bound, in UTC.
totalYesMessages in the folder within the date bounds.
untilNoApplied until bound, in UTC.
folderYesFolder key.
offsetYesPosition of this page.
messagesYesMessages, newest first; undated last.
connectionYesConnection name.
nextOffsetNoPass as offset to get the next page; absent at the end.

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly/idempotent/non-destructive, and the description adds substantial behavior beyond them: undated messages sort last without bounds but are excluded with bounds, results are cards without bodies, every stored copy is counted separately whereas search and getThread group recognized copies, and read/flagged reflect the last sync observation. These are non-obvious semantics that materially change how results should be interpreted.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Four short sentences, front-loaded with what the tool returns and how it differs from search, then the follow-up path, then the subtle copy-counting nuance. No filler and no repetition of schema field descriptions.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, yet the description still sketches the card shape and the read/flagged provenance, which is the right level. Between the description, the 100%-covered schema, and the annotations, an agent has everything needed to call it correctly and chain into getMessage/getThread.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

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 earns extra credit by adding cross-parameter behavior the schema does not state, namely the ordering and exclusion rule for undated messages at the since/until boundaries. It stops short of restating formats, which is appropriate since the schema already carries them.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource (browse locally indexed messages), the scope (one folder), the ordering (newest first), and the optional date filter. It also explicitly distinguishes itself from 'search' and from the card-reading tools getMessage/getThread, so an agent can route without opening sibling schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit when-to-use-elsewhere rules: 'For keyword search use search; for a date range across all folders use search without query.' It also names the follow-up path (pass a card's key plus the same connection to getMessage, or to getThread), which is exactly the alternative-selection guidance an agent needs.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

syncsyncA
Read-only

Refresh one connection's local mail copy from the mail server, including new messages, removals, moves and read/flag states. The remote mailbox is not changed. Call when the user needs newer mail than the local copy. Runs in the background; the first sync can take a long time.

status=started means launched, not finished. For started or alreadyRunning, check syncStatus with the same connection while state is running; do not call sync again to check progress. lockedByOtherProcess means another process holds the local write lock and this call started no sync. That process may be an offline command; syncStatus reports sync runs only.

For refused, relay message to the user and do not retry: after a rejected sign-in, the user must fix the credentials and run mail-sync in a terminal.

NameRequiredDescriptionDefault
connectionYesConnection name from listConnections

Output Schema

NameRequiredDescription
statusYesstarted (launched, not completed); alreadyRunning (a sync is already running in this process); lockedByOtherProcess (another process holds the local write lock, possibly for an offline command; this call started no sync); refused (see message). Use syncStatus for started/alreadyRunning; it reports sync runs, not offline command progress.
messageYesExplanation; relay it to the user when status is refused.
connectionYesConnection name.

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes well beyond the annotations by documenting the async state machine: status=started means launched not finished, lockedByOtherProcess means no sync was started and may be an offline command, and refused requires user action in a terminal. The 'remote mailbox is not changed' clause reconciles with readOnlyHint=true and clarifies that only the local copy is written.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with purpose and scope in the first sentence, then organized into usage and state-handling sections with no filler. The dense state paragraph is justified by the tool's async behavior, though it could be tightened slightly for scanning.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need no explanation, and the description still supplies the operational envelope: latency warning ('the first sync can take a long time'), background execution, per-status guidance, and a lock-conflict case. Nothing critical for correct invocation is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and the single parameter is already documented as 'Connection name from listConnections'. The description only refers to 'the same connection' when describing syncStatus polling, adding no new format or constraint information. Baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Refresh one connection's local mail copy from the mail server') and enumerates exactly what is propagated (new messages, removals, moves, read/flag states), plus an explicit non-effect ('The remote mailbox is not changed'). This clearly separates it from syncStatus, which only reports progress.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives an explicit trigger ('Call when the user needs newer mail than the local copy'), an explicit anti-pattern ('do not call sync again to check progress' → use syncStatus with the same connection), and a failure-path instruction ('For refused, relay message to the user and do not retry'). No inference required.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

syncStatussyncStatusA
Read-onlyIdempotent

Read local progress or the result of the current or last sync; this does not start a sync or contact the mail server. state=running means still working; completed means the run finished; failed means the run stopped with an error; interrupted means the syncing process ended before finishing; never means no sync is recorded. phase is the stage reached; checkingLocal and checkingFolders can take minutes with no folder progress yet.

While running, other tools may see incomplete results. Check state to decide whether to keep waiting. The failed count is a count of messages: completed can still have failed>0. Inspect error and signInRejected for a failed run. If signInRejected is true, tell the user to fix credentials and run mail-sync in a terminal; repeated sync calls cannot recover it. Timestamps describe this run, not a guarantee that the local mail is current now.

NameRequiredDescriptionDefault
connectionYesConnection name from listConnections

Output Schema

NameRequiredDescription
errorNoWhy the sync failed.
phaseNoStage reached, in order: checkingLocal (comparing saved messages with the index, offline; long after a run that did not complete), signingIn, checkingFolders (collecting changes from every folder), applyingChanges (moves and flag changes), downloading (removing and downloading folder by folder). For failed or interrupted, the stage where the run stopped. Absent for never and for runs recorded by an older version.
stateYesrunning (still working), completed (run finished, possibly with individual message failures), failed (run stopped with an error), interrupted (syncing process ended before finishing), or never (no recorded sync).
failedYesCount of new messages that failed to download or convert, all folders. May be nonzero even when state is completed.
foldersYesProgress per folder.
removedYesLocal messages removed because they are gone from the mailbox, all folders.
startedAtNoWhen the sync started.
connectionYesConnection name.
downloadedYesNew messages saved, all folders.
finishedAtNoWhen the sync ended; absent while running.
signInRejectedYesTrue when the mailbox rejected the sign-in; sync works again only after the user fixes the credentials and runs mail-sync in a terminal.

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes well beyond the readOnly/idempotent annotations: enumerates every state and phase value, warns that checkingLocal/checkingFolders can stall for minutes with no progress, notes other tools may see incomplete results while running, clarifies that completed can still have failed>0 (count is of messages, not runs), and explains the signInRejected recovery path. No contradiction with annotations — non-destructive read is consistent.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Dense but front-loaded: the core read-only claim and the state/phase glossary lead, with edge-case handling following. Some sentences (timestamps caveat, phase latency note) are closely packed, but each conveys distinct operational information rather than repeating the schema.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Output schema exists, yet the description still explains the semantics of the key return fields (state, phase, failed, error, signInRejected) that an agent needs in order to act. Combined with the latency and recovery guidance, nothing necessary for correct invocation or follow-up behavior is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Only one parameter (`connection`) and schema coverage is 100%, with the schema itself pointing at listConnections for valid values. The description adds no further parameter semantics, which is acceptable here since the schema fully covers it, but there is no value added beyond it.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Specific verb+resource: reads progress/result of the current or last sync, and explicitly disclaims what it does not do (start a sync, contact the mail server). That negative clause is exactly what separates it from the sibling `sync` tool without opening either schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives clear operational context: 'Check state to decide whether to keep waiting', and warns that repeated sync calls cannot recover a signInRejected failure — the agent should instead tell the user to fix credentials in a terminal. It stops short of explicitly naming `sync` as the tool that actually starts a sync, leaving that inference to the reader.

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.

  1. 9 tool updatesv0.1.0
    • First observedgetAttachment
    • First observedgetMessage
    • First observedgetThread
    • First observedlistConnections
    • First observedlistFolders
    • First observedlistMessages
    • First observedsearch
    • First observedsync
    • First observedsyncStatus

TDQS

A4.5/5.0

Scored across 9 tools

Disambiguation5/5

Each tool has a clearly distinct role: connection discovery, folder browsing, message browsing/reading, search, thread reading, attachment reading, sync, and sync status. The descriptions explicitly disambiguate close pairs like listMessages vs. search and getMessage vs. getThread.

Naming Consistency4/5

Most tools follow a consistent camelCase verb_noun pattern such as listConnections, getMessage, listFolders, and getThread. The bare verbs search and sync plus syncStatus are minor deviations but still readable and predictable.

Tool Count5/5

Nine tools is well-scoped for a local mail access server. Each tool earns its place by covering a specific part of the browse/read/search/sync workflow.

Completeness4/5

The set covers the core read-only mail workflow: connections, folders, messages, search, threads, attachments, syncing, and sync status. It lacks compose/send or message mutation operations, but those may be outside the apparent local-copy purpose.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    B
    maintenance
    Reads and searches local email archives (.mbox or .eml) with full MIME parsing, enabling queries about senders, subjects, dates, and bodies while keeping all data on your machine.
    3
    188 npm
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI agents to search and read a local, provider-independent email archive, reconstruct contacts and interactions, and prepare draft responses without sending anything.
    1
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables AI assistants to read, search, send, reply, forward, schedule, and organize email across multiple IMAP/SMTP accounts, including drafts, labels, bulk operations, real-time mailbox watching, verification-code capture, calendar extraction, and analytics. It runs locally over stdio or as a networked Streamable HTTP service with OAuth2 sign-in and provider auto-detection.
    21 npm
    ISC
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI agents to search and read email, inspect attachments, and optionally send messages and organize folders through existing IMAP and SMTP mailboxes. Supports Docker, stdio, and HTTPS OAuth deployment with read-only or full-access modes.
    MIT