Repository navigation
Releases: sfrangulov/minirag-mcp
Release list
v1.0.0 — Document labels (type, project, date)
Documents can now carry labels — a type, a project, any facet your corpus defines — and a document date, computed locally at ingest and usable as filters. "The calls on project X from last week" and "the specs about Y" become one filtered listing or search. Merged in #20.
Added
.minirag/classify.tomlin the document root turns labels on. It defines facets and their values, each with a one-sentence description. A value comes from the longest matching path rule, then the facet'sdefault, then the model, which is asked only insidewithin.override_atlets a confident model answer replace a path rule. Without the file nothing changes. See "Document labels (optional)" in the README.[classify]extra. A small typed-decision model (Plumb-4B, Apache-2.0, a pinned GGUF) runs in-process throughllama-cpp-python. It is loaded per job and released after, and it is never loaded at server start or bystatus,list_filesor a query. Installing the extra compiles llama.cpp (Xcode command-line tools on macOS); the base install is unchanged.- Document dates.
- Where a date comes from, in order: a header in the document (mail
Date:/Sent:, "Date: …" fields, Teams recording headers, a Russian outgoing-number line, a short title line); the file name; or the file's creation time. Each date says which source it came from. - Never from the body: a cover letter does not get the date of the contract it cites.
- Where a date comes from, in order: a header in the document (mail
- Filters.
query_documentsandlist_filestakelabels,dateFromanddateTo; the CLI'squeryandlisttake--label,--date-fromand--date-to.list_filesandlistsort by date (sort="date",--sort date).- Labels and dates appear on
sourcesandfilesrows. - A filtered answer reports
filterCounts: documents in scope left out only for having no label or no date. - It also reports
filterNoticewhen a model facet cannot be answered on this install. - An uncertain label also matches its second choice.
statusgains alabelsblock whenclassify.tomlexists. It lists the facets, value descriptions and counts, the unlabelled and uncertain counts, the model state, or the file's error.minirag-mcp classify try <file>shows every facet's answer and the model's full distribution, and writes nothing.- A
relabelledsync counter. Editingclassify.tomlre-labels from stored text at the next sync, with no re-embedding. A moved file is re-labelled for its new path. A document the model could not answer is retried.
Changed
- Five string columns are added to existing indexes on open, the same additive migration
ocr_engineused. Nothing is re-embedded. - Sync counts always include
relabelled(0 withoutclassify.toml).
Upgrading
Nothing to do unless you want labels.
To try them:
uv tool install 'minirag-mcp[classify]'.- Write
.minirag/classify.tomlin your document root (the README has an example and the pinned model). - Check a few files with
minirag-mcp classify try <file>. - Run
sync.
The first sync labels every indexed document: about 1 s per document on Apple silicon for facets the model answers, nothing for path-only facets.
v0.8.0 — WebVTT (.vtt) call transcripts
Teams and Zoom call transcripts in .vtt are now indexed directly — no more converting them to .txt by hand. Merged in #19.
Added
.vttis a supported extension.syncandingestpick it up like any other document. Each turn becomes a**Speaker** MM:SSline followed by what was said, so a call is chunked as a transcript: 120-second windows labelled with the call's title, the speaker's name searchable.- Teams, Zoom and plain caption tracks. The segments Teams cuts one utterance into are joined back together, even around another speaker's interjection; Zoom's
Name: textcues keep their speaker; a caption track without speakers still windows by time. - A broken
.vttfails instead of indexing raw cues. A malformed file, or a non-UTF-8 file whose encoding cannot be told, is reported as a failed file rather than indexed as cue ids and timings. Saving it as UTF-8 fixes the second case.
Changed
- The package license is now
MIT AND Apache-2.0: the WebVTT parser is adapted from webvtt-to-markdown and keeps its Apache-2.0 license, shipped inLICENSES/. charset-normalizeris now a declared dependency (it was already installed throughmarkitdown).
Upgrading
Nothing to do beyond the upgrade. The next sync indexes any .vtt files already under your roots. If you kept hand-converted .txt copies of the same calls next to them, remove the copies, or each call is indexed twice.
v0.7.0 — tool errors say what happened and what unblocks it
Tool errors and empty results now say what happened and what unblocks it. Merged in #18.
Changed
- Empty results explain themselves. An empty index or a relative
scopeused to come back as a bare{"results": []}. The response now carries anoticesaying why. - A PNG without the
[ocr]extra says to install the extra. It used to report "Unsupported file extension", which pointed at dropping the file instead. - Folders a listing cannot read are named. Files under them are listed as
unlisted, and the folders themselves appear in a newunlistedFoldersfield. - The CLI and the MCP server share one search and list path, so the two answer identically. One visible consequence:
minirag-mcp statuswith an unknown model now reportsindexErrorand exits 0, as the server'sstatustool already did, instead of failing. - A single failed model load no longer leaves token counting on the rough estimate for the rest of the process.
- README: a
RAG_INSTRUCTIONS_APPENDexample whose trigger is a situation rather than a topic.
Upgrading
Nothing to do beyond the upgrade. Clients that parse responses may see two new fields, notice and unlistedFolders. A script that relied on minirag-mcp status failing for an unknown model should check for indexError instead.
v0.6.1 — Office lock files no longer fail a sync
A one-line scanner fix for #15, reported by @mikko-pulli.
Fixed
An open Word, Excel or PowerPoint document made sync exit 1. Office writes a
lock file beside every document it has open, named after it and so carrying its
extension: ~$port.docx for report.docx. The scan skipped dot-prefixed names
but not that prefix, so the lock file passed the extension whitelist and was handed
to markitdown, which raises BadZipFile on what is a short binary blob rather
than the ZIP the extension promises. The failure was counted, and sync treats any
failed file as a failed run — so a sync failed for no reason but that somebody had
a document open. The scan now skips the ~$… prefix. LibreOffice needed no entry:
its lock files already start with a dot.
The report's second half does not reproduce, and is recorded here because the
symptom is easy to misattribute. The lock file does not hang the sync: the parse
raises in about 0.01 s, per-file failures are caught, and a root holding two lock
files and one markdown file finished in 0.04 s. scanned: 0 on a running job is
unrelated to lock files — a SyncJob keeps its all-zero counts until the run
returns, so every in-flight job polls that way.
Upgrading
Nothing to do beyond the upgrade. If a lock file ever reached your index through
an explicit ingest, the next sync drops it, since the scan no longer finds it
on disk.
v0.6.0 — OCR follow-ups
Follow-ups on the OCR tier from v0.5.0 — the bugs its own merge review filed.
Fixed
A sync from an install without the [ocr] extra deleted indexed images. The
scanner filters by the OCR-gated extension whitelist, so a light install did not
see image files at all and the diff deleted them as if they had vanished from
disk. Such a source is now retained and reported: sync counts it under a new
unreadable key and says which installation cannot read it and how to fix that,
and both shells list it in the unreadable state instead of omitting it. A file
genuinely deleted from disk is still deleted.
The store misreported chunking-scheme staleness on an index it could not
migrate. Because the cosmetic ocr_engine column had joined the
scheme-critical set, a read-only copy or a concurrently-written index was told it
predated the current chunking scheme, had every chunk reported stale, and had
parent-section retrieval switched off. The scheme verdict now keys off only the
columns it is about, and a failed migration records which columns it left behind.
Orientation failures escaped the OCR module as raw exceptions, bypassing the
conversion that attaches the file name and the install hint.
The declared opencv floor was wrong. cv2.imdecodemulti does not exist in
4.5.5 or 4.6.0 — it first appears in 4.7.0.68 — and no wheel before 4.10.0.84
imports under numpy 2, which is the only numpy this project can resolve. Measured
in throwaway environments rather than read from documentation.
Faster
A PDF was parsed twice on every ingest, once by the converter and once by the
per-page character count that decides what needs OCR. With no extra installed
that second pass could never change the outcome, so it is skipped — default
installs pay nothing for a feature they do not have.
Also
The unreadable counter is present from the first status poll rather than
appearing mid-job; failure messages that mislabeled a read error as a decode
error are fixed; the README says what happens to indexed images without the
extra and documents the new status keys, which now report whether this
installation can OCR at all.
524 tests green with the extra absent, 7 slow tests green with it. PR: #14.
v0.5.0 — OCR for scanned documents
OCR for scanned documents
Scanned documents no longer disappear from the index. A scanned PDF used to
convert to an empty document and be counted as a sync failure; image files were
not in the extension whitelist at all.
Install the new optional extra to change that:
uv tool install 'minirag-mcp[ocr]'
# or, in a client config: uvx --from 'minirag-mcp[ocr]' minirag-mcpWith it:
- Scanned PDF pages are recognized page by page. Pages carrying a real text
layer keep it; only pages belowRAG_OCR_MIN_CHARS_PER_PAGE(default 25) go
through the engine, and the recognized text is appended after the converted
document, so a mixed PDF keeps its tables and headings. - Image files become documents —
.png .jpg .jpeg .tiff .tif .bmp .webp,
multi-page TIFFs read in full. - Page orientation is corrected (0/90/180/270) before recognition.
- How a document entered the index is visible:
ocrEngineinlist_files,
[ocr:rapidocr]in the CLI listing.
Recognition runs locally on the CPU — RapidOCR with PP-OCRv5 East Slavic models
on the ONNX runtime the embedding model already uses. Nothing leaves the machine,
no system binaries are needed, and the models land in CACHE_DIR beside the
embedding model. The default language is Cyrillic-capable deliberately: the stock
model drops Cyrillic silently.
Without the extra nothing changes, except that a scanned PDF now fails loudly
with an install hint instead of being indexed as nothing. A PDF whose text layer
is merely short is kept exactly as before.
New settings: RAG_OCR_LANG (default eslav) and RAG_OCR_MIN_CHARS_PER_PAGE
(default 25, 0 disables the check).
OCR text is not authoritative over the source scan. Measured on a real
Russian scanned invoice against 27 verbatim-searchable facts, this tier recovered
21; the misses are character confusions in low-contrast regions.
Full design, alternatives, and measurements:
docs/superpowers/specs/2026-08-09-ocr-design.md. PR: #13.
v0.4.1
Publishes minirag-mcp to the official MCP registry.
The registry verifies ownership of a PyPI package by finding an mcp-name: marker in the package description, and PyPI descriptions are immutable per version — so the marker needs a release of its own. That is what this is. No behaviour changed; no re-index needed.
Also adds server.json (kept in step with the package version by a test and by the release tooling), a workflow that publishes the registry entry from a v* tag using GitHub OIDC rather than a stored secret, a badge row, and docs/publishing.md listing the remaining catalogues in dependency order.
v0.4.0
Inline citation markers are gone. The Sources list stays, and each line is now the document's path relative to your document root, with the real filename, copied verbatim.
Why
The markers referenced nothing. In practice the model produced an unnumbered list, so [1]…[4] indexed into something with no numbers — and there were more markers than entries.
Server-side numbering cannot fix this: an answer is built from two to six searches, each numbering its own results from 1, so the model has to renumber across them. Measured, it renumbers correctly every time — which is precisely why any "copy this number verbatim" instruction is false. Removing the numbering deletes the failure class rather than fighting it.
What you get
Each sources entry now carries displayPath beside source:
instructions/05. Учет баланса металлов/И-85_БМ_Формирование и утверждение документа Баланс металлов_v1.0.docx
The real filename, not a normalised title — underscores and extension intact, so it matches what is on disk. source is unchanged and remains the identity key for read_file, delete_file and re-ingest.
Measured over 16 runs against the previous release: entries the model altered went from 46 of 46 to zero; runs containing a marker from 100% to 0%; and every listed line is now exactly the path the server offered, nothing appended.
No re-index needed.
v0.3.2
Citations were arriving without paths — the one part that makes them checkable.
The rule asked for the document's title and path verbatim. Measured over 136 runs against a real corpus: titles came back verbatim 100% of the time, paths 7%. One citation line in that format costs a median 203 characters on real filenames, so the model trims, and it trimmed the anchor rather than the label.
Reversing the emphasis — the path verbatim, the title free to be shortened — takes full-path delivery to 93% (Fisher exact p = 0.0004), holding at 87% pooled across both carriers and both languages.
No re-index needed.
v0.3.1
Citations now reach clients that drop the MCP instructions field — Claude Desktop among them.
v0.3.0 put the citation policy in instructions, which Claude Code and VS Code inject into the model's context but Claude Desktop and claude.ai do not. Upgrading to 0.3.0 in Desktop therefore changed nothing visible: the server was healthy and the model was calling the tools, it had simply never been told the policy.
Tool descriptions reach the model in every client, so the citation rule now lives there too, on query_documents. It costs +84 characters on that description — a sentence elsewhere in it was removed to pay for most of the addition, since it restated something the text already implied.
A test pins the phrases the two texts share, so the version in the instructions and the version in the tool description cannot drift apart. Clients that deliver both receive one consistent rule rather than two competing ones.
Nothing else changed. No re-index needed.