Skip to main content

flint-slating

MCP server that reads PDFs and exposes them to LLM consumers as structured Markdown, plus the usual ancillaries: metadata, outline, images, tables.

Designed to pair with a separate "wiki" MCP server that handles the writing side — an agent calls flint-slating to read PDFs and another MCP to persist notes about them into a frontmattered-markdown knowledge base.

What it does

Built on a permissive-license PDF stack:

Library License Role
Docling MIT PDF → Markdown with heading hierarchy, multi-column reading order, and Markdown tables
pypdf BSD-3 metadata, TOC, page count, encryption checks, image enumeration
pdfplumber MIT per-page table extraction

There is no PyMuPDF, no MuPDF, no AGPL or GPL anywhere in the dependency tree. The licenses job of license-check.yml rejects PRs that pull in copyleft transitive deps.

Transports

Two transports off the same MCP server, selected via --transport:

Transport Run via Use case
Streamable-HTTP (default) uvx flint-slating or --transport http Long-lived local daemon, container, or shared service.
stdio uvx flint-slating --transport stdio The standard MCP integration shape — drop into claude_desktop_config.json or any mcp.json.

Run

As an HTTP daemon (default)

uvx flint-slating                    # listens on PORT (default 35833)
curl http://127.0.0.1:35833/health

Or pin it:

uv tool install flint-slating
flint-slating

As a stdio MCP server

uvx flint-slating --transport stdio

Wire into Claude Code's MCP config:

{
  "mcpServers": {
    "flint-slating": {
      "command": "uvx",
      "args": ["flint-slating", "--transport", "stdio"]
    }
  }
}

Docker

docker run --rm \
  -p 35833:35833 \
  -v $(pwd)/pdfs:/pdfs:ro \
  -v flint-slating-data:/data \
  ghcr.io/parkviewlab/flint-slating:latest

Or use docker-compose.yml for a persistent stack.

MCP tools

All PDF tools take a source argument with one of:

  • {"path": "/abs/path/to/file.pdf"} — local file
  • {"url": "https://..."} — streamed to a content-addressed cache
  • {"bytes_b64": "...", "filename": "x.pdf"} — base64 upload (size-capped)
Tool What it does
pdf_info {page_count, metadata, is_encrypted, sha256}
pdf_toc flat outline [{level, title, page}]
pdf_read_text plain text by page range (fast — pypdf, no ML)
pdf_read_markdown high-quality Markdown via Docling (hybrid sync/async — see below)
pdf_read_chunks per-page Markdown chunks with tables/images/toc_items (hybrid sync/async)
pdf_list_images enumerate images: [{page, index, name, width, height, ext}]
pdf_extract_image base64 bytes of one image
pdf_find_tables per-page Markdown tables via pdfplumber
get_job_status poll a background job
get_job_result fetch a finished job's artifact
cancel_job cancel a running job

Hybrid sync/async

pdf_read_markdown and pdf_read_chunks run inline when the number of pages requested is at most SYNC_PAGE_THRESHOLD (default 20; all pages when pages is omitted, so the count is the PDF's page_count then). For larger requests they queue a background job and return a job_id — poll get_job_status until state=="done", then call get_job_result (or, in HTTP mode, fetch output_url directly).

stdio mode transparently waits for the job inline — there's no HTTP server to download from, so the originating tool call blocks until the result is ready and returns it directly.

HTTP endpoints (HTTP mode only)

  • GET /health — {ok, version, uptime_seconds}
  • GET /admin/version — package and dependency versions, and Docling status (docling_model_loaded reports whether the converter has been built, not whether a model is loaded)
  • GET /admin/jobs — recent job list
  • GET /outputs/{job_id}/result.md — finished Markdown
  • GET /outputs/{job_id}/result.json — finished chunked output
  • GET /outputs/{job_id}/log.jsonl — append-only job log
  • POST /sse — MCP Streamable-HTTP transport

Configuration

Env var Default (daemon) Default (container) Purpose
PORT 35833 35833 HTTP bind port
HOST 0.0.0.0 0.0.0.0 HTTP bind address
OUTPUT_ROOT ./output /data/output Per-job output dirs
CACHE_ROOT ./cache /data/cache Materialized URL / base64 PDFs
OUTPUT_EXPIRY_DAYS 7 7 Hourly sweep (HTTP mode only) of job directories older than N days by directory mtime; 0 disables
MAX_INLINE_PDF_BYTES 25 MB 25 MB Cap on base64 upload size
MAX_URL_PDF_BYTES 200 MB 200 MB Cap on URL download size
SYNC_PAGE_THRESHOLD 20 20 Inline-vs-job cutoff for Markdown conversion
JOB_HISTORY_MAX 100 100 Caps the job history kept and the limit of /admin/jobs
MAX_IMAGE_EXTRACT_BYTES 8 MiB 8 MiB Cap on the size of one image returned by pdf_extract_image
DOCLING_ARTIFACTS_PATH unset unset Directory of pre-downloaded Docling models; see Resource notes
PUBLIC_BASE_URL http://localhost:35833 http://localhost:35833 Used to build output_url

Resource notes

  • Docling downloads its layout models (~200–500 MB) on the first conversion unless DOCLING_ARTIFACTS_PATH is set. The container image does not pre-fetch them (pre-fetching dominated multi-arch build time under QEMU), so the first Markdown conversion pays the download. If DOCLING_ARTIFACTS_PATH is set, it must point at a directory that already holds every model (docling-tools models download -o <dir>, for instance into a mounted volume); Docling never downloads into it. Default: unset.
  • OCR follows Docling's default options, which run OCR; there is no setting for it.
  • pypdf, pdfplumber, and the URL / base64 paths are fast and have no ML overhead — use pdf_info, pdf_toc, pdf_read_text, and pdf_find_tables whenever Markdown isn't strictly needed.

Releasing

Tag-driven CI publishes to both PyPI (flint-slating) and GHCR (ghcr.io/parkviewlab/flint-slating):

The release runs from the CLI in the flint-slating-main worktree, as the ParkviewLab handbook's releases.md gives it under "Cutting a release" and "The release's last step":

git pull --ff-only                        # sync main
git -C ../flint-slating-develop pull --ff-only   # sync develop
git merge --no-ff develop                 # promote develop to main
git bump <patch|minor|major|release|X.Y.Z>
git release                               # annotated tag, derived from pyproject.toml
git push --follow-tags                    # the tag push fires the release workflow
git back-merge                            # the release's last step

The release workflow refuses a tag that does not match pyproject.toml's version, that still carries a dev marker (.devN), that is not on origin/main, or that is not greater than the previous release tag.

Commit message convention

After the publish jobs, a changelog job generates the new CHANGELOG.md section — an LLM-written "Highlights" paragraph plus a categorized list written by dev-tools' generate-changelog — commits it back to main, and creates the GitHub Release with the same content as its body. Categorization uses Conventional Commits prefixes (the full list is in the ParkviewLab handbook's commits-and-changelogs.md):

Title Group in the notes Notes
any type with ! after it (feat!:), or a breaking-change footer Breaking changes listed there once, whatever its type
feat: Features user-visible
fix: Bug fixes user-visible
perf: Performance user-visible
refactor: Refactor
docs: Docs
test: Tests
revert: Reverts GitHub's Revert button titles a PR Revert "…", which has no type
build: / chore: / ci: / style: Maintenance
any other title Other changes the whole title
a commit with no pull request Direct commits its subject and short hash

A title without a recognised type is not dropped: it is listed whole under Other changes. So prefix your PR titles, and correct a title before the merge, since retitling afterwards does not change the commit. The groups appear in the order above, and an empty group is left out.

License

Licensed under either of

at your option. In SPDX terms: MIT OR Apache-2.0.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this work by you shall be dual-licensed as above, without any additional terms or conditions. See LICENSING.md.

flint-slating only depends on permissive-licensed libraries; the licenses job of license-check.yml enforces this on every PR. torch and torchvision are pinned to the CPU-only PyTorch wheel index for the GHCR image and for uv sync from a checkout (uv.lock resolves torch to the CPU wheels), so those do not bundle NVIDIA's proprietary CUDA libraries. A PyPI install (uvx flint-slating, uv tool install flint-slating) does not read that pin and gets PyPI's torch, which on Linux pulls NVIDIA's CUDA libraries unless torch is installed from the CPU index. Inference runs on CPU on Linux/Windows and on MPS (Metal) on Apple Silicon. See THIRD_PARTY_LICENSES.md for the per-dependency license breakdown.


© 2026 Gary Frattarola · Licensed under MIT OR Apache-2.0 · part of ParkviewLab

Metadata

Release files for flint-slating 0.1.9

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for flint-slating 0.1.9
File Size Uploaded
flint_slating-0.1.9.tar.gz 177.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for flint-slating 0.1.9
File Interpreter ABI Platform
flint_slating-0.1.9-py3-none-any.whl Python 3 none any Details

Total release size: 213.2 kB

Release files / flint_slating-0.1.9.tar.gz

Download URL flint_slating-0.1.9.tar.gz
Size 177.3 kB
Tags Source
SHA-256 checksum
How to use checksums
3237a7f1b7cea97056ea6b97f1484ce6dae4b3b7f551a7787aa0afd48652d76a
BLAKE2b-256 checksum
How to use checksums
e824a3d1db23a51371f9d22d8e10672db1affd2392a1c2bee784eb1b7ebbc63e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.

Transparency log

Release files / flint_slating-0.1.9-py3-none-any.whl

Download URL flint_slating-0.1.9-py3-none-any.whl
Size 35.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3fc79f450d0cbbc031d563e8b2e3fd7b699f8c72b53c3ca4a60cd555b1cdbbc2
BLAKE2b-256 checksum
How to use checksums
8a612745ff1e6268e76af2bfbd948db6abc4a6a3be206e252ae39d7e24048ff0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.9 This release

2 release files

0.1.8

2 release files

0.1.7

2 release files

0.1.6

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page