docslicer 0.2.0__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {docslicer-0.2.0/src/docslicer.egg-info → docslicer-0.2.2}/PKG-INFO +263 -10
- {docslicer-0.2.0 → docslicer-0.2.2}/README.md +259 -9
- {docslicer-0.2.0 → docslicer-0.2.2}/pyproject.toml +8 -1
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/__init__.py +1 -1
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/_config.py +2 -1
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/_orchestrator.py +6 -1
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/_result.py +83 -14
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/cli.py +7 -2
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/html/html_orchestrator.py +46 -17
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/html/step_01_box_extractor.py +25 -6
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/html/step_01_static_box_extractor.py +219 -110
- docslicer-0.2.2/src/docslicer/mcp/__init__.py +16 -0
- docslicer-0.2.2/src/docslicer/mcp/_store.py +343 -0
- docslicer-0.2.2/src/docslicer/mcp/server.py +1186 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/metadata/schema.py +4 -0
- docslicer-0.2.2/src/docslicer/ocr/__init__.py +9 -0
- docslicer-0.2.2/src/docslicer/ocr/_availability.py +61 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/pdf/pdf_orchestrator.py +34 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/shared/shared_orchestrator.py +7 -1
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/shared/step_05_heading_detector.py +21 -10
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/shared/step_07_block_merger.py +36 -5
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/shared/step_08_chunk_builder.py +199 -42
- {docslicer-0.2.0 → docslicer-0.2.2/src/docslicer.egg-info}/PKG-INFO +263 -10
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer.egg-info/SOURCES.txt +6 -1
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer.egg-info/entry_points.txt +1 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer.egg-info/requires.txt +4 -0
- docslicer-0.2.2/tests/test_table_cell_newlines.py +92 -0
- docslicer-0.2.0/src/docslicer/ocr/__init__.py +0 -3
- {docslicer-0.2.0 → docslicer-0.2.2}/LICENSE +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/LICENSE-COMMERCIAL.md +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/setup.cfg +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/_utils/__init__.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/_utils/color_utils.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/_utils/cpu.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/_utils/df_aggregation/__init__.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/_utils/df_aggregation/registry_aggregator.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/_utils/df_aggregation/text_merge.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/_utils/df_export/__init__.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/_utils/df_export/export_debug.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/_utils/df_export/reorder_columns.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/_utils/io/__init__.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/_utils/io/yaml_compilers/__init__.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/_utils/io/yaml_compilers/exhibit_patterns.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/_utils/io/yaml_compilers/hierarchy_type_patterns.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/_utils/io/yaml_compilers/page_label_patterns.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/_utils/io/yaml_loader.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/_utils/layout/__init__.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/_utils/layout/gutter_detector.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/_utils/layout/layouts.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/_utils/layout/line_merger.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/_utils/layout/line_number_detector.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/_utils/layout/reading_order.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/_utils/layout/shape_processor.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/_utils/oxm_package.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/_utils/parallel.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/_utils/password.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/_utils/safe_call.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/_utils/table/__init__.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/_utils/table/table_header.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/_utils/table/table_normalize.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/_utils/table/table_schema.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/_utils/text_utils.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/_utils/timing.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/config/common_author_names.csv +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/config/exhibit_patterns.yaml +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/config/hierarchy_type_patterns.yaml +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/config/page_label_patterns.yaml +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/docx/__init__.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/docx/docx_orchestrator.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/docx/native_metadata.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/docx/step_01_package_reader.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/docx/step_02_run_extractor.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/docx/step_03_chart_point_builder.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/docx/step_04_table_cell_builder.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/docx/step_05_paragraph_builder.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/docx/step_06_line_builder.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/docx/step_07_style_prefiller.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/html/__init__.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/html/extract_boxes.js +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/html/native_metadata.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/html/step_02_box_cleaner.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/html/step_03_page_label_detector.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/html/step_04_line_builder.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/html/step_05_table_extractor.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/html/step_06_style_prefiller.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/metadata/__init__.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/metadata/consolidate.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/metadata/generator.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/metadata/ocr_detector.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/metadata/page_analysis.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/metadata/text_fallback.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/ocr/ocr_orchestrator.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/ocr/step_01_word_extractor.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/ocr/step_02_word_colorizer.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/ocr/step_03_shape_extractor.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/ocr/step_04_text_cleaner.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/ocr/step_05_font_size_estimator.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/pdf/__init__.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/pdf/_utils/__init__.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/pdf/_utils/coordinates.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/pdf/_utils/form_fields.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/pdf/_utils/form_label_link.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/pdf/_utils/line_classification.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/pdf/_utils/page_rotation.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/pdf/_utils/script_thresholds.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/pdf/_utils/struct_context.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/pdf/_utils/struct_tree.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/pdf/native_metadata.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/pdf/step_01_word_extractor.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/pdf/step_02_image_extractor.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/pdf/step_03_shape_extractor.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/pdf/step_04_link_extractor.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/pdf/step_05_struct_group.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/pdf/step_06_style_prefiller.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/pdf/step_07_stream_group.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/pdf/step_08_reading_order.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/pdf/step_09_word_relationships.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/pdf/step_10_cell_builder.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/pdf/step_11_page_label_detector.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/pdf/step_12_cell_grouper.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/pdf/step_13_line_builder.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/pdf/step_14_table_builder.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/pptx/__init__.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/pptx/native_metadata.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/pptx/pptx_orchestrator.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/pptx/step_01_package_reader.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/pptx/step_02_run_extractor.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/pptx/step_03_chart_point_builder.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/pptx/step_04_table_cell_builder.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/pptx/step_05_paragraph_builder.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/pptx/step_06_reading_order.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/pptx/step_07_line_builder.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/pptx/step_08_style_prefiller.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/scraping/__init__.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/scraping/config.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/scraping/cookie_consent.js +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/scraping/dispatcher.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/scraping/fetchers/__init__.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/scraping/fetchers/http_fetcher.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/scraping/fetchers/sec_fetcher.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/scraping/models.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/scraping/stealth_init.js +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/shared/__init__.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/shared/step_01_navigation_detector.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/shared/step_02_toc_detector.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/shared/step_03_exhibit_detector.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/shared/step_04_section_classifier.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer/shared/step_06_hierarchy_builder.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer.egg-info/dependency_links.txt +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/src/docslicer.egg-info/top_level.txt +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/tests/test_api.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/tests/test_charts.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/tests/test_document_parser.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/tests/test_errors.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/tests/test_exports.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/tests/test_loose_box_reconstruction.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/tests/test_packaging.py +0 -0
- {docslicer-0.2.0 → docslicer-0.2.2}/tests/test_smoke.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: docslicer
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Deterministic hierarchical document parser and chunker
|
|
5
5
|
Author-email: "Market Framer Inc." <jelle@docslicer.ai>
|
|
6
6
|
License-Expression: AGPL-3.0-only
|
|
@@ -50,6 +50,9 @@ Provides-Extra: crypto
|
|
|
50
50
|
Requires-Dist: msoffcrypto-tool>=5.0; extra == "crypto"
|
|
51
51
|
Provides-Extra: parquet
|
|
52
52
|
Requires-Dist: pyarrow>=18.0; extra == "parquet"
|
|
53
|
+
Provides-Extra: mcp
|
|
54
|
+
Requires-Dist: mcp>=2.0; extra == "mcp"
|
|
55
|
+
Requires-Dist: tiktoken>=0.10; extra == "mcp"
|
|
53
56
|
Provides-Extra: dev
|
|
54
57
|
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
55
58
|
Requires-Dist: pytest-asyncio>=0.24; extra == "dev"
|
|
@@ -57,11 +60,22 @@ Dynamic: license-file
|
|
|
57
60
|
|
|
58
61
|
# DocSlicer
|
|
59
62
|
|
|
60
|
-
[](LICENSE) [](LICENSE-COMMERCIAL.md)
|
|
63
|
+
[](https://pypi.org/project/docslicer/) [](https://pypi.org/project/docslicer/) [](LICENSE) [](LICENSE-COMMERCIAL.md)
|
|
61
64
|
|
|
62
|
-
|
|
65
|
+
[](https://insiders.vscode.dev/redirect/mcp/install?name=docslicer&config=%7B%22name%22%3A%22docslicer%22%2C%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22docslicer%5Bmcp%5D%22%2C%22docslicer-mcp%22%5D%7D) [](https://cursor.com/install-mcp?name=docslicer&config=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJkb2NzbGljZXJbbWNwXSIsImRvY3NsaWNlci1tY3AiXX0%3D) [](https://github.com/DocSlicer/DocSlicer/releases/latest)
|
|
63
66
|
|
|
64
|
-
|
|
67
|
+
Lightning-fast (31 pages/sec), deterministic document parser and chunker for business documents. No LLM calls or heavy ML models.
|
|
68
|
+
|
|
69
|
+
DocSlicer turns PDFs, Word documents, HTML pages, and PowerPoint files into clean chunks, structured blocks, tables, charts, markdown and a navigable heading hierarchy.
|
|
70
|
+
|
|
71
|
+
Top score on [BizDocBench](https://github.com/DocSlicer/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.
|
|
72
|
+
|
|
73
|
+
**Two ways to use it:**
|
|
74
|
+
|
|
75
|
+
- **As a Python library — classic RAG.** The layout-aware chunker gives you clean, non-overlapping chunks, each carrying its full heading breadcrumb, ready to embed. [Jump to the API ↓](#parsing)
|
|
76
|
+
- **As an MCP server — vectorless RAG.** For when you want an answer out of a document right now. Claude, Cursor, or VS Code pulls the outline, picks the section it needs, and reads only that — no embedding, and no 200-page document in the context window. [Jump to setup ↓](#mcp-server)
|
|
77
|
+
|
|
78
|
+
### Quick start
|
|
65
79
|
|
|
66
80
|
```python
|
|
67
81
|
import docslicer
|
|
@@ -117,6 +131,23 @@ if __name__ == "__main__":
|
|
|
117
131
|
|
|
118
132
|
---
|
|
119
133
|
|
|
134
|
+
## Benchmarks
|
|
135
|
+
|
|
136
|
+
Measured with [BizDocBench](https://github.com/DocSlicer/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.
|
|
137
|
+
|
|
138
|
+
| Tool | Score | Coverage | Speed | Hierarchy | Faithfulness | Tables | Retrieval | Pages/sec |
|
|
139
|
+
|---|---|---|---|---|---|---|---|---|
|
|
140
|
+
| **docslicer** | **0.8796** | 1.0000 | 0.8836 | 0.8466 | 0.9824 | 0.8047 | 0.7601 | 31.27 |
|
|
141
|
+
| docling | 0.7036 | 1.0000 | 0.3805 | 0.4905 | 0.8927 | 0.7467 | 0.7111 | 3.46 |
|
|
142
|
+
| markitdown | 0.5838 | 1.0000 | 0.8513 | 0.0604 | 0.7972 | 0.2584 | 0.5357 | 27.42 |
|
|
143
|
+
| unstructured | 0.5798 | 0.9091 | 0.1073 | 0.4327 | 0.9057 | 0.4812 | 0.6430 | 0.52 |
|
|
144
|
+
| opendataloader | 0.5359 | 0.5844 | 1.0000 | 0.3853 | 0.6484 | 0.2655 | 0.3317 | 117.26 |
|
|
145
|
+
| pymupdf4llm | 0.4519 | 0.5974 | 0.6492 | 0.1089 | 0.6456 | 0.3551 | 0.3552 | 11.84 |
|
|
146
|
+
| mineru | 0.4107 | 0.5974 | 0.1353 | 0.4220 | 0.6176 | 0.3012 | 0.3910 | 0.70 |
|
|
147
|
+
| marker | 0.3735 | 0.5974 | 0.1598 | 0.1926 | 0.6121 | 0.3012 | 0.3778 | 0.87 |
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
120
151
|
## Install
|
|
121
152
|
|
|
122
153
|
```bash
|
|
@@ -135,6 +166,7 @@ pip install 'docslicer[ocr]' # scanned PDF support via Tesseract + OpenCV
|
|
|
135
166
|
# Linux: apt install tesseract-ocr
|
|
136
167
|
# macOS: brew install tesseract
|
|
137
168
|
|
|
169
|
+
pip install 'docslicer[mcp]' # MCP server for LLM clients (Claude, Cursor, …)
|
|
138
170
|
pip install 'docslicer[llm]' # exact token counts via tiktoken (exact_tokens=True)
|
|
139
171
|
pip install 'docslicer[crypto]' # password-protected Office files (msoffcrypto-tool)
|
|
140
172
|
pip install 'docslicer[parquet]' # Parquet export support
|
|
@@ -437,22 +469,51 @@ result.tables_by_page(14)
|
|
|
437
469
|
result.charts_by_page(14)
|
|
438
470
|
```
|
|
439
471
|
|
|
472
|
+
### Parse once, navigate many times
|
|
473
|
+
|
|
474
|
+
A parsed result is plain data, so you can persist it and reload it later. When an
|
|
475
|
+
agent asks many questions about the same document, there's no need to parse it
|
|
476
|
+
again on every question:
|
|
477
|
+
|
|
478
|
+
```python
|
|
479
|
+
from pathlib import Path
|
|
480
|
+
import docslicer
|
|
481
|
+
|
|
482
|
+
cache = Path("annual_report.json")
|
|
483
|
+
|
|
484
|
+
if cache.exists():
|
|
485
|
+
result = docslicer.ParseResult.load(cache)
|
|
486
|
+
else:
|
|
487
|
+
result = docslicer.parse_document("annual_report.pdf")
|
|
488
|
+
result.save(cache)
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
A reloaded result supports the full API — `hierarchy`, `find_heading`,
|
|
492
|
+
`chunks_under`, `tables` — so a long-running agent session or document server can
|
|
493
|
+
keep documents open across requests without re-parsing.
|
|
494
|
+
|
|
440
495
|
---
|
|
441
496
|
|
|
442
497
|
## Export
|
|
443
498
|
|
|
499
|
+
`save()` decides what to write from the path you give it.
|
|
500
|
+
|
|
444
501
|
```python
|
|
445
|
-
# Save
|
|
446
|
-
result.save("
|
|
447
|
-
|
|
448
|
-
# (+ charts.parquet when the document has charts)
|
|
502
|
+
# Save the whole result and reload it later — keeps the heading hierarchy
|
|
503
|
+
result.save("result.json") # same output as result.to_json()
|
|
504
|
+
result = docslicer.ParseResult.load("result.json")
|
|
449
505
|
|
|
450
|
-
#
|
|
506
|
+
# A single collection, in the format you name
|
|
451
507
|
result.save("chunks.csv")
|
|
452
508
|
result.save("charts.jsonl") # stems: chunks | blocks | tables | charts | metadata
|
|
453
|
-
result.save("result.json") # full parse result as JSON
|
|
454
509
|
result.export_chunks_jsonl("chunks.jsonl")
|
|
455
510
|
|
|
511
|
+
# One file per collection
|
|
512
|
+
result.save("output/")
|
|
513
|
+
# → output/chunks.parquet, blocks.parquet, tables.parquet, metadata.json
|
|
514
|
+
# (+ charts.parquet when the document has charts)
|
|
515
|
+
# Falls back to .csv unless the [parquet] extra is installed.
|
|
516
|
+
|
|
456
517
|
# Render as Markdown or plain text
|
|
457
518
|
md = result.export_to_markdown(include_tables=True)
|
|
458
519
|
txt = result.export_to_text()
|
|
@@ -461,6 +522,9 @@ txt = result.export_to_text()
|
|
|
461
522
|
df = result.chunks_df()
|
|
462
523
|
```
|
|
463
524
|
|
|
525
|
+
Only `result.json` round-trips — the collection and directory forms write flat rows
|
|
526
|
+
without the heading hierarchy, so `ParseResult.load()` can't read them back.
|
|
527
|
+
|
|
464
528
|
### Debug mode
|
|
465
529
|
|
|
466
530
|
```python
|
|
@@ -492,6 +556,142 @@ pip install 'docslicer[ocr]'
|
|
|
492
556
|
|
|
493
557
|
---
|
|
494
558
|
|
|
559
|
+
## MCP server
|
|
560
|
+
|
|
561
|
+
DocSlicer ships an [MCP](https://modelcontextprotocol.io) server, so LLM clients
|
|
562
|
+
(Claude Desktop, Claude Code, Cursor, …) can parse and read documents directly.
|
|
563
|
+
|
|
564
|
+
```bash
|
|
565
|
+
pip install 'docslicer[mcp]'
|
|
566
|
+
docslicer-mcp # stdio — what desktop clients launch
|
|
567
|
+
docslicer-mcp --transport http --port 8000
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
### Claude Desktop / Cowork — one-click install
|
|
571
|
+
|
|
572
|
+
Download `docslicer-X.Y.Z.mcpb` from the
|
|
573
|
+
[latest release](https://github.com/DocSlicer/DocSlicer/releases/latest) and
|
|
574
|
+
double-click it, or drag it onto the Claude Desktop window. You pick the folder
|
|
575
|
+
DocSlicer is allowed to read and write during install; no config file, and no
|
|
576
|
+
Python of your own — `uv` provisions the interpreter.
|
|
577
|
+
|
|
578
|
+
### Other clients
|
|
579
|
+
|
|
580
|
+
Every client below launches the server over stdio. `uvx` needs nothing
|
|
581
|
+
installed ahead of time:
|
|
582
|
+
|
|
583
|
+
```jsonc
|
|
584
|
+
{
|
|
585
|
+
"mcpServers": {
|
|
586
|
+
"docslicer": {
|
|
587
|
+
"command": "uvx",
|
|
588
|
+
"args": ["--from", "docslicer[mcp]", "docslicer-mcp"],
|
|
589
|
+
"env": { "DOCSLICER_MCP_ROOT": "/Users/you/Documents" }
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
If you'd rather install it once and skip the resolve on every launch, use
|
|
596
|
+
`pip install 'docslicer[mcp]'` (or `uv tool install`) and set
|
|
597
|
+
`"command": "docslicer-mcp"` with no `args`.
|
|
598
|
+
|
|
599
|
+
| Client | Where the config goes |
|
|
600
|
+
| --- | --- |
|
|
601
|
+
| Claude Code | `claude mcp add docslicer -- uvx --from 'docslicer[mcp]' docslicer-mcp` |
|
|
602
|
+
| Cursor | `~/.cursor/mcp.json`, or `.cursor/mcp.json` per project |
|
|
603
|
+
| VS Code | `.vscode/mcp.json` (use a `servers` key instead of `mcpServers`) |
|
|
604
|
+
| Windsurf | `~/.codeium/windsurf/mcp_config.json` |
|
|
605
|
+
| Zed | `settings.json`, under `context_servers` |
|
|
606
|
+
|
|
607
|
+
> **On GUI-launched clients, prefer the `.mcpb`.** An app started from the dock
|
|
608
|
+
> does not inherit your shell `PATH` — on macOS that excludes
|
|
609
|
+
> `/opt/homebrew/bin` — so a bare `uvx` or `docslicer-mcp` can work in a
|
|
610
|
+
> terminal and fail when the client spawns it. Use an absolute path
|
|
611
|
+
> (`which uvx`) if you hit this. The extension sidesteps it entirely.
|
|
612
|
+
|
|
613
|
+
### How it works
|
|
614
|
+
|
|
615
|
+
A parsed document is far larger than a model's context window, so the server
|
|
616
|
+
never returns one in a single call. `parse` registers the document and hands
|
|
617
|
+
back a `doc_id` handle plus a heading outline. Every other tool takes that
|
|
618
|
+
handle and returns a bounded slice — the model pulls in only what it needs.
|
|
619
|
+
|
|
620
|
+
| Tool | Returns |
|
|
621
|
+
| --- | --- |
|
|
622
|
+
| `parse` | `doc_id` handle, title, page count, heading outline |
|
|
623
|
+
| `get_outline` | The outline again, for when it scrolls out of context |
|
|
624
|
+
| `read` | The text under one or more headings, named from the outline |
|
|
625
|
+
| `search` | Headings to `read`, ranked, each with a snippet |
|
|
626
|
+
| `to_markdown` | Writes the whole document to disk; returns the path |
|
|
627
|
+
|
|
628
|
+
Every outline line carries what reading it would cost:
|
|
629
|
+
|
|
630
|
+
```
|
|
631
|
+
- Financial statements ~48k
|
|
632
|
+
- Note 14 — Segment reporting ~900
|
|
633
|
+
- Note 15 — Income taxes ~2.1k
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
That figure is the same estimate `read` reports back, so a budget made from the
|
|
637
|
+
outline holds when it is spent. Sizes are cumulative — a parent never costs less
|
|
638
|
+
than the children beneath it — which is what makes "descend or just read it" a
|
|
639
|
+
decision the model can make before spending the context rather than after.
|
|
640
|
+
|
|
641
|
+
`read` takes heading text exactly as the outline prints it. Where a heading
|
|
642
|
+
appears twice, prefixing any ancestor disambiguates it (`"Notes > Revenue"`);
|
|
643
|
+
the full chain is never required. Returned text is interleaved with `[Page X]`
|
|
644
|
+
markers using the document's own page labels (`S-23`, `iv`), so a quotation can
|
|
645
|
+
be cited to the page it actually came from rather than to wherever its section
|
|
646
|
+
began.
|
|
647
|
+
|
|
648
|
+
`search` is the fallback for when the outline does not settle the question —
|
|
649
|
+
headings that name nothing useful (`Note 14`, `Item 7A`), or a figure buried in
|
|
650
|
+
a table no heading mentions. It combines a whole-word literal match with BM25
|
|
651
|
+
over the chunks, and returns *places*, not answers: each hit is a heading to
|
|
652
|
+
pass to `read`. Query terms that appear nowhere in the document are reported
|
|
653
|
+
back, so a query that scored well on one rare word can be recognised as the bad
|
|
654
|
+
query it was.
|
|
655
|
+
|
|
656
|
+
`to_markdown` is the escape hatch for when the user wants the document itself
|
|
657
|
+
rather than an answer drawn from it. It writes to disk and returns a path, so
|
|
658
|
+
nothing enters the model's context and document size stops mattering.
|
|
659
|
+
|
|
660
|
+
Parsed results are cached on disk, so re-parsing the same file with the same
|
|
661
|
+
options is free. The cache key includes the file's size and mtime — edit the
|
|
662
|
+
document and the next `parse` re-parses it automatically.
|
|
663
|
+
|
|
664
|
+
### Configuration
|
|
665
|
+
|
|
666
|
+
| Variable | Effect |
|
|
667
|
+
| --- | --- |
|
|
668
|
+
| `DOCSLICER_MCP_ROOT` | Restrict file sources **and** written output to this directory tree |
|
|
669
|
+
| `DOCSLICER_MCP_ALLOW_URLS` | Set to `0` to reject `http(s)` sources |
|
|
670
|
+
| `DOCSLICER_MCP_CACHE` | Where parsed results are persisted (default `~/.cache/docslicer-mcp`) |
|
|
671
|
+
| `DOCSLICER_MCP_CACHE_MAX_MB` | Cache size ceiling, oldest pruned first (default `2048`; `0` disables) |
|
|
672
|
+
|
|
673
|
+
Set `DOCSLICER_MCP_ROOT` when exposing the server to anything but yourself —
|
|
674
|
+
without it, any readable path on the machine is parseable, and `to_markdown`
|
|
675
|
+
can write anywhere the server process can.
|
|
676
|
+
|
|
677
|
+
---
|
|
678
|
+
|
|
679
|
+
## Command line
|
|
680
|
+
|
|
681
|
+
`docslicer` parses one document to JSON on stdout — for a quick look at a file,
|
|
682
|
+
or to pipe into `jq`.
|
|
683
|
+
|
|
684
|
+
```bash
|
|
685
|
+
docslicer report.pdf # chunks as JSON
|
|
686
|
+
docslicer report.pdf -o chunks.json # write to a file
|
|
687
|
+
docslicer report.pdf --no-chunking # blocks instead of chunks
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
It takes the same parsing and chunking options as `parse_document`; run
|
|
691
|
+
`docslicer --help` for the full list.
|
|
692
|
+
|
|
693
|
+
---
|
|
694
|
+
|
|
495
695
|
## Format-specific functions
|
|
496
696
|
|
|
497
697
|
If you know the format upfront and want explicit failure on unexpected input, use the
|
|
@@ -506,6 +706,47 @@ docslicer.parse_html("filing.html")
|
|
|
506
706
|
|
|
507
707
|
---
|
|
508
708
|
|
|
709
|
+
## Privacy Policy
|
|
710
|
+
|
|
711
|
+
Full policy: <https://docslicer.ai/privacy>
|
|
712
|
+
|
|
713
|
+
**What is collected.** Nothing. DocSlicer has no telemetry, analytics, crash
|
|
714
|
+
reporting, or usage tracking, and requires no account, licence key, or
|
|
715
|
+
registration.
|
|
716
|
+
|
|
717
|
+
**How your documents are used.** Parsing runs entirely on your own machine, in a
|
|
718
|
+
local process. Document contents are used only to produce the outline, text
|
|
719
|
+
slices, search results, and markdown you ask for, and are returned only to the
|
|
720
|
+
caller. Documents are never uploaded to DocSlicer or to any third party. When
|
|
721
|
+
running as an MCP server, `DOCSLICER_MCP_ROOT` bounds which directory tree may
|
|
722
|
+
be read from and written to.
|
|
723
|
+
|
|
724
|
+
**Where data is stored, and for how long.** Parsed results are cached on your
|
|
725
|
+
own disk — by default `~/.cache/docslicer-mcp`, configurable with
|
|
726
|
+
`DOCSLICER_MCP_CACHE`. The cache is pruned to a size ceiling
|
|
727
|
+
(`DOCSLICER_MCP_CACHE_MAX_MB`, default 2048 MB); otherwise it persists until you
|
|
728
|
+
delete it, and deleting the directory removes it permanently with no copy
|
|
729
|
+
retained elsewhere. Nothing is written outside the cache directory and any
|
|
730
|
+
output path you supply.
|
|
731
|
+
|
|
732
|
+
**Network access and third parties.** No network request is made for a local
|
|
733
|
+
file. Requests leave your machine only when you pass an `http(s)` source: that
|
|
734
|
+
URL is fetched directly from the host you named, and for HTML pages Playwright
|
|
735
|
+
may load the subresources that page references, exactly as a browser would.
|
|
736
|
+
Requests to `sec.gov` send a `User-Agent` header identifying the client, as the
|
|
737
|
+
SEC fair-access policy requires. These hosts are third parties chosen by you,
|
|
738
|
+
not by DocSlicer, and their own policies govern what they log. Set
|
|
739
|
+
`DOCSLICER_MCP_ALLOW_URLS=0` to reject remote sources entirely.
|
|
740
|
+
|
|
741
|
+
**Third-party clients.** When DocSlicer runs as an MCP server, the client
|
|
742
|
+
(Claude, Cursor, …) handles the conversation under its own privacy policy.
|
|
743
|
+
DocSlicer is not a party to that and receives nothing from it.
|
|
744
|
+
|
|
745
|
+
**Contact.** Privacy questions: jelle@docslicer.ai · Issues:
|
|
746
|
+
<https://github.com/DocSlicer/DocSlicer/issues>
|
|
747
|
+
|
|
748
|
+
---
|
|
749
|
+
|
|
509
750
|
## License
|
|
510
751
|
|
|
511
752
|
DocSlicer is **dual-licensed**:
|
|
@@ -514,3 +755,15 @@ DocSlicer is **dual-licensed**:
|
|
|
514
755
|
- **[Commercial license](LICENSE-COMMERCIAL.md)** — for embedding DocSlicer in a closed-source or proprietary product, or offering it as part of a hosted/SaaS service without releasing your source.
|
|
515
756
|
|
|
516
757
|
See [LICENSE-COMMERCIAL.md](LICENSE-COMMERCIAL.md) for details, or reach out about a commercial license.
|
|
758
|
+
|
|
759
|
+
---
|
|
760
|
+
|
|
761
|
+
<!--
|
|
762
|
+
Ownership marker for the official MCP registry (registry.modelcontextprotocol.io).
|
|
763
|
+
The registry verifies a PyPI package by looking for this exact line in the
|
|
764
|
+
project description, which is this README — so it only takes effect once a
|
|
765
|
+
release carrying it is published. Namespace matches the GitHub org that owns
|
|
766
|
+
the repository. Leave it in place; removing it breaks re-publication.
|
|
767
|
+
-->
|
|
768
|
+
|
|
769
|
+
mcp-name: io.github.DocSlicer/docslicer
|
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
# DocSlicer
|
|
2
2
|
|
|
3
|
-
[](LICENSE) [](LICENSE-COMMERCIAL.md)
|
|
3
|
+
[](https://pypi.org/project/docslicer/) [](https://pypi.org/project/docslicer/) [](LICENSE) [](LICENSE-COMMERCIAL.md)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://insiders.vscode.dev/redirect/mcp/install?name=docslicer&config=%7B%22name%22%3A%22docslicer%22%2C%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22docslicer%5Bmcp%5D%22%2C%22docslicer-mcp%22%5D%7D) [](https://cursor.com/install-mcp?name=docslicer&config=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJkb2NzbGljZXJbbWNwXSIsImRvY3NsaWNlci1tY3AiXX0%3D) [](https://github.com/DocSlicer/DocSlicer/releases/latest)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Lightning-fast (31 pages/sec), deterministic document parser and chunker for business documents. No LLM calls or heavy ML models.
|
|
8
|
+
|
|
9
|
+
DocSlicer turns PDFs, Word documents, HTML pages, and PowerPoint files into clean chunks, structured blocks, tables, charts, markdown and a navigable heading hierarchy.
|
|
10
|
+
|
|
11
|
+
Top score on [BizDocBench](https://github.com/DocSlicer/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.
|
|
12
|
+
|
|
13
|
+
**Two ways to use it:**
|
|
14
|
+
|
|
15
|
+
- **As a Python library — classic RAG.** The layout-aware chunker gives you clean, non-overlapping chunks, each carrying its full heading breadcrumb, ready to embed. [Jump to the API ↓](#parsing)
|
|
16
|
+
- **As an MCP server — vectorless RAG.** For when you want an answer out of a document right now. Claude, Cursor, or VS Code pulls the outline, picks the section it needs, and reads only that — no embedding, and no 200-page document in the context window. [Jump to setup ↓](#mcp-server)
|
|
17
|
+
|
|
18
|
+
### Quick start
|
|
8
19
|
|
|
9
20
|
```python
|
|
10
21
|
import docslicer
|
|
@@ -60,6 +71,23 @@ if __name__ == "__main__":
|
|
|
60
71
|
|
|
61
72
|
---
|
|
62
73
|
|
|
74
|
+
## Benchmarks
|
|
75
|
+
|
|
76
|
+
Measured with [BizDocBench](https://github.com/DocSlicer/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.
|
|
77
|
+
|
|
78
|
+
| Tool | Score | Coverage | Speed | Hierarchy | Faithfulness | Tables | Retrieval | Pages/sec |
|
|
79
|
+
|---|---|---|---|---|---|---|---|---|
|
|
80
|
+
| **docslicer** | **0.8796** | 1.0000 | 0.8836 | 0.8466 | 0.9824 | 0.8047 | 0.7601 | 31.27 |
|
|
81
|
+
| docling | 0.7036 | 1.0000 | 0.3805 | 0.4905 | 0.8927 | 0.7467 | 0.7111 | 3.46 |
|
|
82
|
+
| markitdown | 0.5838 | 1.0000 | 0.8513 | 0.0604 | 0.7972 | 0.2584 | 0.5357 | 27.42 |
|
|
83
|
+
| unstructured | 0.5798 | 0.9091 | 0.1073 | 0.4327 | 0.9057 | 0.4812 | 0.6430 | 0.52 |
|
|
84
|
+
| opendataloader | 0.5359 | 0.5844 | 1.0000 | 0.3853 | 0.6484 | 0.2655 | 0.3317 | 117.26 |
|
|
85
|
+
| pymupdf4llm | 0.4519 | 0.5974 | 0.6492 | 0.1089 | 0.6456 | 0.3551 | 0.3552 | 11.84 |
|
|
86
|
+
| mineru | 0.4107 | 0.5974 | 0.1353 | 0.4220 | 0.6176 | 0.3012 | 0.3910 | 0.70 |
|
|
87
|
+
| marker | 0.3735 | 0.5974 | 0.1598 | 0.1926 | 0.6121 | 0.3012 | 0.3778 | 0.87 |
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
63
91
|
## Install
|
|
64
92
|
|
|
65
93
|
```bash
|
|
@@ -78,6 +106,7 @@ pip install 'docslicer[ocr]' # scanned PDF support via Tesseract + OpenCV
|
|
|
78
106
|
# Linux: apt install tesseract-ocr
|
|
79
107
|
# macOS: brew install tesseract
|
|
80
108
|
|
|
109
|
+
pip install 'docslicer[mcp]' # MCP server for LLM clients (Claude, Cursor, …)
|
|
81
110
|
pip install 'docslicer[llm]' # exact token counts via tiktoken (exact_tokens=True)
|
|
82
111
|
pip install 'docslicer[crypto]' # password-protected Office files (msoffcrypto-tool)
|
|
83
112
|
pip install 'docslicer[parquet]' # Parquet export support
|
|
@@ -380,22 +409,51 @@ result.tables_by_page(14)
|
|
|
380
409
|
result.charts_by_page(14)
|
|
381
410
|
```
|
|
382
411
|
|
|
412
|
+
### Parse once, navigate many times
|
|
413
|
+
|
|
414
|
+
A parsed result is plain data, so you can persist it and reload it later. When an
|
|
415
|
+
agent asks many questions about the same document, there's no need to parse it
|
|
416
|
+
again on every question:
|
|
417
|
+
|
|
418
|
+
```python
|
|
419
|
+
from pathlib import Path
|
|
420
|
+
import docslicer
|
|
421
|
+
|
|
422
|
+
cache = Path("annual_report.json")
|
|
423
|
+
|
|
424
|
+
if cache.exists():
|
|
425
|
+
result = docslicer.ParseResult.load(cache)
|
|
426
|
+
else:
|
|
427
|
+
result = docslicer.parse_document("annual_report.pdf")
|
|
428
|
+
result.save(cache)
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
A reloaded result supports the full API — `hierarchy`, `find_heading`,
|
|
432
|
+
`chunks_under`, `tables` — so a long-running agent session or document server can
|
|
433
|
+
keep documents open across requests without re-parsing.
|
|
434
|
+
|
|
383
435
|
---
|
|
384
436
|
|
|
385
437
|
## Export
|
|
386
438
|
|
|
439
|
+
`save()` decides what to write from the path you give it.
|
|
440
|
+
|
|
387
441
|
```python
|
|
388
|
-
# Save
|
|
389
|
-
result.save("
|
|
390
|
-
|
|
391
|
-
# (+ charts.parquet when the document has charts)
|
|
442
|
+
# Save the whole result and reload it later — keeps the heading hierarchy
|
|
443
|
+
result.save("result.json") # same output as result.to_json()
|
|
444
|
+
result = docslicer.ParseResult.load("result.json")
|
|
392
445
|
|
|
393
|
-
#
|
|
446
|
+
# A single collection, in the format you name
|
|
394
447
|
result.save("chunks.csv")
|
|
395
448
|
result.save("charts.jsonl") # stems: chunks | blocks | tables | charts | metadata
|
|
396
|
-
result.save("result.json") # full parse result as JSON
|
|
397
449
|
result.export_chunks_jsonl("chunks.jsonl")
|
|
398
450
|
|
|
451
|
+
# One file per collection
|
|
452
|
+
result.save("output/")
|
|
453
|
+
# → output/chunks.parquet, blocks.parquet, tables.parquet, metadata.json
|
|
454
|
+
# (+ charts.parquet when the document has charts)
|
|
455
|
+
# Falls back to .csv unless the [parquet] extra is installed.
|
|
456
|
+
|
|
399
457
|
# Render as Markdown or plain text
|
|
400
458
|
md = result.export_to_markdown(include_tables=True)
|
|
401
459
|
txt = result.export_to_text()
|
|
@@ -404,6 +462,9 @@ txt = result.export_to_text()
|
|
|
404
462
|
df = result.chunks_df()
|
|
405
463
|
```
|
|
406
464
|
|
|
465
|
+
Only `result.json` round-trips — the collection and directory forms write flat rows
|
|
466
|
+
without the heading hierarchy, so `ParseResult.load()` can't read them back.
|
|
467
|
+
|
|
407
468
|
### Debug mode
|
|
408
469
|
|
|
409
470
|
```python
|
|
@@ -435,6 +496,142 @@ pip install 'docslicer[ocr]'
|
|
|
435
496
|
|
|
436
497
|
---
|
|
437
498
|
|
|
499
|
+
## MCP server
|
|
500
|
+
|
|
501
|
+
DocSlicer ships an [MCP](https://modelcontextprotocol.io) server, so LLM clients
|
|
502
|
+
(Claude Desktop, Claude Code, Cursor, …) can parse and read documents directly.
|
|
503
|
+
|
|
504
|
+
```bash
|
|
505
|
+
pip install 'docslicer[mcp]'
|
|
506
|
+
docslicer-mcp # stdio — what desktop clients launch
|
|
507
|
+
docslicer-mcp --transport http --port 8000
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
### Claude Desktop / Cowork — one-click install
|
|
511
|
+
|
|
512
|
+
Download `docslicer-X.Y.Z.mcpb` from the
|
|
513
|
+
[latest release](https://github.com/DocSlicer/DocSlicer/releases/latest) and
|
|
514
|
+
double-click it, or drag it onto the Claude Desktop window. You pick the folder
|
|
515
|
+
DocSlicer is allowed to read and write during install; no config file, and no
|
|
516
|
+
Python of your own — `uv` provisions the interpreter.
|
|
517
|
+
|
|
518
|
+
### Other clients
|
|
519
|
+
|
|
520
|
+
Every client below launches the server over stdio. `uvx` needs nothing
|
|
521
|
+
installed ahead of time:
|
|
522
|
+
|
|
523
|
+
```jsonc
|
|
524
|
+
{
|
|
525
|
+
"mcpServers": {
|
|
526
|
+
"docslicer": {
|
|
527
|
+
"command": "uvx",
|
|
528
|
+
"args": ["--from", "docslicer[mcp]", "docslicer-mcp"],
|
|
529
|
+
"env": { "DOCSLICER_MCP_ROOT": "/Users/you/Documents" }
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
If you'd rather install it once and skip the resolve on every launch, use
|
|
536
|
+
`pip install 'docslicer[mcp]'` (or `uv tool install`) and set
|
|
537
|
+
`"command": "docslicer-mcp"` with no `args`.
|
|
538
|
+
|
|
539
|
+
| Client | Where the config goes |
|
|
540
|
+
| --- | --- |
|
|
541
|
+
| Claude Code | `claude mcp add docslicer -- uvx --from 'docslicer[mcp]' docslicer-mcp` |
|
|
542
|
+
| Cursor | `~/.cursor/mcp.json`, or `.cursor/mcp.json` per project |
|
|
543
|
+
| VS Code | `.vscode/mcp.json` (use a `servers` key instead of `mcpServers`) |
|
|
544
|
+
| Windsurf | `~/.codeium/windsurf/mcp_config.json` |
|
|
545
|
+
| Zed | `settings.json`, under `context_servers` |
|
|
546
|
+
|
|
547
|
+
> **On GUI-launched clients, prefer the `.mcpb`.** An app started from the dock
|
|
548
|
+
> does not inherit your shell `PATH` — on macOS that excludes
|
|
549
|
+
> `/opt/homebrew/bin` — so a bare `uvx` or `docslicer-mcp` can work in a
|
|
550
|
+
> terminal and fail when the client spawns it. Use an absolute path
|
|
551
|
+
> (`which uvx`) if you hit this. The extension sidesteps it entirely.
|
|
552
|
+
|
|
553
|
+
### How it works
|
|
554
|
+
|
|
555
|
+
A parsed document is far larger than a model's context window, so the server
|
|
556
|
+
never returns one in a single call. `parse` registers the document and hands
|
|
557
|
+
back a `doc_id` handle plus a heading outline. Every other tool takes that
|
|
558
|
+
handle and returns a bounded slice — the model pulls in only what it needs.
|
|
559
|
+
|
|
560
|
+
| Tool | Returns |
|
|
561
|
+
| --- | --- |
|
|
562
|
+
| `parse` | `doc_id` handle, title, page count, heading outline |
|
|
563
|
+
| `get_outline` | The outline again, for when it scrolls out of context |
|
|
564
|
+
| `read` | The text under one or more headings, named from the outline |
|
|
565
|
+
| `search` | Headings to `read`, ranked, each with a snippet |
|
|
566
|
+
| `to_markdown` | Writes the whole document to disk; returns the path |
|
|
567
|
+
|
|
568
|
+
Every outline line carries what reading it would cost:
|
|
569
|
+
|
|
570
|
+
```
|
|
571
|
+
- Financial statements ~48k
|
|
572
|
+
- Note 14 — Segment reporting ~900
|
|
573
|
+
- Note 15 — Income taxes ~2.1k
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
That figure is the same estimate `read` reports back, so a budget made from the
|
|
577
|
+
outline holds when it is spent. Sizes are cumulative — a parent never costs less
|
|
578
|
+
than the children beneath it — which is what makes "descend or just read it" a
|
|
579
|
+
decision the model can make before spending the context rather than after.
|
|
580
|
+
|
|
581
|
+
`read` takes heading text exactly as the outline prints it. Where a heading
|
|
582
|
+
appears twice, prefixing any ancestor disambiguates it (`"Notes > Revenue"`);
|
|
583
|
+
the full chain is never required. Returned text is interleaved with `[Page X]`
|
|
584
|
+
markers using the document's own page labels (`S-23`, `iv`), so a quotation can
|
|
585
|
+
be cited to the page it actually came from rather than to wherever its section
|
|
586
|
+
began.
|
|
587
|
+
|
|
588
|
+
`search` is the fallback for when the outline does not settle the question —
|
|
589
|
+
headings that name nothing useful (`Note 14`, `Item 7A`), or a figure buried in
|
|
590
|
+
a table no heading mentions. It combines a whole-word literal match with BM25
|
|
591
|
+
over the chunks, and returns *places*, not answers: each hit is a heading to
|
|
592
|
+
pass to `read`. Query terms that appear nowhere in the document are reported
|
|
593
|
+
back, so a query that scored well on one rare word can be recognised as the bad
|
|
594
|
+
query it was.
|
|
595
|
+
|
|
596
|
+
`to_markdown` is the escape hatch for when the user wants the document itself
|
|
597
|
+
rather than an answer drawn from it. It writes to disk and returns a path, so
|
|
598
|
+
nothing enters the model's context and document size stops mattering.
|
|
599
|
+
|
|
600
|
+
Parsed results are cached on disk, so re-parsing the same file with the same
|
|
601
|
+
options is free. The cache key includes the file's size and mtime — edit the
|
|
602
|
+
document and the next `parse` re-parses it automatically.
|
|
603
|
+
|
|
604
|
+
### Configuration
|
|
605
|
+
|
|
606
|
+
| Variable | Effect |
|
|
607
|
+
| --- | --- |
|
|
608
|
+
| `DOCSLICER_MCP_ROOT` | Restrict file sources **and** written output to this directory tree |
|
|
609
|
+
| `DOCSLICER_MCP_ALLOW_URLS` | Set to `0` to reject `http(s)` sources |
|
|
610
|
+
| `DOCSLICER_MCP_CACHE` | Where parsed results are persisted (default `~/.cache/docslicer-mcp`) |
|
|
611
|
+
| `DOCSLICER_MCP_CACHE_MAX_MB` | Cache size ceiling, oldest pruned first (default `2048`; `0` disables) |
|
|
612
|
+
|
|
613
|
+
Set `DOCSLICER_MCP_ROOT` when exposing the server to anything but yourself —
|
|
614
|
+
without it, any readable path on the machine is parseable, and `to_markdown`
|
|
615
|
+
can write anywhere the server process can.
|
|
616
|
+
|
|
617
|
+
---
|
|
618
|
+
|
|
619
|
+
## Command line
|
|
620
|
+
|
|
621
|
+
`docslicer` parses one document to JSON on stdout — for a quick look at a file,
|
|
622
|
+
or to pipe into `jq`.
|
|
623
|
+
|
|
624
|
+
```bash
|
|
625
|
+
docslicer report.pdf # chunks as JSON
|
|
626
|
+
docslicer report.pdf -o chunks.json # write to a file
|
|
627
|
+
docslicer report.pdf --no-chunking # blocks instead of chunks
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
It takes the same parsing and chunking options as `parse_document`; run
|
|
631
|
+
`docslicer --help` for the full list.
|
|
632
|
+
|
|
633
|
+
---
|
|
634
|
+
|
|
438
635
|
## Format-specific functions
|
|
439
636
|
|
|
440
637
|
If you know the format upfront and want explicit failure on unexpected input, use the
|
|
@@ -449,6 +646,47 @@ docslicer.parse_html("filing.html")
|
|
|
449
646
|
|
|
450
647
|
---
|
|
451
648
|
|
|
649
|
+
## Privacy Policy
|
|
650
|
+
|
|
651
|
+
Full policy: <https://docslicer.ai/privacy>
|
|
652
|
+
|
|
653
|
+
**What is collected.** Nothing. DocSlicer has no telemetry, analytics, crash
|
|
654
|
+
reporting, or usage tracking, and requires no account, licence key, or
|
|
655
|
+
registration.
|
|
656
|
+
|
|
657
|
+
**How your documents are used.** Parsing runs entirely on your own machine, in a
|
|
658
|
+
local process. Document contents are used only to produce the outline, text
|
|
659
|
+
slices, search results, and markdown you ask for, and are returned only to the
|
|
660
|
+
caller. Documents are never uploaded to DocSlicer or to any third party. When
|
|
661
|
+
running as an MCP server, `DOCSLICER_MCP_ROOT` bounds which directory tree may
|
|
662
|
+
be read from and written to.
|
|
663
|
+
|
|
664
|
+
**Where data is stored, and for how long.** Parsed results are cached on your
|
|
665
|
+
own disk — by default `~/.cache/docslicer-mcp`, configurable with
|
|
666
|
+
`DOCSLICER_MCP_CACHE`. The cache is pruned to a size ceiling
|
|
667
|
+
(`DOCSLICER_MCP_CACHE_MAX_MB`, default 2048 MB); otherwise it persists until you
|
|
668
|
+
delete it, and deleting the directory removes it permanently with no copy
|
|
669
|
+
retained elsewhere. Nothing is written outside the cache directory and any
|
|
670
|
+
output path you supply.
|
|
671
|
+
|
|
672
|
+
**Network access and third parties.** No network request is made for a local
|
|
673
|
+
file. Requests leave your machine only when you pass an `http(s)` source: that
|
|
674
|
+
URL is fetched directly from the host you named, and for HTML pages Playwright
|
|
675
|
+
may load the subresources that page references, exactly as a browser would.
|
|
676
|
+
Requests to `sec.gov` send a `User-Agent` header identifying the client, as the
|
|
677
|
+
SEC fair-access policy requires. These hosts are third parties chosen by you,
|
|
678
|
+
not by DocSlicer, and their own policies govern what they log. Set
|
|
679
|
+
`DOCSLICER_MCP_ALLOW_URLS=0` to reject remote sources entirely.
|
|
680
|
+
|
|
681
|
+
**Third-party clients.** When DocSlicer runs as an MCP server, the client
|
|
682
|
+
(Claude, Cursor, …) handles the conversation under its own privacy policy.
|
|
683
|
+
DocSlicer is not a party to that and receives nothing from it.
|
|
684
|
+
|
|
685
|
+
**Contact.** Privacy questions: jelle@docslicer.ai · Issues:
|
|
686
|
+
<https://github.com/DocSlicer/DocSlicer/issues>
|
|
687
|
+
|
|
688
|
+
---
|
|
689
|
+
|
|
452
690
|
## License
|
|
453
691
|
|
|
454
692
|
DocSlicer is **dual-licensed**:
|
|
@@ -457,3 +695,15 @@ DocSlicer is **dual-licensed**:
|
|
|
457
695
|
- **[Commercial license](LICENSE-COMMERCIAL.md)** — for embedding DocSlicer in a closed-source or proprietary product, or offering it as part of a hosted/SaaS service without releasing your source.
|
|
458
696
|
|
|
459
697
|
See [LICENSE-COMMERCIAL.md](LICENSE-COMMERCIAL.md) for details, or reach out about a commercial license.
|
|
698
|
+
|
|
699
|
+
---
|
|
700
|
+
|
|
701
|
+
<!--
|
|
702
|
+
Ownership marker for the official MCP registry (registry.modelcontextprotocol.io).
|
|
703
|
+
The registry verifies a PyPI package by looking for this exact line in the
|
|
704
|
+
project description, which is this README — so it only takes effect once a
|
|
705
|
+
release carrying it is published. Namespace matches the GitHub org that owns
|
|
706
|
+
the repository. Leave it in place; removing it breaks re-publication.
|
|
707
|
+
-->
|
|
708
|
+
|
|
709
|
+
mcp-name: io.github.DocSlicer/docslicer
|