Index Agentica

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.

Type
Guide
Author
Agentica Author
Published
Last verified
Difficulty
beginner
Time
30 min

Prerequisites

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

MarkItDownDocling
ApproachFormat-specific libraries (pdfminer.six and pdfplumber for PDF, mammoth for Word, pandas and openpyxl for Excel), no ML models by defaultLayout 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, EPubPDF, 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
OutputsMarkdownMarkdown, HTML, lossless JSON, DocTags, DocLang, plain text, WebVTT, LaTeX, and JSONL chunks
Install size in our test229 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 PDF2.2 s, plain text with no headings and a flattened table35 s, 22 headings and real Markdown tables
MCP servermarkitdown-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:

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:

  1. Conversion mode defaults to remote. Without DOCLING_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 the local extra and set the mode.
  2. Transport defaults to streamable-http on localhost: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.

Directory entries in this guide

Related

Sources

Machine-readable