
opendockit
io.github.hyeonsang010716/opendockit
Read local PDF, Office, HWP, OpenDocument, RTF, EPUB, HTML and email files as Markdown. Offline.
Documentation
Turn any document into clean, LLM-ready Markdown.
PDF · DOCX · PPTX · XLSX · HWP/HWPX · ODF · RTF · EPUB · HTML · email · images — one function, no GPU, MIT licensed.
from opendockit import convert_file
result = convert_file("quarterly-report.hwp")
result.text # Markdown: headings, tables, lists, footnotes, LaTeX equations
result.sections # per page / slide / sheet — for chunking and citations ("page 3")
result.warnings # what was skipped or approximated, in plain sentences
Why OpenDocKit
Most converters handle the happy path. OpenDocKit is built for the files people actually upload.
- Structure, not just text. Merged table cells, multi-column PDFs, borderless tables, nested lists, footnotes, speaker notes, native charts as data tables, SmartArt as nested lists, Word / PowerPoint / Hancom equations as LaTeX, links and bold / italic as Markdown.
- First-class Korean documents. A from-scratch HWP 5.x parser, HWPX, CP949 detection, NFC normalization, and the form-heavy tables Korean offices produce. Most general-purpose converters cannot read these at all.
- Detects by content, not extension. An HTML table saved as
.xls, a CSV with a title block, an MHTML page named.doc— each is read as what it really is, and a warning tells you. - Safe on untrusted uploads. Zip bombs, runaway merged-cell counts, recursive PDF objects and multi-gigabyte images
are bounded.
IsolatedConverterruns every file in a disposable worker process with memory and time limits. - Images in place. Pictures, charts and scanned pages are extracted, filtered, and — if you plug in a vision model — described right where they appeared in the document.
- A hard character budget.
max_charsstops work instead of truncating afterwards, so a file that will be rejected anyway costs neither parsing time nor vision-model calls. - Permissive by default. MIT licensed; everything a plain
pip install opendockitbrings in is MIT / BSD / Apache-style (no PyMuPDF, no pyhwp), with no ML models and no GPU. The only copyleft code is behind the opt-inheicextra.
Installation
pip install opendockit # conversion only — never touches the network
pip install "opendockit[openai]" # + image description with a vision model
pip install "opendockit[heic]" # + HEIC/HEIF images (pulls in GPL/LGPL-licensed codec libraries)
pip install "opendockit[langchain]" # + LangChain loader
pip install "opendockit[llamaindex]" # + LlamaIndex reader
pip install "opendockit[mcp]" # + MCP server for agents
Requires Python 3.12 or newer on Linux or macOS. Windows is not supported yet for HTML, email and EPUB (why).
Optional: install LibreOffice (brew install --cask libreoffice, apt install libreoffice)
to read legacy .doc and .ppt files. Every other format is read by OpenDocKit itself.
Quick start
Convert
from opendockit import convert, convert_file
result = convert_file("report.pdf") # from a path
result = convert(data, "report.pdf") # from bytes (the filename is only a hint)
print(result.text)
print(result.char_count, result.exceeded) # size of the output, and whether the limit stopped it
convert is synchronous, offline and deterministic (the one exception: .doc and .ppt start a local LibreOffice
process). Pictures are collected in result.images and the text keeps an
[Image] placeholder where each one was.
Describe images with a vision model
from opendockit import OpenAIImageDescriber, aconvert
result = await aconvert(data, "report.pdf", describer=OpenAIImageDescriber()) # reads OPENAI_API_KEY
Charts become data tables, diagrams become lists, scanned pages are transcribed, and decorative images are dropped.
OpenAIImageDescriber(model) accepts any LangChain chat model, and anything with an
async describe(image) -> str method works as a describer. See docs/images.md.
Serve it
from opendockit import ConversionError, IsolatedConverter
async with IsolatedConverter(workers=2) as converter: # 2 GB and 2 minutes per file by default
try:
result = await converter.convert(data, filename)
except ConversionError as exc:
print(exc.code, exc) # stable code + a message safe to show users
One bad file ends one worker; your server and the next upload carry on. In a script, run this under
if __name__ == "__main__": — the workers import the main module again. See docs/server.md.
Command line
opendockit report.pdf # Markdown on stdout, warnings on stderr
opendockit report.pdf --json # sections, metadata, warnings, images
opendockit report.pdf --count # just the character count
opendockit scan.pdf --describe # describe images with a vision model (uses OPENAI_API_KEY)
LangChain, LlamaIndex and MCP
from opendockit.integrations.langchain import OpenDocKitLoader
from opendockit.integrations.llama_index import OpenDocKitReader
docs = OpenDocKitLoader("report.pdf", mode="sections").load() # one Document per page / slide / sheet
docs = OpenDocKitReader(mode="sections").load_data("report.pdf")
opendockit-mcp --root ~/Documents # MCP server: Claude, Cursor and other agents read your documents as Markdown
To let the client start the server itself, with nothing installed beforehand, add it to the client's MCP settings:
{
"mcpServers": {
"opendockit": {
"command": "uvx",
"args": ["opendockit-mcp", "--root", "/path/to/documents"]
}
}
}
With mode="sections" each page, slide or sheet is its own Document, with its number in the metadata for citations. See docs/integrations.md.
Supported formats
| Format | Sections | Highlights |
|---|---|---|
| page | reading order across columns, ruled and borderless tables, form field values, headings from font size, repeated header/footer removal, scanned pages via vision model | |
| DOCX | document | heading styles, list numbering, merged cells, charts → tables, SmartArt, footnotes/endnotes, equations → LaTeX, comments |
| PPTX | slide | on-screen order, bullet levels, tables, charts → tables, SmartArt, equations, speaker notes |
| XLSX · XLS · Excel 2003 XML · CSV | sheet | several tables per sheet, merged cells, number and date formats, charts, cell comments |
| HWP · HWPX | document | outline headings, numbering, merged cells, captions, footnotes, equations → LaTeX |
| ODT · ODS · ODP | document / sheet / slide | read directly from the package: headings, lists, merged cells, charts, formulas → LaTeX, speaker notes |
| RTF | document | built-in parser: legacy code pages (CP949), tables with merged cells, lists, footnotes, pictures |
| EPUB | document | chapters in reading order, pictures stored in the book, metadata |
| DOC · PPT | document / slide | converted through LibreOffice when it is installed, then read like DOCX / PPTX |
| HTML · MHTML | document | tables with rowspan/colspan, layout tables unwrapped, MathML → LaTeX, hidden elements removed |
Email (.eml, mbox) | document | headers, HTML or plain body, forwarded messages, calendar invites |
| TXT · Markdown | document | encoding detection (UTF-8, CP949, UTF-16, Windows-1252) |
| Images | document / page | JPEG, PNG, WebP, GIF, BMP, TIFF, AVIF, and HEIC with the heic extra — described by a vision model |
Legacy .doc and .ppt need LibreOffice installed; everything else is read by OpenDocKit itself. The full list of
what each format restores is in docs/formats.md; the honest list of what it cannot do is in
docs/limitations.md.
How it compares
We ran OpenDocKit, MarkItDown, Kreuzberg, Unstructured and Docling on the same files on a standard GitHub runner (method, raw results and how to rerun it):
- Structure — OpenDocKit keeps the most: 49 of 49 checks such as "is the merged cell still attached to its row", against 36 for the next tool. The checks are ours, and 0.2.0 was written with them known; the report says what that means.
- Formats — it reads 26 of the 31 formats tested without any model or service, the most of the five, and is the only one that reads HWP and HWPX.
- Not the fastest, and no OCR — scans and images are read only through a vision model, where Docling and
Unstructured read them offline. Kreuzberg is about thirty times faster, and legacy
.doc/.pptcost about a second each because they go through LibreOffice.
Examples
examples/ has one sample document per format and the Markdown OpenDocKit produces from each — with and
without a vision model — so you can judge the output before installing anything.
Documentation
| Formats | What is extracted from each format and how it is rendered |
| Options | ConvertOptions, exceptions and error codes |
| Images | Extraction, filtering, vision models, custom describers |
| Running on a server | IsolatedConverter, resource limits, the character budget |
| Integrations | LangChain, LlamaIndex and the MCP server |
| Integration guide | What an upload UI should tell its users |
| Design | Why it is built this way, and how it was verified |
| Limitations | Known gaps, stated plainly |
| Decision records | Every deliberate trade-off, with the test that pins it |
Contributing
Contributions are welcome — bug reports with a minimal reproducing document are especially valuable. Start with CONTRIBUTING.md.
git clone https://github.com/hyeonsang010716/OpenDocKit.git && cd OpenDocKit
uv sync
uv run pytest -n auto -m "not timing" # the bulk of the suite, in parallel
uv run pytest -m timing # tests that measure time, one at a time
uv run ruff check
License
opendockit-mcppip install opendockit-mcp