Metadata-Version: 2.5
Name: jdocmunch-mcp
Version: 1.150.0
Summary: Token-efficient MCP server for structured documentation retrieval via section-level indexing
Project-URL: Homepage, https://github.com/jgravelle/jdocmunch-mcp
Project-URL: Repository, https://github.com/jgravelle/jdocmunch-mcp
Project-URL: Bug Tracker, https://github.com/jgravelle/jdocmunch-mcp/issues
Author-email: "J. Gravelle" <j@gravelle.us>
License-Expression: LicenseRef-jDocMunch-Dual-Use
License-File: LICENSE
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27.0
Requires-Dist: mcp<2.0.0,>=1.10.0
Requires-Dist: pathspec>=0.12.0
Requires-Dist: pyyaml>=6.0
Requires-Dist: watchfiles>=0.21.0
Provides-Extra: anthropic
Requires-Dist: anthropic>=0.40.0; extra == 'anthropic'
Provides-Extra: fastembed
Requires-Dist: fastembed>=0.8.0; extra == 'fastembed'
Provides-Extra: gemini
Requires-Dist: google-generativeai>=0.8.0; extra == 'gemini'
Provides-Extra: minimax
Requires-Dist: openai>=1.0.0; extra == 'minimax'
Provides-Extra: ocr
Requires-Dist: markitdown[docx,pdf,pptx]>=0.1.6; extra == 'ocr'
Requires-Dist: pillow>=10.0; extra == 'ocr'
Requires-Dist: pypdfium2>=4.30; extra == 'ocr'
Requires-Dist: winrt-runtime>=3.0; (sys_platform == 'win32') and extra == 'ocr'
Requires-Dist: winrt-windows-foundation-collections>=3.0; (sys_platform == 'win32') and extra == 'ocr'
Requires-Dist: winrt-windows-foundation>=3.0; (sys_platform == 'win32') and extra == 'ocr'
Requires-Dist: winrt-windows-globalization>=3.0; (sys_platform == 'win32') and extra == 'ocr'
Requires-Dist: winrt-windows-graphics-imaging>=3.0; (sys_platform == 'win32') and extra == 'ocr'
Requires-Dist: winrt-windows-media-ocr>=3.0; (sys_platform == 'win32') and extra == 'ocr'
Requires-Dist: winrt-windows-storage-streams>=3.0; (sys_platform == 'win32') and extra == 'ocr'
Provides-Extra: office
Requires-Dist: markitdown[docx,pdf,pptx]>=0.1.6; extra == 'office'
Provides-Extra: openai
Requires-Dist: openai>=1.0.0; extra == 'openai'
Provides-Extra: semantic
Requires-Dist: google-generativeai>=0.8.0; extra == 'semantic'
Provides-Extra: zhipu
Requires-Dist: openai>=1.0.0; extra == 'zhipu'
Description-Content-Type: text/markdown

<!-- mcp-name: io.github.jgravelle/jdocmunch-mcp -->

# jDocMunch MCP

**jDocMunch is an MCP server for coding agents that retrieves the exact documentation section a task needs, without loading whole files into the context window.**

Index a documentation set once by heading hierarchy, then fetch a single section, a heading subtree, or a ranked search result — extracted byte-precisely from the original file.

[**Install**](#install) · [**Quickstart**](#quickstart) · [**Benchmarks**](benchmarks/) · [**Commercial licensing**](#licensing-and-commercial-use)

[![PyPI version](https://img.shields.io/pypi/v/jdocmunch-mcp)](https://pypi.org/project/jdocmunch-mcp/)
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/jdocmunch-mcp)](https://pypi.org/project/jdocmunch-mcp/)
![License](https://img.shields.io/badge/license-dual--use-blue)
![MCP](https://img.shields.io/badge/MCP-compatible-purple)
![Local-first](https://img.shields.io/badge/local--first-yes-brightgreen)
[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.20102349.svg)](https://doi.org/10.5281/zenodo.20102349)

**Free for personal use.** Commercial use requires a paid license — [terms below](#licensing-and-commercial-use).

---

## Why jDocMunch?

**The problem.** An agent asked "how do I configure authentication?" opens a documentation file, skims hundreds of paragraphs it does not need, opens another, and repeats. Large context windows do not fix this. They just make the waste affordable enough to ignore until the bill arrives, and they crowd out the context the model actually needed.

**The mechanism.** jDocMunch parses a documentation set into a section tree keyed by heading hierarchy, stores each section's byte offsets into the original file, and exposes retrieval over MCP. Sections keep durable identities across re-indexing as long as path, heading text, and heading level are unchanged.

**The outcome.** The unit of access changes from *file* to *section*. An agent retrieves the installation section, one configuration block, or a specific heading subtree — and nothing else.

---

## What makes it different

### Section-first retrieval
Search and retrieve documentation by section, not just file path or keyword match.

### Byte-precise extraction
Full content is pulled on demand from exact byte offsets into the original file.

### Stable section IDs
Sections retain durable identities across re-indexing when path, heading text, and heading level remain unchanged.

---

## Evidence

Four benchmarks against public documentation corpora, each with the corpus, date, and per-query results recorded in [`benchmarks/`](benchmarks/).

| Corpus | Scale | Indexed in | Result |
|---|---|---|---|
| [Kubernetes](benchmarks/jDocMunch_Benchmark_Kubernetes.md) (`kubernetes/website`, 2026-03-04) | 1,569 `.md` files, 4,355 sections, 16 MB | 3,352 ms | 27,285 tokens saved on a single node-affinity query; 100 ms latency |
| [SciPy](benchmarks/jDocMunch_Benchmark_SciPy.md) | 10,402 sections, ~855,000 corpus tokens | 2,247 ms | 135–152 ms per query across sparse-solver, FFT, and optimization lookups |
| [LangChain](benchmarks/jDocMunch_Benchmark_LangChain_MDX.md) (MDX) | 5,973 sections | 5,204 ms | MDX-aware sectioning found 754% more sections than the naive pass |
| [Wiki](benchmarks/jDocMunch_Benchmark_Wiki.md) | 7,449-token corpus | — | Search returns ranked metadata in ~190 tokens against a 7,449-token whole-corpus read |

**Read these as per-corpus results, not as a single headline multiple.** Savings depend on how large the containing file is relative to the section you needed: a small file with one heading saves almost nothing, and the Kubernetes corpus saves a great deal. The benchmark files record the queries that did poorly alongside the ones that did well.

A separate, measured result from the [v1.121.0](CHANGELOG.md) projection work, on this repository's own docs at `max_results=10`: a search row went **1,989 chars → 319 with `compact=true` (−84%)**, or 431 with `snippet_bytes=200` (−78%) while removing the follow-up `get_section` call entirely.

**Retrieval quality is gated, not assumed.** Every release runs a replay fixture over a frozen golden set and fails below **nDCG 0.95**. That gate has failed builds and blocked releases; it is not decorative.

---

## Install

**Requirements:** Python 3.10+, any MCP-compatible client.

```bash
uv tool install jdocmunch-mcp
jdocmunch-mcp init
```

No virtualenv to manage, nothing written into system Python, and it works as-is on PEP 668 distros (Ubuntu 24.04+, Debian 12+) where bare `pip install` is refused. [Don't have `uv` yet?](https://docs.astral.sh/uv/getting-started/installation/)

`init` detects your MCP clients, writes their config entries, installs the doc-exploration prompt policy so your agent actually reaches for the tools, and optionally installs hooks and indexes your docs.

<details>
<summary><b>Other install paths</b></summary>

| Command | Use it when |
|---|---|
| `uvx jdocmunch-mcp` | **Zero install.** Runs from an ephemeral environment — nothing lands on disk permanently. The client entries `init` writes already invoke the server this way, so for most setups this is all that ever runs. ⚠ Hooks are the exception: they're spawned by a minimal-PATH subshell and resolve the executable by name, so they need `uv tool install` (or `pipx`/`pip`) to work. |
| `pipx install jdocmunch-mcp` | You already standardise on pipx |
| `pip install jdocmunch-mcp` | Inside a virtualenv you manage yourself |

</details>

Verify:

```bash
jdocmunch-mcp --version
```

**Manual Claude Code setup:**

```bash
claude mcp add -s user jdocmunch -- uvx jdocmunch-mcp
```

No install step — `uvx` fetches and runs the server on demand. Prefer it on your PATH (and required for hooks)? `uv tool install jdocmunch-mcp`, then `claude mcp add -s user jdocmunch jdocmunch-mcp`.

Installing the server makes the tools available; it does not break an agent's habit of brute-reading files. One line in your `CLAUDE.md` does that:

```markdown
Call the jdocmunch_guide tool and strictly follow its instructions.
```

---

## Quickstart

**Assumes:** jDocMunch installed and registered with your client, and a folder of documentation.

Index a local documentation folder:

```bash
jdocmunch-mcp index-local --path ./docs
```

It prints JSON naming the corpus and what it found:

```json
{
  "success": true,
  "repo": "local/docs",
  "file_count": 1,
  "section_count": 4,
  "doc_types": { ".md": 1 },
  "semantic_search": false
}
```

`section_count` greater than `file_count` is the whole point: the index addresses headings, not files.

Then, inside your agent:

> Using jdocmunch, search the docs for "authentication configuration" and show me that section.

The agent should call `search_sections`, then `get_section` on the top hit — returning one section rather than a file. `_meta.tokens_saved` on the `get_section` response is a modeled saving: the containing document's size minus the section served (`_meta.savings_basis: "modeled"`). `search_sections` itself earns no credit.

**Next step:** `get_toc_tree` for a structural view of the whole corpus, or `index_repo` to index documentation straight from a GitHub repository.

### Very large folders

An MCP client stops waiting on a tool call after a fixed time. Indexing a folder with tens of thousands of files can take longer than that. On one Windows machine, a full index of 10,000 files (271,585 sections) took 67 s without embeddings, and sentence-transformers embeddings added about 3 minutes.

If a folder is that large, or an `index_local` call times out, index it from a shell:

```bash
jdocmunch-mcp index-local --path /path/to/folder
```

The CLI runs the same indexing with no tool timeout. It writes to the same store as the MCP server, so the server uses the index once the command finishes. If you set `DOC_INDEX_PATH` in your client's MCP config, set it in the shell too.

- **Custom names.** If the folder was indexed under a custom name, pass the same `--name`. Without it, the CLI uses the folder name and writes a second index.
- **The file cap.** A folder is capped at 10,000 files, newest first. Raise it with `--max-files N` (the CLI form of the MCP tool's `max_files`). When a call hits the cap, its warning prints the command with the folder's discovered count.
- **Narrowing a folder.** Exclude what you don't need with `--extra-ignore-pattern` (repeatable, the CLI form of `extra_ignore_patterns`), or index only listed files with `--paths-from FILE` (the CLI form of `paths`).
- **Embeddings.** `--no-embeddings` builds a lexical index first. Run the command again later with `--rebuild` to add embeddings.

### Scanned PDFs

A scanned PDF is a picture of text, with no text layer to index. The optional `[ocr]` extra reads them with an OCR engine **already on your machine**. jDocMunch never installs or downloads an engine, a language pack or a model.

```bash
pip install "jdocmunch-mcp[ocr]"
```

**Engines.** jDocMunch looks for one in this order and uses the first that answers:

1. `JDOCMUNCH_TESSERACT_PATH`, if you set it.
2. `tesseract` on `PATH`.
3. Tesseract's default install folders: `C:\Program Files\Tesseract-OCR`, the per-user folder under `%LOCALAPPDATA%\Programs`, `/opt/homebrew/bin`, `/usr/local/bin` and `/usr/bin`.
4. On Windows, the built-in Windows OCR, if an English OCR language is installed (it is on English-language Windows).

`JDOCMUNCH_OCR_ENGINE=tesseract` or `windows` picks one. `JDOCMUNCH_OCR_LANG` passes Tesseract languages that are already installed (default `eng`).

**OCR needs two keys.** It runs only when both are set:

- **You** set `JDOCMUNCH_OCR=1` in your MCP client's `env` block (or your shell, for the CLI).
- **The call** asks for it: `index_local(ocr=true)`, or `index-local --ocr` on the CLI.

So an agent can't turn OCR on by itself. The choice is stored with the index, and later refreshes and the watcher reuse it; `ocr=false` or `--no-ocr` turns it off. Every response that ran with OCR requested has an `ocr` block saying whether it was enabled in config, whether it was used, how much it read and with which engine.

**What you get.** Each scanned image becomes a `## Page N` section. Every section from OCR is marked `text_source: "ocr"`, with `ocr_engine`, `ocr_pages` and (Tesseract only) `ocr_confidence`. The document opens with a visible note that its text came from OCR, and the response warns about it. Any part of a PDF with its own text layer keeps it. OCR sections earn no token-savings credit.

**How good it is.** On 6 real public-domain scans of printed prose (*Encyclopaedia Britannica* 1911 and *The American Printer*), Tesseract 5.4 found 97.4% of the words with a 2.0% character error rate, and Windows OCR 97.1% and 2.0%. The limits:

- **Tables and forms may come out scrambled.** The words are mostly there, so a search can find them, but columns and fields lose their layout. On two scanned forms, Tesseract found 25% to 80% of the words.
- **Decorative and black-letter (Gothic) type isn't read.** Neither engine reads it.
- **Handwriting isn't read.**

**Speed.** Tesseract took 3.4 s per image here (Windows OCR 0.3 s): a 20-image scan took 69 s with Tesseract and 6 s with Windows OCR. An MCP call gives OCR a 60-second budget (`ocr_budget_seconds`), so **with Tesseract, a scan of 20 images or more may not finish in one call.** It's skipped as `ocr_budget_exhausted`. What was already read is cached, and the next call carries on. For a folder of scans, run the CLI, which has no budget:

```bash
JDOCMUNCH_OCR=1 jdocmunch-mcp index-local --path /path/to/scans --ocr
```

The first 50 scanned images of a document are read (`ocr_max_pages`, `--ocr-max-pages`); the rest are reported as `ocr_pages_capped`.

**Privacy.** OCR runs on your machine. Tesseract is a local program, and Windows OCR is an on-device Windows API; no page leaves the machine. But OCR turns scans into searchable text. If you OCR tax forms, pay stubs, IDs or medical records, their contents (names, SSNs, account numbers) become readable to any agent that can query this index, and they're stored in the index's text cache on disk. jDocMunch's secret-file rules match file names, not contents, so they won't catch a scanned W-2. Keep such files out of indexed folders, or exclude them with `extra_ignore_patterns`.

---

## What you can do

- **Retrieve one section instead of a document.** `get_section` and `get_sections` pull byte-precise content from the original file; `get_section_excerpt` narrows further.
- **Search by meaning, not just keywords.** `search_sections` fuses BM25 with semantic cosine when an embedding provider is configured. `compact=true`, `fields=[...]`, and `snippet_bytes=N` cut the response further.
- **Navigate structure.** `get_toc`, `get_toc_tree`, `get_section_path`, `get_section_descendants`, and `section_neighbors` traverse the heading tree without reading content.
- **Find what documentation is missing or rotting.** `get_doc_coverage`, `get_undocumented_symbols`, `get_stale_pages`, `get_orphan_sections`, `get_broken_links`, and `doc_health_radar`.
- **Work across API specs.** `find_endpoint`, `list_endpoints_by_tag`, `find_operations_using_schema`, and `get_schema_graph` treat OpenAPI documents as first-class.
- **Preflight documentation changes.** `check_section_delete_safe` and `get_section_blast_radius` before you remove or restructure.
- **Know when an answer is stale.** Content reads disclose `_meta.freshness`, `_meta.verdict`, and which source layer answered.

64 tools in total. The full reference is in [USER_GUIDE.md](USER_GUIDE.md).

---

## How it works

Everything runs locally. Indexes live under your home directory; no hosted service is required for indexing or retrieval.

```text
docs/ ──► parser (per format) ──► section tree ──► local index
                                                      │
                          MCP client ◄── retrieval ◄──┘
```

- **Parsing** is per format, one module each: Markdown/MDX, reStructuredText, AsciiDoc, Jupyter notebooks, HTML, plain text, OpenAPI (YAML), JSON/JSONC, XML/SVG/XHTML, Godot scenes, and — via the optional `[office]` extra — PDF, DOCX, PPTX, and EPUB.
- **Storage** is a versioned local index (`INDEX_VERSION = 3`) that auto-migrates on first load. A 1.x release never forces a reindex.
- **Retrieval** is lexical BM25 by default, hybrid when embeddings are available.
- **Embeddings are optional and provider-agnostic** — Gemini, OpenAI, an OpenAI-compatible endpoint, or a local offline model through either FastEmbed (ONNX) or sentence-transformers (torch). Without one, search stays lexical and entirely offline.

Deeper detail: [ARCHITECTURE.md](ARCHITECTURE.md) and [SPEC.md](SPEC.md).

### Memory in long-running processes

A parsed index takes about 2.5 to 3 times its file's size in memory (measured on five real indexes, 0.5 MB to 112 MB). Two settings keep long-running processes bounded:

- **`JDOCMUNCH_INDEX_CACHE_MAX_BYTES`** caps the indexes the server keeps parsed between calls. The default is 512 MiB, estimated as 3 times each index file's size. Past it, the least recently used index is dropped and re-read when it's next needed. A single index larger than the cap is still kept. `0` or less turns the byte cap off; the 8-index cap stays either way.
- **`JDOCMUNCH_WATCH_INPROCESS=1`** makes the watcher re-index in its own process instead of a child per batch (see below).

---

## Security and privacy

Local-first by design. Your documentation is parsed and stored on your machine, and the base package's only default network behavior is an anonymous savings counter — a random ID plus aggregate token counts, no content, no paths, no PII.

Opt out completely:

```bash
JDOCMUNCH_SHARE_SAVINGS=0
```

Embedding and summarizer providers call their configured API **only when you enable them**, and never by default. `watch-install` registers a login service **only** when you run it yourself.

### Background behavior, fully disclosed

**A model download, the first time a local embedding provider runs.** Both
offline providers fetch their model from HuggingFace on first use and cache it
on disk. Nothing downloads until you enable embeddings, and a lexical-only
install never contacts the hub. Startup warmup is **skipped** when the model is
not already cached, so a first run defers the download to your first search
rather than stalling the MCP handshake behind it
([#110](https://github.com/jgravelle/jdocmunch-mcp/issues/110)).

**A package install and a model download, only if you say yes to `init`.**
`jdocmunch-mcp init` checks whether an embedding provider is available. If none
is, it asks once whether to turn semantic search on. **The default answer is
no.** On a yes it does two things and prints both before doing them:

- runs `python -m pip install "fastembed>=0.8.0"` in the Python environment
  jdocmunch is running in (about 160 MB installed; pulls onnxruntime, not torch);
- downloads one model file, `all-MiniLM-L6-v2` in ONNX form, from
  huggingface.co (87 MB, once).

After that, embedding runs on your machine. No text from your documents is sent
anywhere. `--yes` does **not** answer this question for you: it accepts config
edits, not a download. A scripted install opts in with `--with-embeddings`.
`--dry-run` prints the command and the sizes and touches nothing. If pip is not
usable (a `uv tool` or `pipx` install has none) or the install fails, `init`
prints the manual command for your install style and carries on with word
matching. The MCP server itself never installs or downloads anything unasked.

Why `init` asks at all: on a public benchmark of 492 real user questions over 6
documentation sets, semantic (hybrid) search put a direct answer in the top 5
results for 47% of questions, against 34% for word matching alone. Method, data
and the negative result that came with it:
[jdoc-rerank-bench](https://github.com/jgravelle/jdoc-rerank-bench). Measured on
one CPU-only machine, embedding added about 7 seconds per 1,000 sections to a
first index. `doc_list_repos` reports `has_embeddings` per index, so you can see
which ones are still words-only.

**FastEmbed as the offline provider.** `pip install jdocmunch-mcp[fastembed]`
runs the same `all-MiniLM-L6-v2` model through onnxruntime instead of torch,
which is a much smaller install. When both offline providers are present
FastEmbed is preferred; `JDOCMUNCH_EMBEDDING_PROVIDER=sentence-transformers`
selects the other one. On the shared model the two write the **same** vector
store, so switching runtimes does not re-embed your corpus. Point FastEmbed at
a different model with `JDOCMUNCH_FASTEMBED_MODEL` and it keeps its own vectors
instead, because vectors from two models are not interchangeable
([#126](https://github.com/jgravelle/jdocmunch-mcp/issues/126)).

**Where the FastEmbed model lives, and repairing it.** FastEmbed's own default
folder is `<temp dir>/fastembed_cache`, and OS cleanup can empty it. When the
model can't load, `index_local` says so in `embedding_provider_error` and a
warning, and search is word matching until it is fixed. `init` checks the model
from local files only, without a download. If the model is missing or damaged,
it offers a repair, and the repair asks for the same consent as the install: a
yes at the prompt, or `--with-embeddings`. The repair downloads the model
(87 MB, once) into `~/.cache/jdocmunch-mcp/fastembed`, outside the temp
directory. The server loads from that folder once the model is there; an
install whose temp-dir copy works keeps using it and is never re-downloaded.
`JDOCMUNCH_FASTEMBED_CACHE` names a different folder, and FastEmbed's own
`FASTEMBED_CACHE_PATH` still takes precedence over both.

**A local OCR program, only if you turn OCR on.** With the `[ocr]` extra,
`JDOCMUNCH_OCR=1` in config and `ocr=true` on the call (or `--ocr`),
jDocMunch runs Tesseract as a short-lived child process once per scanned page,
or calls Windows' on-device OCR. Neither opens a network connection. The text
is cached under your doc-index directory (`.office_cache/ocr`). Nothing runs in
the background and nothing is installed. See [Scanned PDFs](#scanned-pdfs) for
what OCR'd text exposes.

**A child process per watcher batch.** The watcher (`jdocmunch-mcp watch`, or
the `jdocmunch-watch` login service) re-indexes each batch of changed files in a
short-lived child, `python -m jdocmunch_mcp.watch_batch`. A re-index rewrites
the whole index file, and memory used for that doesn't return to the system in
a long-running process, so the watcher's memory used to climb toward its limit
([#189](https://github.com/jgravelle/jdocmunch-mcp/issues/189)). The child
returns it when it exits. It:

- runs once per batch, and exits when the batch is done;
- opens no network connection of its own; it calls only what indexing already
  calls, such as your embedding or summary provider if you set one;
- starts its embedding provider fresh each time. On one Windows PC, a
  one-file batch took 0.3 s in-process, 0.55 s in a child, and 1.7 s in a child
  with FastEmbed on (the model loads each time). sentence-transformers wasn't
  measured and loads more slowly;
- on Windows, opens no console window.

`JDOCMUNCH_WATCH_INPROCESS=1` restores the previous in-process re-index.

**A child process, when local embeddings are in use.** When the
`sentence-transformers` provider is active, jDocMunch runs the embedding model
in a **child process** (`python -m jdocmunch_mcp.embeddings.worker`) instead of
inside the server. It:

- starts when something first needs an embedding — at startup if the model is
  already in your local HuggingFace cache, otherwise on the first search or
  index that uses it. A lexical-only install never spawns it;
- opens **no network connection** and speaks only to its parent, over a private pipe;
- exits when the server exits, and is killed if it stops responding;
- is **not** a login service, is not registered anywhere, and survives nothing.

This exists because importing the embedding stack inside the server process can
deadlock in the Windows loader
([#118](https://github.com/jgravelle/jdocmunch-mcp/issues/118)), hanging every
tool call for as long as the server runs. Disable it with
`JDOCMUNCH_EMBED_WORKER=0`, which restores the previous in-process import.

**A login service, only if you install one.** `jdocmunch-mcp watch-install`
registers the doc watcher to start at login (systemd user unit, launchd agent,
or a Task Scheduler task named `jdocmunch-watch`). Nothing installs it for you.
Once installed it:

- re-indexes every locally-indexed doc repo when a doc file on disk changes;
- runs **exactly** `jdocmunch-mcp watch` with the flags you passed to
  `watch-install` — `--no-ai-summaries` to keep the summarizer out of it,
  `--quiet` to suppress its per-change log lines;
- writes to `watch.log` and `watch.err` under your doc-index directory;
- is removed by `jdocmunch-mcp watch-uninstall`.

⚠ Re-running `watch-install` **rewrites** the service definition, so a
hand-edited one is replaced. It now prints what it replaced; pass the flags to
`watch-install` itself so an upgrade keeps them
([#120](https://github.com/jgravelle/jdocmunch-mcp/issues/120)).

Path traversal prevention, symlink escape protection, secret exclusion, file-size limits, binary detection, and encoding safety are documented in [SECURITY.md](SECURITY.md), along with how to report a vulnerability.

---

## Limitations

- **Section retrieval helps least on small files.** If a document has one heading and 40 lines, retrieving the section and reading the file cost about the same.
- **Semantic search requires an embedding provider.** Without one, search is lexical only — good for identifiers and exact phrasing, weaker for paraphrased questions.
- **Office formats need the optional `[office]` extra** and are supported for local indexing only. A scanned (image-only) PDF has no text layer, so without OCR it is not indexed. The response names it under `skipped_paths.no_extractable_text`, adds a warning, and reports `coverage_complete: false`.
- **OCR is optional, off by default, and modest.** With the `[ocr]` extra, an engine already on your machine and both keys set, scanned pages are read (see [Scanned PDFs](#scanned-pdfs)). Printed prose reads well. Tables and forms may come out scrambled, and decorative, black-letter and handwritten text isn't read.
- **Freshness is disclosed, not guaranteed.** A section whose source cannot be checked is reported as `unknown` rather than assumed current.
- **jDocMunch does not parse code.** Symbols, signatures, and call graphs belong to [jcodemunch-mcp](https://github.com/jgravelle/jcodemunch-mcp); tabular data belongs to [jdatamunch-mcp](https://github.com/jgravelle/jdatamunch-mcp).

---

## Documentation

| Doc | What it covers |
|-----|----------------|
| [USER_GUIDE.md](USER_GUIDE.md) | Full tool reference, workflows, and best practices |
| [ARCHITECTURE.md](ARCHITECTURE.md) | Storage model, parsing pipeline, extension points |
| [SPEC.md](SPEC.md) | Response contracts and reason-code vocabulary |
| [SECURITY.md](SECURITY.md) | Security controls and vulnerability reporting |
| [TOKEN_SAVINGS.md](TOKEN_SAVINGS.md) | How savings are counted and reported |
| [CONTRIBUTING.md](CONTRIBUTING.md) | Development setup and the CLA requirement |
| [CHANGELOG.md](CHANGELOG.md) · [ROADMAP.md](ROADMAP.md) | Release history and what's next |

---

## Licensing and commercial use

Released under the **jDocMunch-MCP Dual-Use License** ([full terms](LICENSE)). **Free for non-commercial use. Commercial use requires a paid license**, one-time, sold by jMunch LLC.

**jDocMunch only:** [Builder, $29](https://jcodemunch.com/descriptions.php#builder) (1 developer) · [Studio, $99](https://jcodemunch.com/descriptions.php#studio) (up to 5) · [Platform, $499](https://jcodemunch.com/descriptions.php#platform) (org-wide internal deployment)

**Full jMunch suite (code + docs + data):** [Trio Builder, $99](https://jcodemunch.com/descriptions.php#builder) · [Trio Studio, $449](https://jcodemunch.com/descriptions.php#studio) · [Trio Platform, $2,499](https://jcodemunch.com/descriptions.php#platform)

Individual developers and non-commercial projects need no license. Organizations deploying jDocMunch across internal teams do.

### 1.x compatibility commitment

Every 1.x license entitles you to every future 1.x release. We will never ship a 1.x version that:

- removes or renames an MCP tool (deprecated tool names keep their aliases),
- drops a `Section` field from the response shape,
- forces a reindex without auto-migrating your existing index on first load,
- changes the JSON wire format of any tool response in a way that breaks an existing consumer,
- or makes a previously-default behavior raise.

Anything that would require breaking these promises is reserved for a future major version (2.x). The full machine-checked contract is enforced via `tests/test_server.py` (tool-name and required-field invariants) and the replay-fixture gate that runs on every release.

---

## Support and project status

Actively maintained. Issues and bug reports: [GitHub Issues](https://github.com/jgravelle/jdocmunch-mcp/issues). Security reports: see [SECURITY.md](SECURITY.md). Commercial licensing questions go through [jcodemunch.com](https://jcodemunch.com/).

Part of the jMunch suite alongside [jcodemunch-mcp](https://github.com/jgravelle/jcodemunch-mcp) (code symbols) and [jdatamunch-mcp](https://github.com/jgravelle/jdatamunch-mcp) (tabular data). All three implement [jMRI](https://github.com/jgravelle/mcp-retrieval-spec), the open retrieval interface spec.
