dead-letter
dead-letter's MCP server lets LLM clients convert local email exports into clean Markdown and inspect conversion quality.
Convert a single
.emlto Markdown with YAML front matter, optionally writing it to a specified output path.Convert a
.emlinto a bundle with Markdown, extracted attachment files, and (over MCP, copy-only) the untouched original source.Recursively batch-convert directories of
.emlfiles into Markdown, capped at 50 messages per call.Convert a flat
.mboxarchive into Markdown files or bundles, bounded at 256 MiB and 1000 messages per call.Get diagnostics for an
.eml(state, selected body, segmentation, warnings, attachments, stripped images) without writing permanent files.Control output with presets (
default,clean,verbose,raw), thread mode/order, and per-flag overrides for signatures, disclaimers, quoted headers, tracking pixels, inline images, raw HTML, headers, and calendar summaries.
dead-letter
Turn .eml email exports into clean, local, LLM-ready Markdown.
dead-letter converts .eml email exports into clean Markdown with YAML front matter — threads split, signatures stripped, attachments extracted, calendars parsed. One file or ten thousand.
Use it to build a readable email archive, move messages into Markdown-based knowledge systems, or prepare email for RAG and LLM pipelines without feeding raw MIME and base64 into your context window. No account, upload, or API key required.
⚡ Try it
If you already have uv, run dead-letter without installing it globally:
uvx --python 3.12 dead-letter convert message.emlOr install with Homebrew or pip below. Agents and MCP clients can use the client-specific setup in llms-install.md.
Related MCP server: DingusMail
🎯 Common use cases
Email → Markdown archives — turn exported
.emlcollections into readable, portable Markdown with structured metadataRAG and LLM ingestion — normalize message text, thread structure, links, and attachment metadata before chunking or indexing
Agent workflows — expose conversion and diagnostics directly to Claude, Codex, and other MCP clients
Knowledge bases — move email into Markdown-first systems such as Obsidian, static archives, or local search pipelines
Digital preservation — retain human-readable content and useful message structure without depending on one mail client
✨ Features
Full-fidelity conversion — HTML sanitization, Gmail/Outlook thread segmentation, inline image handling, and calendar event summaries
CLI — point it at a file or a directory and go
Local web UI — dark command-center interface with drag-and-drop import, watch mode, conversion grade badges, processing history, and per-job diagnostics
Inbox/Cabinet workflow — drop
.emlfiles into an Inbox, let dead-letter organize the Markdown bundles into a CabinetInstall validation —
dead-letter doctorchecks your runtime environmentConversion report — opt-in JSON report with per-file diagnostics, including attachment referenced/retained counts for automation and audit
MCP server — integrate with Claude Desktop, Claude Code, Codex, and other MCP clients
Claude plugin — marketplace install in Claude Code or Cowork with five slash commands (
/dead-letter:convert,/dead-letter:summarize,/dead-letter:triage,/dead-letter:cabinet,/dead-letter:mbox)Portable Agent Skill — teaches skill-aware agents when and how to convert
.emlfilesPython API —
from dead_letter import convertand you're off
🧠 Built for LLM Pipelines
Raw .eml files are noisy input for downstream LLM and retrieval pipelines — MIME headers, multipart boundaries, duplicated HTML/plain bodies, and encoded attachments all get mixed into the text path.
dead-letter normalizes that into Markdown with YAML front matter, so message text and metadata are ready for chunking or indexing without MIME parsing or base64 cleanup. Default convert() and convert_dir() runs write a single .md per message and keep attachment names in front matter.
To separate the filesystem artifacts too, bundle and Cabinet workflows write message.md plus retained decoded files under attachments/. The Markdown is ready for text ingestion, while PDFs, spreadsheets, calendar files, and other retained binary attachments stay cleanly split out for whatever downstream parser you already use.
For direct LLM integration, the MCP server lets clients call dead-letter's conversion tools without shelling out. Conversion is local; your chosen MCP host may still send returned email text to a remote model.
📊 Token-cost benchmarks
dead-letter's value isn't fewer tokens than every alternative — it's fidelity per token: keeping useful email structure without carrying raw MIME into context. Measured across an 11-message synthetic corpus of HTML threads, attachments, and newsletters (tokenizer o200k_base, structured thread mode):
~88% fewer tokens than the raw
.emlin the reported aggregate comparison — the attachment category's median is ~126k tokens raw vs ~180 converted.Structure survives — thread structure, per-message sender attribution, links, and attachment metadata remain readable. The tested naive baselines are often cheaper because they discard information (0/2 attachment names retained vs dead-letter's 2/2).
Those counts measure the Markdown representation, not the contents of retained binary attachments or downstream answer quality. The shipping default is latest-message mode; the benchmark uses structured mode for a same-thread comparison.
The benchmark is honest about where it loses: naive extraction is fewer tokens when you don't mind throwing away metadata, links, and thread structure. Full method, the complete table (including those rows), tokenizer disclosure, and a one-command reproduce are in benchmarks/.
📦 Install
Pick one route. The distribution map explains how the channels fit together; installing all of them is not necessary.
You want | Start here |
Core CLI or Python API | Homebrew / pip below, or the |
Local web UI |
|
Claude Desktop extension or another MCP client | |
Claude Code / Cowork commands | |
Container-isolated MCP | |
Portable agent instructions |
With Homebrew on Apple silicon macOS:
brew tap BigCactusLabs/tap
brew install dead-letterThe Homebrew formula installs the CLI (dead-letter convert, dead-letter doctor).
Formulas for 0.4.7 and later also install the dead-letter-mcp server; the
optional web UI is not included.
With pip:
pip install dead-letter # core + CLI
pip install 'dead-letter[cli]' # + watchfiles (used by backend/UI watch mode)
pip install 'dead-letter[ui]' # + web UI, API server, and watch mode
pip install 'dead-letter[mcp]' # + MCP serverUse pipx for an isolated UI or MCP install:
pipx install 'dead-letter[ui]' # installs dead-letter and dead-letter-ui
# Or, for MCP instead:
pipx install 'dead-letter[mcp]' # installs dead-letter and dead-letter-mcpOr run individual entrypoints without a global package install using uvx:
uvx --python 3.12 dead-letter convert message.eml
uvx --python 3.12 --from 'dead-letter[mcp]' dead-letter-mcpuv caches tools/dependencies and may download Python on first use. These unpinned trial commands do not promise a fresh latest version on every run; see version pinning for a reviewed deployment.
From source:
git clone https://github.com/BigCactusLabs/dead-letter.git
cd dead-letter
uv sync --extra dev --locked # all extras
# Or choose only the surface you're developing:
uv sync --extra ui --locked # UI only
uv sync --extra mcp --locked # MCP onlyAgent Skill (any host)
Install the portable Agent Skill into whichever agent you use:
gh skill install BigCactusLabs/dead-letter dead-letter --agent claude-code
gh skill install BigCactusLabs/dead-letter dead-letter --agent codex
gh skill install BigCactusLabs/dead-letter dead-letter --agent github-copilotNeeds gh 2.90 or newer. The skill is independent of the Claude plugin. Pin a
reviewed skill tag/commit for reproducibility; the default latest release can
also be a plugin release. For exact pin syntax, other hosts, manual installation,
and discovery metadata, see Agent Discovery.
🚀 Quick Start
CLI — convert a single file:
dead-letter convert message.emlConvert a whole directory:
dead-letter convert inbox/ --output out/Generate a JSON conversion report alongside the output:
dead-letter convert inbox/ --output out/ --reportWith --output, the report is written to that output directory as
.dead-letter-report.json. Without --output, file conversions write the
report next to the source message and directory conversions write it to the
input directory root.
Check your runtime environment:
dead-letter doctorDirectory conversion scans recursively for .eml files, matches the suffix
case-insensitively, skips symlinked files whose resolved targets escape the
requested input tree, and deduplicates in-tree symlink aliases that resolve to
the same message file.
Web UI — start the local server:
dead-letter-ui --host 127.0.0.1 --port 8765Open http://127.0.0.1:8765 — on first launch, a setup prompt suggests default Inbox and Cabinet folders. Configure those folders before importing or starting jobs. Skipping dismisses the prompt but leaves those actions gated until setup is completed. Import .eml files with drag and drop or the file picker. Single-file imports use file mode, while multi-file drops create one directory-mode batch job. Mixed drops ask for confirmation before skipping non-.eml files.
The backend enforces a 100 MB per-file import limit for both single and batch uploads; browser batches also have aggregate-size and file-count limits.
From a source checkout, prefix with uv run:
uv run dead-letter convert message.eml
uv run --extra ui dead-letter-ui --host 127.0.0.1 --port 8765🐍 Python API
from dead_letter import convert
result = convert("message.eml")
print(result.subject, result.sender)
print(result.output) # path to the generated .mdWith options:
from dead_letter import convert, ConvertOptions
result = convert("message.eml", options=ConvertOptions(
strip_signatures=True,
strip_quoted_headers=True,
))Strip signature images (logos, social icons) and tracking pixels:
result = convert("message.eml", options=ConvertOptions(
strip_signature_images=True,
strip_tracking_pixels=True,
))When enabled, these filters remove matched images from rendered Markdown and omit stripped inline signature/tracking assets from bundle attachment output.
Bundle conversion (Markdown + attachments + source in one directory):
from dead_letter import convert_to_bundle
bundle = convert_to_bundle("message.eml", bundle_root="cabinet/", source_handling="copy")
print(bundle.markdown) # cabinet/message/message.md
print(bundle.attachments) # retained extracted files under cabinet/message/attachments/source_handling="copy" preserves the original .eml in place. If omitted,
convert_to_bundle() defaults to source_handling="move" and moves the source
message into the bundle.
Retained extracted attachment filenames are normalized to safe basenames before
they are written under attachments/.
Quality diagnostics include referenced/retained attachment counts when a message has attachments eligible for retention, so dropped artifacts are machine-detectable. See Quality Diagnostics.
Batch:
from dead_letter import convert_dir
for r in convert_dir("inbox/", output="out/"):
print(f"{'✓' if r.success else '✗'} {r.source.name}")🔌 MCP Server
dead-letter ships an MCP server so LLM clients can convert .eml files directly without shelling out.
VS Code, Cursor, and Cline:
Install in VS Code · Install in Cursor
Requires uv/uvx on the desktop client's PATH. These links configure a local
stdio server using the published PyPI package; no catalog admission is needed.
First use may download Python and dependencies. Review the command, scope, and
tool permissions before accepting. Cline setup, manual JSON examples, and
verification: Client installation.
Launch it directly with uvx:
uvx --python 3.12 --from 'dead-letter[mcp]' dead-letter-mcpOr install it first (from 0.4.7 the MCP SDK is a core dependency; [mcp] is
kept as a compatibility extra):
pip install 'dead-letter[mcp]'
dead-letter-mcpFrom a source checkout:
uv run dead-letter-mcpClaude Desktop (extension bundle):
Download the .mcpb file and its .sha256 sidecar from a published package release (vX.Y.Z) on the releases page. Plugin-only releases do not contain this bundle. Verify the bytes before installation:
# macOS: verifies against the downloaded sidecar
shasum -a 256 -c dead-letter-mcp-X.Y.Z.mcpb.sha256
# Windows (PowerShell): compare this hash to the sidecar's hash
certutil -hashfile dead-letter-mcp-X.Y.Z.mcpb SHA256Replace X.Y.Z with the selected package version. In a compatible Claude Desktop build, double-click the downloaded bundle, drag it onto the window, or use Settings > Extensions > Advanced settings > Install Extension. Check that the extension connects and exposes the tools below (five from 0.4.5; convert_mbox is absent in 0.4.0 and earlier), then convert a synthetic message.
The bundle uses a managed uv runtime and an exact package pin. First launch may download Python and dependencies. A checksum or command-line smoke test is not proof of a successful GUI installation on your client version. For hosts without MCPB support, use manual stdio setup below.
Claude Desktop (manual claude_desktop_config.json — alternative):
{
"mcpServers": {
"dead-letter": {
"command": "uvx",
"args": ["--python", "3.12", "--from", "dead-letter[mcp]", "dead-letter-mcp"]
}
}
}Merge the entry rather than replacing existing client settings. VS Code and other hosts can use different schemas; see the agent install guide.
Claude Code or Cowork (recommended — Claude plugin):
/plugin marketplace add BigCactusLabs/bigcactuslabs-plugins
/plugin install dead-letterOn Claude Code 2.1.275 or later, /plugin install dead-letter --marketplace BigCactusLabs/bigcactuslabs-plugins does both in one step.
The plugin launches the MCP server via uvx and adds five slash commands: /dead-letter:convert, /dead-letter:summarize, /dead-letter:triage, /dead-letter:cabinet, /dead-letter:mbox. Local Claude Code needs uv on PATH; see plugin/ for runtime-specific setup and updates. Email content is treated as untrusted data, not instructions: embedded requests for tool use, credentials, or exfiltration are not followed.
The marketplace pins the plugin tag and commit, and its launcher pins an exact published Python package. Claude Code and Cowork keep separate installed copies; update and verify each client. The plugin also ships a uv.lock with hashed dependency versions for hosts that launch from a lock; plain uvx resolves compatible transitive versions itself.
Claude Code (manual MCP add — alternative):
claude mcp add dead-letter -- uvx --python 3.12 --from 'dead-letter[mcp]' dead-letter-mcpCodex:
codex mcp add dead-letter -- uvx --python 3.12 --from 'dead-letter[mcp]' dead-letter-mcp
codex mcp listmcp list confirms registration, not a successful tool call. Connect through the target client, confirm the tools below, and convert a synthetic fixture before treating the installation as verified.
Tools
Tool | Required arguments | Returns |
|
| Markdown text. Also writes a file when |
|
| JSON with |
|
| JSON summary. Capped at 50 |
|
| JSON summary. One flat |
|
| Quality and structure JSON. Writes nothing permanent. |
All five take a preset (default, clean, verbose, raw) and per-flag overrides. Full contract, including the MCP-only constraints and the error-text table: docs/reference/v4-runtime-contracts.md.
🗂 Project Structure
src/dead_letter/
├── core/ # conversion pipeline (MIME, HTML, threads, rendering)
├── backend/ # CLI, API server, job runner, watch mode, MCP server
└── frontend/ # static web UI (Alpine.js ES modules + vanilla fetch)
tests/
├── core/ # conversion pipeline tests with .eml fixtures
├── backend/ # API, job, and watch tests
├── plugin/ # plugin, skill, packaging, and release contracts
└── frontend/ # JS unit testsThe agent guide maps bundle, container, skill, and maintainer tooling without turning this README into a file inventory.
🧪 Testing
uv sync --extra dev --locked
uv run pytest -q tests/core # conversion pipeline
uv run pytest -q tests/backend # API and job runner
uv run pytest -q tests/plugin # plugin, skill, packaging, and release contracts
node --test tests/frontend/*.test.js
python scripts/release.py check # offline distribution metadataCI also validates plugin/skill schemas, frontend syntax, maintained Markdown links, and cross-platform packaging. Full commands and the distinction between offline contracts and real client tests are in Contributing.
📚 Docs
Docs Index — choose by task, not by filename
Distribution map — CLI, UI, MCPB, plugin, container, and skill choices
Agent install guide — client-specific setup and verification
Runtime Contracts — full API and core behavior spec
Publishing — tagging, channel order, and recovery
Agent Guide — operational guide for AI coding agents working in this repo
🔧 Tools We Love
MarkEdit — TextEdit for Markdown, native macOS, ~4 MB. Opens dead-letter output like it was always meant to live there.
mo — local Markdown viewer that renders files in the browser with live reload. Point it at your Cabinet and read converted mail like a feed.
⚠️ Known Limitations
CLI/Python accept
.eml, flat.mboxexports, and (from 0.4.5) ZIP/TGZ Takeout downloads; see the Takeout guide. The web UI is EML-only. From 0.4.5 the MCP server adds a boundedconvert_mboxtool for one flat.mbox. PST, MSG and live-mailbox connections are unsupported. An opt-in resumable MBOX import (--mbox-resume, CLI/Python only) is available from 0.4.6; see the resume contract.Local-only, single-user, single-machine; no remote server or authentication service. An MCP host may send results to its model provider.
In-memory job registry: state resets on restart. Retained binary attachments need a separate parser for text indexing.
License
PolyForm Noncommercial 1.0.0 — free for personal, educational, and nonprofit use. Commercial use requires a separate license from Big Cactus Labs.
Available Tools
5 toolsconvert_directoryConvert email folderA
Batch convert all .eml files in a directory to Markdown.
Recursively finds .eml files (at most 50 per call; larger directories are rejected before any conversion). output_directory is required: Markdown is written there, mirroring subfolders, and source .eml files are left in place. Returns a JSON summary with total, successes, failures, output_paths, and errors. Each errors entry contains file and error_code (defaulting to conversion_error), never raw exception text.
Use convert_eml to retrieve individual converted file content.
| Name | Required | Description | Default |
|---|---|---|---|
| preset | No | default | |
| dry_run | No | ||
| directory | Yes | ||
| thread_mode | No | latest | |
| thread_order | No | oldest-first | |
| include_raw_html | No | ||
| output_directory | Yes | ||
| strip_signatures | No | ||
| strip_disclaimers | No | ||
| embed_inline_images | No | ||
| include_all_headers | No | ||
| no_calendar_summary | No | ||
| strip_quoted_headers | No | ||
| strip_tracking_pixels | No | ||
| strip_signature_images | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Adds substantial behavior beyond the annotations: the 50-file cap with pre-conversion rejection, that source .eml files are left in place, that subfolders are mirrored into output_directory, and that errors carry a file plus a sanitized error_code rather than raw exception text. That is exactly the operational context an agent needs before spending a large batch run.
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?
Front-loads the core action, then constraints, then output contract, then the sibling pointer — each sentence carries distinct information with no filler. Length is proportionate to a 15-parameter batch tool.
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?
The output schema exists, and the description still usefully summarizes the JSON summary shape, so return values are covered. However, for a tool with 15 options and an enum-based preset system, the complete absence of guidance on how the conversion options interact (preset vs individual strip_* flags) leaves a real gap.
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 0% across 15 parameters, so the description carries the full burden — but it only explains output_directory (required, receives Markdown, mirrors subfolders). The 12 conversion flags and 3 enums (preset, thread_mode, thread_order) are entirely undocumented, so an agent cannot tell what 'clean' vs 'verbose' preset or 'structured' thread_mode does.
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?
Opens with a specific verb and resource: 'Batch convert all .eml files in a directory to Markdown,' which immediately separates it from the single-file convert_eml. It does not explicitly contrast with convert_mbox or convert_eml_to_bundle, so sibling differentiation is only partial.
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?
Gives concrete usage context (recursive .eml discovery, hard cap of 50 files with upfront rejection) and routes the agent to convert_eml for reading back individual converted content. It stops short of stating when-not to use it, e.g. for mbox archives, which is left to inference from sibling names.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
convert_emlConvert emailA
Convert a .eml email file to Markdown with YAML front matter.
Returns the full Markdown content (front matter + body). When output_path is provided, also writes the file to disk.
Attachments are listed in the front matter but not written to disk; use convert_eml_to_bundle to save the decoded attachment files.
thread_mode="structured" adds a section per earlier reply or forwarded message. Gmail HTML forwards and unquoted plain-text forwards are kept in latest mode too; a plain-text forward inside ">" quoting is not.
Presets bundle common flag combinations:
default: strips signatures, tracking pixels, signature images
clean: default + strips disclaimers and quoted headers
verbose: includes all headers and raw HTML
raw: no stripping, preserves everything
Individual flags override the preset when provided.
| Name | Required | Description | Default |
|---|---|---|---|
| preset | No | default | |
| eml_path | Yes | ||
| output_path | No | ||
| thread_mode | No | latest | |
| thread_order | No | oldest-first | |
| include_raw_html | No | ||
| strip_signatures | No | ||
| strip_disclaimers | No | ||
| embed_inline_images | No | ||
| include_all_headers | No | ||
| no_calendar_summary | No | ||
| strip_quoted_headers | No | ||
| strip_tracking_pixels | No | ||
| strip_signature_images | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well beyond the annotations: it discloses the file-writing side effect of output_path, that attachments are listed but not written, the per-preset stripping behavior, and that individual flags override presets. The described write behavior is consistent with readOnlyHint=false and destructiveHint=false.
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?
Front-loaded with the primary behavior, then organized into short paragraphs and a bulleted preset list that is genuinely useful given the empty schema descriptions. Slightly verbose on the forward-quoting edge case, but nothing extraneous.
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?
Covers the entity, the output shape, the disk side effect, and the preset/flag interaction, and an output schema exists so return-value detail is not owed. The remaining gap is semantics for the unlisted boolean flags in a 14-parameter 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 description coverage is 0%, so the description carries the param burden and it partially does: all four preset values and both thread_mode values are given meaning, and output_path behavior is stated. However, thread_order, no_calendar_summary, embed_inline_images, and several strip_* flags are never characterized, leaving half the surface to title-only inference.
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 plus the exact output format ('Convert a .eml email file to Markdown with YAML front matter'). It also names the sibling convert_eml_to_bundle and the distinct condition that selects it, so an agent can separate the two without reading schemas.
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 routes attachment-saving use cases to convert_eml_to_bundle and explains the thread_mode and preset choices in operational terms. It does not address when convert_directory or convert_mbox should be preferred over this single-file tool, leaving the batch-vs-single decision implied.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
convert_eml_to_bundleConvert email to bundleA
Convert a .eml file to a self-contained bundle with markdown and attachments.
Creates a directory containing the converted markdown, extracted attachments, and optionally the original .eml source.
source_handling only accepts 'copy' over MCP: the original .eml is copied into the bundle and left untouched. The 'move' and 'delete' modes are rejected here — use the CLI or the Python API for those.
Returns JSON with bundle_path, markdown_path, attachment_paths, and optional diagnostics.
| Name | Required | Description | Default |
|---|---|---|---|
| preset | No | default | |
| eml_path | Yes | ||
| bundle_root | Yes | ||
| thread_mode | No | latest | |
| thread_order | No | oldest-first | |
| source_handling | No | copy | |
| include_raw_html | No | ||
| strip_signatures | No | ||
| strip_disclaimers | No | ||
| embed_inline_images | No | ||
| include_all_headers | No | ||
| no_calendar_summary | No | ||
| strip_quoted_headers | No | ||
| strip_tracking_pixels | No | ||
| strip_signature_images | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare destructiveHint=false and readOnlyHint=false, and the description usefully adds that the original .eml is copied and left untouched, plus that unsupported modes are rejected rather than silently downgraded. It lists the returned fields, giving concrete expectations. No contradiction with annotations; only missing detail is failure behavior (e.g., existing bundle directory).
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 output artifact is described first, then the source_handling caveat, then the return shape — a sensible front-loaded order. Paragraph breaks are used well. Minor redundancy: the return JSON is listed despite an output schema existing.
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?
An output schema exists, so the return-value recap is not strictly needed, and the description covers artifact creation and the source-preservation contract. But with 15 parameters at 0% schema coverage and no mention of the preset/thread options that materially change output, an agent cannot choose parameters confidently from this definition alone.
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 0% across 15 parameters, and the description documents only source_handling. The four enums (preset, thread_mode, thread_order, source_handling) and the ten boolean knobs (strip_signatures, embed_inline_images, include_all_headers, etc.) are left entirely to name inference, so the description does not compensate for the coverage gap.
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 and resource — convert a .eml file into a self-contained bundle of markdown plus extracted attachments — and clarifies what the output artifact is. It does not explicitly distinguish itself from sibling convert_eml, so the caller must infer the difference from the 'bundle' vs. single-conversion wording.
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 clearly states the MCP-level constraint: source_handling only accepts 'copy' here, and 'move'/'delete' must go through the CLI or Python API. That is a genuine when/when-not statement. It stops short of routing between this tool and convert_eml/convert_directory/convert_mbox, which is the remaining gap.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
convert_mboxConvert MBOX archiveA
Convert one flat .mbox archive (e.g. Gmail Takeout) to Markdown files.
Bounded: the archive must be at most 256 MiB, and conversion stops after 1000 messages (the response then has truncated=true; use the dead-letter CLI for larger archives). Compressed archives and Apple Mail .mbox directories are rejected. output_directory is required; one .md per message (or one bundle directory when bundles=true) is written there with a collision-safe JSON report. The source archive is never modified.
Returns a JSON summary (processed, converted, skipped, failed, truncated, report_path, and at most 20 failure entries), never message content. Cancellation is not supported; byte/message caps are not a time limit.
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | ||
| preset | No | default | |
| bundles | No | ||
| dry_run | No | ||
| thread_mode | No | latest | |
| thread_order | No | oldest-first | |
| include_raw_html | No | ||
| output_directory | Yes | ||
| strip_signatures | No | ||
| strip_disclaimers | No | ||
| embed_inline_images | No | ||
| include_all_headers | No | ||
| no_calendar_summary | No | ||
| strip_quoted_headers | No | ||
| strip_tracking_pixels | No | ||
| strip_signature_images | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Adds substantial context beyond annotations: hard 256 MiB and 1000-message caps, the truncated=true signal, the requirement that output_directory be supplied, one .md per message vs bundle directories, a collision-safe report, and the guarantee that the source archive is never modified. It also clarifies negative behavior (no cancellation, caps are not a time limit), which is exactly the kind of trap an agent would otherwise fall into.
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?
Front-loads the core purpose in the first sentence, then groups constraints, output behavior, and return shape into short paragraphs. Dense but every sentence carries information an agent needs; no filler or restatement of the name.
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?
Limits, rejection cases, side effects, and return shape are all covered, and an output schema exists so return values need no further explanation. The gap is the undocumented parameter surface: an agent can call this correctly with defaults, but cannot make informed use of the 14 optional formatting toggles.
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 0% across 16 parameters, so the description carries the full burden, yet it only touches output_directory (already marked required in schema) and bundles=true. Fourteen parameters — preset, dry_run, thread_mode, thread_order, include_raw_html, embed_inline_images, no_calendar_summary, and all the strip_* toggles — get no explanation of what they change or when to set them.
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 ('Convert one flat .mbox archive ... to Markdown files') with its scope ('one flat', example Gmail Takeout). The distinction from siblings is implicit but strong: convert_directory handles directories, which the description explicitly rejects, and the format (.mbox vs .eml) separates it from convert_eml.
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?
Gives explicit when-not conditions (compressed archives and Apple Mail .mbox directories are rejected) and a named alternative for out-of-scope work ('use the dead-letter CLI for larger archives'). It does not explicitly route between convert_eml/convert_directory, but the input-format constraint makes the choice unambiguous.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_diagnosticsGet conversion diagnosticsARead-only
Inspect email quality and structure without writing permanent files.
Use this to assess conversion quality before committing, or to troubleshoot problematic .eml files.
Always returns JSON with: state (normal/degraded/review_recommended), selected_body, segmentation_path, client_hint, confidence, fallback_used, and warnings. Two keys are conditional: stripped_images appears only when images were removed, and attachments only when the message had attachments eligible for retention.
| Name | Required | Description | Default |
|---|---|---|---|
| preset | No | default | |
| eml_path | Yes | ||
| thread_mode | No | latest | |
| thread_order | No | oldest-first | |
| include_raw_html | No | ||
| strip_signatures | No | ||
| strip_disclaimers | No | ||
| embed_inline_images | No | ||
| include_all_headers | No | ||
| no_calendar_summary | No | ||
| strip_quoted_headers | No | ||
| strip_tracking_pixels | No | ||
| strip_signature_images | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Consistent with readOnlyHint=true and openWorldHint=false, and adds value beyond them by stating no permanent files are written and enumerating the return-state vocabulary (normal/degraded/review_recommended) plus conditional keys. Falls short of disclosing limits, cost, or when 'degraded' should trigger a re-run.
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?
Front-loaded with purpose, then usage, then return contract; no filler. The long enumeration of return keys is slightly heavy given an output schema exists, but it is organized and readable.
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?
Return values are well covered even though an output schema exists, and safety context is adequate. However, for a tool with 13 parameters and zero schema descriptions, omitting any parameter guidance leaves a genuine gap an agent would hit when deciding preset/thread/strip behavior.
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?
Thirteen parameters with 0% schema description coverage, and the description explains none of them — preset, thread_mode, thread_order, and the eight strip_*/include_* flags are opaque. The only indirect hint is stripped_images appearing conditionally when images were removed, which barely signals the stripping flags.
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 ('Inspect email quality and structure') and differentiates from the convert_* siblings by noting it does not write permanent files. A clear read-only inspection tool, though it doesn't name the siblings 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?
Gives two concrete situations: assess conversion quality before committing, or troubleshoot problematic .eml files. This implicitly routes agents away from convert_eml when they only want inspection, but no alternative is named outright.
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.
2 tool updates
v0.4.5- Changed
convert_directory4 fields changed- removed
Input schema / properties / output_directory / anyOfRemoved value: -[ - { - "type": "string" - }, - { - "type": "null" - } -] - removed
Input schema / properties / output_directory / defaultRemoved value: -null - added
Input schema / properties / output_directory / typeAdded value: +"string" - changed
Input schema / requiredPrevious value: -[ - "directory" -]New value: +[ + "directory", + "output_directory" +]
- Added
convert_mbox
4 tool updates
v0.2.4- First observed
convert_directory - First observed
convert_eml - First observed
convert_eml_to_bundle - First observed
get_diagnostics
TDQS
Scored across 5 tools
convert_eml and convert_eml_to_bundle are closely related but differentiated by bundle/attachment output, while convert_directory and convert_mbox are separated by input type. get_diagnostics is clearly distinct, leaving only minor ambiguity between single-file conversion variants.
All tool names use consistent snake_case and are verb-led, with the convert_* family forming a predictable group and get_diagnostics fitting naturally as a read-only operation. The extra preposition in convert_eml_to_bundle is still clear and consistent.
Five tools form a well-scoped surface for email conversion: single-file, bundle, directory batch, mbox batch, and diagnostics. Each tool has a clear reason to exist without obvious bloat.
The surface covers the core conversion workflows, including attachments via bundles and quality inspection via diagnostics. Minor gaps remain around reading back already-converted outputs, and some archive modes are deliberately rejected and deferred to the CLI/API.
Maintenance
Related MCP Connectors
Document-to-Markdown MCP server — convert PDF, Office and HTML into LLM-ready Markdown.
Email infrastructure for AI agents — send, receive, search, and reply to email over MCP.
Hosted MCP server: convert PDFs to clean, LLM-ready Markdown with tables, formulas and OCR.
Document conversion MCP server: PDF to Markdown, image OCR, spreadsheet parsing.
Related MCP Servers
- AlicenseBqualityDmaintenanceA local MCP server that provides LLM clients with read/write access to email and calendar data from Gmail, iCloud, and generic IMAP providers. It runs entirely on your machine, keeping data private while enabling email management, calendar operations, and task handling through natural language.391MIT
- AlicenseAqualityDmaintenanceMCP server for parsing .eml email files, extracting metadata, content, and attachments with smart organization into folders. Enables AI to read and handle email files offline without triggering trackers.22AGPL 3.0
- FlicenseNot gradedqualityBmaintenanceA private, single-user MCP server that unifies Gmail, Microsoft 365/Outlook, and IMAP mailboxes for LLMs to search and read emails live, without storing or caching mailbox contents.-
- AlicenseCqualityDmaintenanceA local-first Python MCP server that turns Gmail, Outlook/Microsoft 365, iCloud Mail, and generic IMAP/SMTP mailboxes into a synchronized, searchable OKF knowledge layer, exposing 38 tools and four resources for mailbox actions, synchronization, retrieval, attachments, and optional semantic search while keeping the provider mailbox authoritative.381MIT