Convert documents to Markdown for agents with MarkItDown and Docling
Turn PDFs, Office files and other documents into Markdown or chunks an agent can read, with Microsoft's MarkItDown for speed and Docling for layout and tables, then expose either one to an agent over MCP.
Prerequisites
- Python 3.10 or later (MarkItDown supports 3.10 to 3.14), and uv or pip
- About 2 GB of free disk for Docling, its CPU PyTorch build and the models it downloads
- Node.js, only for the MCP Inspector checks
Agents read text. Most of what people hand them is not: PDFs, Word files, slide decks, spreadsheets, scans. A converter in front of the agent turns those into Markdown, which keeps headings, lists and tables in a form models handle well and that costs few tokens. MarkItDown (Microsoft) and Docling (started at IBM Research, now an LF AI & Data project) are the two open-source converters with MIT licenses and their own MCP servers.
What was run for this guide, on 2026-10-18, on a Linux box with 8 vCPUs and no GPU: MarkItDown 0.1.8 and Docling 2.140.0 on the 9-page Docling Technical Report PDF and on a small .xlsx and .docx; Docling's chunk export; and the MCP Inspector CLI against markitdown-mcp 0.0.1a7 and docling-mcp 3.3.0. Timings below are from that one machine and one file, so treat them as a rough guide.
Which one to use
| MarkItDown | Docling | |
|---|---|---|
| Approach | Format-specific libraries (pdfminer.six and pdfplumber for PDF, mammoth for Word, pandas and openpyxl for Excel), no ML models by default | Layout analysis, reading order, table-structure and OCR models; optional vision-language models |
| Formats (from each README) | PDF, PowerPoint, Word, Excel, images (EXIF and OCR), audio (transcription), HTML, CSV/JSON/XML, ZIP, YouTube URLs, EPub | PDF, DOCX/XLSX/PPTX, legacy Office and RTF (via LibreOffice), ODF, EPUB, Apple iWork, HTML, LaTeX, images, audio and video (with the asr extra), email, and more |
| Outputs | Markdown | Markdown, HTML, lossless JSON, DocTags, DocLang, plain text, WebVTT, LaTeX, and JSONL chunks |
| Install size in our test | 229 MB virtualenv with [all] | 1.5 GB virtualenv with CPU PyTorch, plus about 0.5 GB of models downloaded on first run |
| Our 9-page PDF | 2.2 s, plain text with no headings and a flattened table | 35 s, 22 headings and real Markdown tables |
| MCP server | markitdown-mcp: one tool, convert_to_markdown(uri) | docling-mcp: 21 tools for converting, editing and exporting documents |
In short: use MarkItDown for Office files, HTML and quick text out of born-digital PDFs. Use Docling when PDF layout matters, meaning multi-column pages, tables, scans, or chunks headed for a vector database. For web pages, a URL-to-Markdown service such as Jina Reader, Firecrawl or Crawl4AI is the better fit; the research agent stack covers that side.
1. MarkItDown
Install it in a virtual environment. [all] pulls in every optional converter; you can also pick extras such as [pdf, docx, pptx].
uv venv --python 3.12 .venv && source .venv/bin/activate
uv pip install 'markitdown[all]'
markitdown report.pdf -o report.md # or: markitdown report.pdf > report.md
cat report.pdf | markitdown # stdin works too
From Python:
from markitdown import MarkItDown
md = MarkItDown(enable_plugins=False)
result = md.convert_local("pricing.xlsx") # local files only; see Security below
print(result.markdown)
On Office files the output was clean. A one-sheet workbook came back as a heading per sheet plus a Markdown table:
## Pricing
| Plan | Price per month (USD) | Seats |
| --- | --- | --- |
| Free | 0 | 1 |
| Team | 20 | 5 |
On the PDF it was fast (2.2 seconds) but shallow. The output had no Markdown headings. Words in justified lines often ran together ("state-of-the-artspecializedAImodels"), and the report's runtime table became loose lines of numbers. Pages that reproduce another paper's layout turned into wide pseudo-tables of letter fragments. That is what text-layer extraction gives you, and the README says as much: the output is meant for text-analysis tools, not high-fidelity conversion.
For better PDF and scan handling, MarkItDown can hand conversion to Azure Document Intelligence (-d -e <endpoint>) or Azure Content Understanding (--use-cu). Both are billable Azure API calls. The markitdown-ocr plugin adds OCR of embedded images through an LLM vision client you pass in. If you pass no client, the plugin loads but silently skips OCR.
2. Docling
uv venv --python 3.12 .venv-docling && source .venv-docling/bin/activate
uv pip install docling # on CPU-only Linux, add the PyTorch CPU index to skip CUDA wheels:
# uv pip install docling --extra-index-url https://download.pytorch.org/whl/cpu --index-strategy unsafe-best-match
docling report.pdf --image-export-mode placeholder --output out/
Two defaults to know about:
- Images. With the CLI defaults, our Markdown file was 581 KB, because the figures were embedded as base64 data URIs.
--image-export-mode placeholderreplaces each one with<!-- image -->and brought the file down to 34 KB. Usereferencedto write PNG files and link to them instead. - First run. The first conversion downloads the layout and table models to the Hugging Face cache and the RapidOCR models into the package. That run took 60 seconds; later runs of the same file took about 35 seconds wall time, of which Docling reported 29 seconds converting.
The output had real structure: ## 1 Introduction-style headings, proper paragraphs, and Table 1 as a Markdown table whose column headers are joined from the original's two header rows (native backend - TTS, pypdfium backend - Mem). Rows that share cells in the original were merged into one Markdown row, so check complex tables before you trust a number in them.
OCR is on by default. --no-ocr cut the run to 8 seconds of conversion, but the Markdown was 33 lines shorter: it lost part of the reproduced DocLayNet paper pages at the end of the report, including that paper's Table 2. Only switch OCR off after you've compared the output on your own documents.
From Python:
from docling.document_converter import DocumentConverter
result = DocumentConverter().convert("report.pdf") # a local path or a URL
print(result.document.export_to_markdown())
Docling also has a VLM pipeline (docling --pipeline vlm --vlm-model granite_docling <file>) and integrations with LlamaIndex, Haystack, LangChain and CrewAI. To run it as a service, docling-serve provides an HTTP API on port 5001 (pip install docling-serve then docling-serve run, or the docling-serve-cpu container image, listed at 4.4 GB).
3. Chunks for retrieval
If the text is going into a vector store, chunk it with Docling rather than splitting the Markdown yourself. Its chunkers work on the document structure, so each chunk carries its headings, captions and page numbers:
docling report.pdf --to chunks --output chunks/
That wrote one JSONL file with 48 chunks for our PDF. Each line has text (the chunk with its heading prepended, ready to embed), raw_text, num_tokens, headings, captions, page_numbers and the origin file. The default is the HybridChunker, which starts from the document hierarchy, splits chunks that are too long for the tokenizer and merges small neighbours that share headings. When a table spans chunks, it repeats the table header by default. Set --chunks-tokenizer (a Hugging Face model id) and --chunks-max-tokens to match your embedding model. The first chunking run downloaded the sentence-transformers/all-MiniLM-L6-v2 tokenizer as the default.
Load those lines into any store from the vector database comparison, and keep page_numbers in the metadata so the agent can cite where an answer came from. Give an agent long-term memory covers the memory side.
4. Give the converter to an agent over MCP
Both projects ship an MCP server, so an agent can convert files on demand instead of you running a pipeline first (background: Connect an agent to a remote MCP server).
markitdown-mcp
uv pip install markitdown-mcp
markitdown-mcp # stdio, the default
markitdown-mcp --http --host 127.0.0.1 --port 3001 # Streamable HTTP at /mcp, SSE at /sse
{
"mcpServers": {
"markitdown": { "command": "markitdown-mcp" }
}
}
It exposes one tool, convert_to_markdown, taking an http:, https:, file: or data: URI. Checked with the Inspector:
npx @modelcontextprotocol/inspector --cli markitdown-mcp \
--method tools/call --tool-name convert_to_markdown --tool-arg uri=file:///path/to/pricing.xlsx
It returned the same table as the CLI. The README recommends its Docker image for Claude Desktop, mounting only the directory you want converted (-v /home/user/data:/workdir).
docling-mcp
The client snippet in Docling's docs is uvx --from=docling-mcp docling-mcp-server. With docling-mcp 3.3.0 that is not enough, for two reasons we hit:
- Conversion mode defaults to
remote. WithoutDOCLING_MCP_SERVICE_URL, every conversion failed with "DOCLING_MCP_SERVICE_URL is not set but DOCLING_MCP_CONVERSION_MODE=remote". To convert on your own machine, install thelocalextra and set the mode. - Transport defaults to
streamable-httponlocalhost:8000. A client that launches the command and expects stdio needs--transport stdio.
This configuration converted our PDF (the first call took about 40 seconds, including server start-up):
{
"mcpServers": {
"docling": {
"command": "uvx",
"args": ["--from", "docling-mcp[local]", "docling-mcp-server", "--transport", "stdio"],
"env": { "DOCLING_MCP_CONVERSION_MODE": "local" }
}
}
}
The server keeps converted documents in an in-memory cache. convert_document_into_docling_document returns a document_key, and the agent then calls export_docling_document_to_markdown (with an optional max_size in characters) or the anchor tools (get_overview_of_document_anchors, search_for_text_in_document_anchors, get_text_of_document_item_at_anchor) to read parts of a long document instead of all of it. The other tools create and edit documents. Load fewer tool groups with the positional argument (conversion, generation, manipulation, plus optional RAG groups for LlamaIndex and Llama Stack). For a shared deployment, run docling-serve once and point each docling-mcp at it in remote mode with DOCLING_MCP_SERVICE_URL and DOCLING_MCP_SERVICE_API_KEY.
5. Security
Both converters read whatever their process can read.
- MarkItDown's README warns that
convert()accepts local paths, remote URIs and streams, so untrusted input can make it read local files or fetch internal URLs. It recommends validating input, blocking private, loopback, link-local and metadata-service addresses, and calling the narrowest method you need (convert_local(),convert_response()orconvert_stream()). - markitdown-mcp has no authentication and binds to localhost in HTTP mode. Its README says the tool can read any file the server's user can access. In our test,
convert_to_markdownwithfile:///etc/hostnamereturned the file. Run it in a container or VM with only the documents mounted, and do not bind it to other interfaces. - docling-mcp takes local file paths and URLs too, so the same advice applies.
- Converted text is untrusted input. A PDF can carry instructions aimed at the agent that reads it. Keep converted content separate from instructions, and keep risky tools behind approval. Run untrusted code in a sandbox covers isolation.
Directory entries in this guide
- MarkItDown: Microsoft's lightweight Python utility for converting PDF, Office, images, audio, HTML and more into structure-preserving Markdown for LLMs.
- Docling: Open-source document parser that converts PDF, Office, HTML, audio, images and more into Markdown, JSON or DocTags for gen-AI pipelines; includes an MCP server.
- MCP Inspector: Official interactive developer tool for testing and debugging MCP servers in the browser, on the command line or in the terminal.
- Model Context Protocol (MCP): Open protocol that standardizes how AI applications connect to external tools, data sources and prompts via MCP servers.
- Jina Reader: Converts any URL into LLM-friendly Markdown by prefixing it with https://r.jina.ai/.
- Firecrawl API: Web data API for AI agents: search the web, scrape any page to clean data and interact with it through one API.
- Crawl4AI: Open-source LLM-friendly web crawler and scraper that turns any website into clean Markdown; self-host or use the hosted cloud.
- LlamaIndex: Document-processing platform for AI (open-source LlamaIndex library plus hosted LlamaParse APIs that turn PDFs, scans and Office files into markdown or JSON).
- Haystack: deepset's open-source orchestration framework for production-ready, context-engineered LLM applications and agents.
- CrewAI: Python framework for orchestrating role-playing, autonomous AI agents that collaborate as crews and flows.
Related
- Vector databases for agents compared (Comparison)
- Give an agent long-term memory (Guide)
- Research agent stack (Stack)
- Web search APIs for agents (Comparison)
- Connect an agent to a remote MCP server (Guide)
- Run agent-generated code safely in a sandbox (Guide)
- Data analysis agent stack (Stack)
Sources
- MarkItDown README, accessed
- markitdown-mcp README, accessed
- MarkItDown pyproject.toml (optional dependencies), accessed
- markitdown on PyPI, accessed
- markitdown-mcp on PyPI, accessed
- Docling README, accessed
- Docling, Supported formats, accessed
- Docling, Chunking, accessed
- Docling, MCP, accessed
- docling-mcp README, accessed
- docling-serve README, accessed
- docling on PyPI, accessed
- docling-mcp on PyPI, accessed
- Docling Technical Report (arXiv 2408.09869), the test PDF, accessed