Skip to main content
Glama

anymd

Convert any document to LLM-friendly Markdown.

A CLI, Python library, and MCP server built for AI agents.

English | 简体中文

CI PyPI Python License: MIT Code style: ruff

$ anymd convert report.docx
# Quarterly Report
...

Why

Agents drown in formats. anymd turns every real-world document type into clean, structured Markdown with predictable conventions, so an LLM can read it without format-specific prompting:

  • every table has a header row and --- separator; | escaped, cell newlines as <br>

  • truncation is always stated in the output, never silent

  • structure markers (<!-- page 3 of 12 -->) are HTML comments — greppable, invisible when rendered

  • exit codes, stderr/stdout separation, and an NDJSON manifest designed for programmatic consumption

  • robust to mislabelled files: a .docx renamed .doc is still identified by content sniffing

  • encoding-safe out of the box: UTF-8 / UTF-16 BOM / GB18030 / Big5 / HTML <meta charset> detection, built for Chinese-Windows realities

Related MCP server: Markitdown MCP Server

Features

  • 24 converters covering Office, PDF, e-mail, e-books, images, archives, data files and code

  • Optional extras: missing dependencies fail with the exact pip install "anymd[x]" command, never a raw ImportError

  • OCR built in (optional extra): RapidOCR transcribes images and scanned PDF pages, models ship in-wheel — nothing downloaded at runtime

  • Safe with hostile input: zip/tar decompression-bomb caps, SSRF guard on URLs, size caps everywhere

  • Agent-first CLI: --json NDJSON manifest, strict stdout/stderr separation, meaningful exit codes

  • Three ways in: CLI, Python API, and an MCP server

Installation

pip install anymd            # core: text, code, csv/json/yaml/xml, html, eml, epub, markdown
pip install "anymd[all]"     # everything below, one shot

extra

adds

formats unlocked

pdf

pymupdf, pymupdf4llm

.pdf (headings, tables, page markers)

office

python-docx, openpyxl, xlrd, python-pptx, odfpy, striprtf

.docx .xlsx .xls .pptx .odt .ods .odp .rtf

ocr

rapidocr[onnxruntime], pillow

text recognition in images + scanned PDF pages

email

extract-msg

.msg (Outlook); .eml is already core

web

httpx

converting http(s)://… URLs directly

mcp

mcp

the anymd-mcp MCP server

Requires Python ≥ 3.11. Windows, macOS and Linux are all supported — the CLI is Windows-first (UTF-8 output on cp936 consoles).

Usage

CLI

anymd convert <INPUT...> [-o PATH] [options]

INPUT   file, glob, directory, http(s) URL, or - (stdin)
anymd convert report.pdf                      # → stdout
anymd convert report.docx -o out.md           # → file
anymd convert ./docs -o md/                   # → directory tree → .md files
anymd convert "*.xlsx" --json                 # → NDJSON manifest
anymd convert scan.pdf --image-mode ocr       # OCR scanned pages (needs [ocr])
anymd convert data.csv --max-rows 50          # cap table rows
anymd convert - --stdin-filename msg.eml      # stdin with a filename hint
anymd convert report.pdf --page-range 3-7     # partial PDF
anymd --list-formats                          # everything supported + required extras

Agent contract

  • stdout carries only the product. Progress, warnings and diagnostics go to stderr.

  • Exit codes: 0 all converted · 1 partial failure · 2 usage error · 3 zero output (missing/unsupported inputs). A file you name explicitly that has no converter is an error (exit 1); the same file swept up by a glob or directory walk is skipped (exit 0). --strict promotes those skips to errors too.

  • --json emits one JSON object per line, each with a type field so an agent can stream-parse:

{"type":"item","input":"a.docx","output":"a.md","status":"ok","converter":"docx","duration_ms":42,"bytes_in":12345,"bytes_out":678,"sha256_in":"…","warnings":[]}
{"type":"item","input":"legacy.doc","output":null,"status":"error","error":{"code":"UnsupportedFormatError","message":"legacy binary Office formats (.doc/.ppt) require LibreOffice…","hint":"run anymd --list-formats to see supported types"}}
{"type":"summary","total":2,"ok":1,"error":1,"skipped":0,"bytes_in":9000,"bytes_out":678,"duration_ms":120,"exit_code":1}

Python API

from anymd import convert, convert_result, list_formats, ConvertContext

md = convert("report.docx")
result = convert_result("report.xlsx")     # .metadata (sheets, pages, …) + .warnings
md = convert("https://example.com/post",   # URL (needs anymd[web])
             ctx=ConvertContext(frontmatter=True))

MCP server

pip install "anymd[mcp]"
anymd-mcp        # stdio transport

Two tools for any MCP client:

  • convert_document(source, max_rows, image_mode, frontmatter, max_chars, include_metadata, allow_remote) — one document → Markdown. Missing extras return an actionable ERROR[...] string instead of a protocol error; output is truncated at max_chars with a visible note.

  • list_formats() — every supported extension and its extra.

Claude Desktop / Claude Code registration:

{"mcpServers": {"anymd": {"command": "anymd-mcp"}}}

Supported formats

family

formats

Office

.docx .xlsx .xlsm .xls .pptx .odt .ods .odp .rtf

PDF

.pdf (structure-aware; scanned or image-heavy pages → OCR when [ocr] installed)

Web

.html .htm .xhtml .mht/.mhtml, http(s) URLs

E-books

.epub (spine-ordered)

Data

.csv .tsv → tables · .json .jsonl → tables or fenced · .yaml .yml .toml .xml .ini .cfg .conf .env .properties → fenced

Email

.eml (headers + body + attachment listing) · .msg

Notebooks

.ipynb (cells in order, outputs truncated)

Images

.png .jpg .jpeg .gif .bmp .tif .tiff .webp (OCR or metadata)

Archives

.zip .tar .tar.gz .tgz .tar.bz2 .tar.xz — members converted recursively

Text/code

.txt .log .md and ~60 code extensions → language-tagged fences

No extension

content sniffing: PDF/zip-family/OLE/RTF/HTML/images/plain text

Not supported (by design): legacy binary .doc / .ppt have no pip-installable reader — anymd fails with a precise message telling you to re-save as .docx/.pptx. (.xls is supported via xlrd.)

Notes

  • License: anymd is MIT. Two optional extras carry copyleft licenses that activate only for their own code paths: pymupdf (AGPL-3.0, the pdf extra) and striprtf (GPL-3.0, inside the office extra). They are separate packages installed at runtime — if you deploy anymd as a hosted service, review your obligations (a pdfminer.six-based fallback can replace the pdf extra).

  • OCR engine: RapidOCR bundles its models in-wheel; nothing is downloaded at runtime. If model files fail to load in a restricted environment, OCR degrades to a warning, never an error.

  • Windows-first: the CLI reconfigures stdout to UTF-8 so piping Chinese text through cp936 consoles never raises UnicodeEncodeError.

Development

git clone https://github.com/Ljf857/anymd.git
cd anymd
pip install -e ".[all,dev]"

pytest                 # slow OCR tests excluded by default
pytest -m slow         # OCR end-to-end
ruff check src tests
mypy src/anymd

Contributions are welcome — open an issue or pull request.

License

MIT © 2026 Ljf857

Optional extras keep their own licenses (see Notes).

Related MCP Connectors

Related MCP Servers