Deterministic hierarchical document parser and chunker for PDF, DOCX, PPTX and HTML.
Copy the AI prompt to install this server into Claude Code, Cursor, or another agent โ or use 1-click editor setup below.
We haven't yet run this listing's install command through our automated sandbox check. This isn't a red flag โ we're steadily working through the catalog.
๐ก Paste into ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows)
Lightning-fast (31 pages/sec), deterministic document parser and chunker for business documents. No LLM calls or heavy ML models.
DocSlicer turns PDFs, Word documents, HTML pages, and PowerPoint files into clean chunks, structured blocks, tables, charts, markdown and a navigable heading hierarchy.
Top score on BizDocBench (0.88 overall vs 0.70 for the next-best tool). 0.80 table accuracy, 0.98 content faithfulness, 0.85 heading recognition and hierarchy preservation, and 0.76 RAG retrieval performance.
Two ways to use it:
1., 1.2., 1.2.3) and free-form headings; uses font size, bold weight, and document structure โ not inference; handles re-entry after exhibit breaks and repeated navigation headings across pageschunks, blocks, tables, charts, metadata, and hierarchy in one placepdf, docx, pptx, and html โ including JS-rendered pages via PlaywrightMeasured with BizDocBench โ an open benchmark for multi-format business document parsing. All scores are 0โ1 (higher is better); pages_per_sec_aggregate is throughput across the full corpus.
| Tool | Score | Coverage | Speed | Hierarchy | Faithfulness | Tables | Retrieval | Pages/sec |
|---|---|---|---|---|---|---|---|---|
| docslicer | 0.8796 | 1.0000 | 0.8836 | 0.8466 | 0.9824 | 0.8047 | 0.7601 | 31.27 |
| docling | 0.7036 | 1.0000 | 0.3805 | 0.4905 | 0.8927 | 0.7467 | 0.7111 | 3.46 |
| markitdown | 0.5838 | 1.0000 | 0.8513 | 0.0604 | 0.7972 | 0.2584 | 0.5357 | 27.42 |
| unstructured | 0.5798 | 0.9091 | 0.1073 | 0.4327 | 0.9057 | 0.4812 | 0.6430 | 0.52 |
| opendataloader | 0.5359 | 0.5844 | 1.0000 | 0.3853 | 0.6484 | 0.2655 | 0.3317 | 117.26 |
| pymupdf4llm | 0.4519 | 0.5974 | 0.6492 | 0.1089 | 0.6456 | 0.3551 | 0.3552 | 11.84 |
| mineru | 0.4107 | 0.5974 | 0.1353 | 0.4220 | 0.6176 | 0.3012 | 0.3910 | 0.70 |
| marker | 0.3735 | 0.5974 | 0.1598 | 0.1926 | 0.6121 | 0.3012 | 0.3778 | 0.87 |
The core install is dependency-light. Optional features are available as extras:
Extras can be combined: pip install 'docslicer[html,ocr,llm]'.
Requires Python 3.10+
parse_document returns a ParseResult:
Each Chunk carries:
Every chunk carries its full heading breadcrumb, no matter how deeply nested. For example, a paragraph six levels deep in a financial filing:
This lets downstream code filter or group chunks by any level of the hierarchy without re-parsing the document.
| Format | Extension | Notes |
|---|---|---|
.pdf | Text-based and scanned (OCR extra required for scanned) | |
| Word | .docx | Full style and outline hierarchy |
| HTML | .html, URLs | Static files and JS-rendered pages (html extra required for URLs) |
| PowerPoint | .pptx | Slides, speaker notes, charts |
Not supported: .doc, .ppt (legacy Office formats), .xlsx.
parse_document auto-detects the format from the file extension or magic bytes. Pass a file path, URL, raw bytes, or a file-like object:
parse_document (and the format-specific functions) accept options that control what
gets parsed and how, before it's chunked. Format-specific toggles are accepted everywhere
for a uniform API but only take effect for the relevant format.
Because DocSlicer is structure-aware, it initially produces one chunk per heading or paragraph boundary. For documents with many short sections this can yield a lot of small chunks. With merge_small_chunks=True (the default), sibling sections under the same parent heading are merged together until they reach min_chunk_size โ but never across heading boundaries into a different parent.
For example, these five short sections all fall under ## Products and Services Performance:
Instead of four tiny chunks, they get merged into one coherent chunk that still carries the correct path for each paragraph. Set merge_small_chunks=False if you need one chunk per section regardless of size.
table_representation controls how tables are serialised into chunk text. Given a
financial table with multi-row column headers:
"markdown" (default) โ preserves the original 2D layout:
"melted" โ one row per cell, headers joined with >. Good for sparse or
pivot-style tables where individual cell retrieval matters:
"jsonl" โ one JSON object per row, multi-row headers joined with _. Useful
when chunks are fed into structured extraction or tool-use pipelines:
Point parse_all at a folder (or pass a list of paths/URLs). It yields (source, result)
pairs, and a file that fails to parse yields the Exception instead of aborting the batch.
Any parse_document keyword โ chunk sizes, include_*, etc. โ is forwarded per document.
DocumentParser holds a fixed ParseConfig across many documents and keeps a single
browser open across HTML/URL inputs (launched lazily on the first HTML parse), so a batch
of URLs starts Chromium once instead of once per document. Use it as a context manager so
that browser is always released:
There are two independent knobs, and they compose:
ParseConfig(max_workers=N) โ within a single document: parallelizes PDF word
extraction, cell building, and OCR across processes (default: auto, sized to CPU cores).
Best when documents are large.DocumentParser(config, workers=N) โ across documents: fans whole documents out
over N worker processes, each with its own config and browser. Best when you have many
documents. Results arrive in submission order (this path isn't lazy per-document).Setting workers alone defaults each worker's max_workers to 1, so nested pools don't
oversubscribe the machine; set both explicitly to run both levels at once. The workers
path can't forward a browser session or on_stage callback across processes โ leave
workers unset when you need those.
Guard your entry point. DocSlicer uses a
ProcessPoolExecutorwhenever there's real CPU work to fan out โ any PDF over ~50 pages, any scanned/OCR PDF of any length, and both parallelism knobs above. This is not opt-in: a plaindocslicer.parse_document("big.pdf")triggers it too. On macOS and Windows, Python spawns workers by re-importing your script top to bottom, so a parse that runs at module level makes each worker re-run it and spawn again โ raisingRuntimeError: An attempt has been made to start a new process before the current process ... bootstrapping phase. Put your parsing code inside a function behind anif __name__ == "__main__":guard:
Most chunking libraries give you a flat list of text segments. DocSlicer also gives you a navigable tree of the document's heading structure, extracted deterministically from the document itself.
This is particularly useful for agents and retrieval pipelines working with long documents: rather than feeding the entire document into context, the agent can inspect the outline first to understand the structure, decide which sections are relevant, and then pull only those chunks โ keeping token usage proportional to the task.
.level(n) returns all headings at depth n. Pass a parent to scope it to a
specific subtree โ the typical pattern for an agent navigating a long document:
find_heading matches any node whose text contains the search term (case-insensitive).
All retrieval methods recurse into subsections by default.
A parsed result is plain data, so you can persist it and reload it later. When an agent asks many questions about the same document, there's no need to parse it again on every question:
A reloaded result supports the full API โ hierarchy, find_heading,
chunks_under, tables โ so a long-running agent session or document server can
keep documents open across requests without re-parsing.
save() decides what to write from the path you give it.
Only result.json round-trips โ the collection and directory forms write flat rows
without the heading hierarchy, so ParseResult.load() can't read them back.
parse_document automatically detects scanned pages and falls back to OCR when the
[ocr] extra is installed. No configuration needed โ result.metadata.has_ocr
tells you whether OCR was used.
DocSlicer ships an MCP server, so LLM clients (Claude Desktop, Claude Code, Cursor, โฆ) can parse and read documents directly.
Download docslicer-X.Y.Z.mcpb from the
latest release and
double-click it, or drag it onto the Claude Desktop window. You pick the folder
DocSlicer is allowed to read and write during install; no config file, and no
Python of your own โ uv provisions the interpreter.
Every client below launches the server over stdio. uvx needs nothing
installed ahead of time:
If you'd rather install it once and skip the resolve on every launch, use
pip install 'docslicer[mcp]' (or uv tool install) and set
"command": "docslicer-mcp" with no args.
| Client | Where the config goes |
|---|---|
| Claude Code | claude mcp add docslicer -- uvx --from 'docslicer[mcp]' docslicer-mcp |
| Cursor | ~/.cursor/mcp.json, or .cursor/mcp.json per project |
| VS Code | .vscode/mcp.json (use a servers key instead of mcpServers) |
| Windsurf | ~/.codeium/windsurf/mcp_config.json |
| Zed | settings.json, under context_servers |
On GUI-launched clients, prefer the
.mcpb. An app started from the dock does not inherit your shellPATHโ on macOS that excludes/opt/homebrew/binโ so a bareuvxordocslicer-mcpcan work in a terminal and fail when the client spawns it. Use an absolute path (which uvx) if you hit this. The extension sidesteps it entirely.
A parsed document is far larger than a model's context window, so the server
never returns one in a single call. parse registers the document and hands
back a doc_id handle plus a heading outline. Every other tool takes that
handle and returns a bounded slice โ the model pulls in only what it needs.
| Tool | Returns |
|---|---|
parse | doc_id handle, title, page count, heading outline |
get_outline | The outline again, for when it scrolls out of context |
read | The text under one or more headings, named from the outline |
search | Headings to read, ranked, each with a snippet |
to_markdown | Writes the whole document to disk; returns the path |
Every outline line carries what reading it would cost:
That figure is the same estimate read reports back, so a budget made from the
outline holds when it is spent. Sizes are cumulative โ a parent never costs less
than the children beneath it โ which is what makes "descend or just read it" a
decision the model can make before spending the context rather than after.
read takes heading text exactly as the outline prints it. Where a heading
appears twice, prefixing any ancestor disambiguates it ("Notes > Revenue");
the full chain is never required. Returned text is interleaved with [Page X]
markers using the document's own page labels (S-23, iv), so a quotation can
be cited to the page it actually came from rather than to wherever its section
began.
search is the fallback for when the outline does not settle the question โ
headings that name nothing useful (Note 14, Item 7A), or a figure buried in
a table no heading mentions. It combines a whole-word literal match with BM25
over the chunks, and returns places, not answers: each hit is a heading to
pass to read. Query terms that appear nowhere in the document are reported
back, so a query that scored well on one rare word can be recognised as the bad
query it was.
to_markdown is the escape hatch for when the user wants the document itself
rather than an answer drawn from it. It writes to disk and returns a path, so
nothing enters the model's context and document size stops mattering.
Parsed results are cached on disk, so re-parsing the same file with the same
options is free. The cache key includes the file's size and mtime โ edit the
document and the next parse re-parses it automatically.
| Variable | Effect |
|---|---|
DOCSLICER_MCP_ROOT | Restrict file sources and written output to this directory tree |
DOCSLICER_MCP_ALLOW_URLS | Set to 0 to reject http(s) sources |
DOCSLICER_MCP_CACHE | Where parsed results are persisted (default ~/.cache/docslicer-mcp) |
DOCSLICER_MCP_CACHE_MAX_MB | Cache size ceiling, oldest pruned first (default 2048; 0 disables) |
Set DOCSLICER_MCP_ROOT when exposing the server to anything but yourself โ
without it, any readable path on the machine is parseable, and to_markdown
can write anywhere the server process can.
docslicer parses one document to JSON on stdout โ for a quick look at a file,
or to pipe into jq.
It takes the same parsing and chunking options as parse_document; run
docslicer --help for the full list.
If you know the format upfront and want explicit failure on unexpected input, use the
format-specific variants. They accept the same arguments as parse_document:
Full policy: https://docslicer.ai/privacy
What is collected. Nothing. DocSlicer has no telemetry, analytics, crash reporting, or usage tracking, and requires no account, licence key, or registration.
How your documents are used. Parsing runs entirely on your own machine, in a
local process. Document contents are used only to produce the outline, text
slices, search results, and markdown you ask for, and are returned only to the
caller. Documents are never uploaded to DocSlicer or to any third party. When
running as an MCP server, DOCSLICER_MCP_ROOT bounds which directory tree may
be read from and written to.
Where data is stored, and for how long. Parsed results are cached on your
own disk โ by default ~/.cache/docslicer-mcp, configurable with
DOCSLICER_MCP_CACHE. The cache is pruned to a size ceiling
(DOCSLICER_MCP_CACHE_MAX_MB, default 2048 MB); otherwise it persists until you
delete it, and deleting the directory removes it permanently with no copy
retained elsewhere. Nothing is written outside the cache directory and any
output path you supply.
Network access and third parties. No network request is made for a local
file. Requests leave your machine only when you pass an http(s) source: that
URL is fetched directly from the host you named, and for HTML pages Playwright
may load the subresources that page references, exactly as a browser would.
Requests to sec.gov send a User-Agent header identifying the client, as the
SEC fair-access policy requires. These hosts are third parties chosen by you,
not by DocSlicer, and their own policies govern what they log. Set
DOCSLICER_MCP_ALLOW_URLS=0 to reject remote sources entirely.
Third-party clients. When DocSlicer runs as an MCP server, the client (Claude, Cursor, โฆ) handles the conversation under its own privacy policy. DocSlicer is not a party to that and receives nothing from it.
Contact. Privacy questions: jelle@docslicer.ai ยท Issues: https://github.com/DocSlicer/DocSlicer/issues
DocSlicer is dual-licensed:
See LICENSE-COMMERCIAL.md for details, or reach out about a commercial license.
mcp-name: io.github.DocSlicer/docslicer
Showcase your server listing on GitHub or your project documentation. Embed this dynamic SVG badge to highlight official listing status and live engagement.
[](https://allmcps.com/mcp/docslicer)<a href="https://allmcps.com/mcp/docslicer"><img src="https://allmcps.com/api/badge/docslicer?style=directory" alt="DocSlicer on AllMCPs" /></a>