[project] name = "minirag-mcp" version = "0.6.1" description = "Local-first RAG MCP server: hybrid search over your documents, fully local" readme = "README.md" requires-python = ">=3.11" license = "MIT" dependencies = [ "fastmcp>=3.4", "lancedb>=0.36", "fastembed>=0.8", "markitdown[pdf,docx,pptx,xlsx]>=0.1.7", "cyclopts>=4.0", "platformdirs>=4.0", # markitdown already pulls this in, but we import it directly: the URL fetch runs # through a requests session we build and guard ourselves. "requests>=2.32", # fastembed already pulls this in, but we import it directly: the chunker's token # counter is built from a copy of the model's own tokenizer with truncation off. "tokenizers>=0.15", # markitdown[pdf] already pulls this in, but we import it directly: OCR routing # counts a PDF's text per page with pdfminer's own layout pass, and that import # runs at startup on every install, extra or not. The bound matches the floor # markitdown[pdf] already imposes, so declaring it can never move the resolution. "pdfminer.six>=20251230", ] [project.scripts] minirag-mcp = "minirag_mcp.__main__:main" [project.optional-dependencies] ocr = [ "rapidocr>=3.9", "onnxruntime>=1.19", "pypdfium2>=5", "rapid-orientation>=0.0.11", # rapidocr already pulls this in, but we import it directly to decode image files, # and its own floor (4.5.1.48) is below both of the floors that bind here. The API # floor is 4.7.0.68: `imdecodemulti` — the call that keeps every frame of a # multi-page TIFF — is absent from 4.5.5.62, 4.5.5.64 and 4.6.0.66, and no wheel # sits between 4.6.0.66 and 4.7.0.68. The floor declared is higher because it is # the numpy one: no wheel before 4.10.0.84 imports against numpy 2.x (4.7.0.68 # through 4.10.0.82 all raise "numpy.core.multiarray failed to import"), and # nothing in this dependency graph permits numpy 1.x — fastembed, a core # dependency, requires numpy>=2.1 on 3.13. All four components are spelled out # because `>=4.10` would admit 4.10.0.82, which is one of the broken ones. "opencv-python>=4.10.0.84", ] [build-system] requires = ["hatchling"] build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] packages = ["src/minirag_mcp"] [dependency-groups] dev = ["pytest>=8", "pytest-asyncio>=1.0", "ruff>=0.6", "bump-my-version>=1.5.1"] # `uv run bump-my-version bump ` does the whole release # prologue: rewrite `version` above, commit it, tag it. It never pushes — the # push is what makes a version number real on PyPI, and everything up to it is # still reversible, so that stays a separate, deliberate command. # # `__version__` is derived from installed metadata, so `version` in [project] # is the only place a number lives and the only thing this has to rewrite. [tool.bumpversion] current_version = "0.6.1" commit = true message = "chore: release v{new_version}" tag = true # release.yml strips one leading "v" from the tag before comparing it against # the built wheel's version, so the tag has to be spelled exactly this way. # Change one without the other and a release either fails that check or needs # a retag after the fact. tag_name = "v{new_version}" tag_message = "v{new_version}" sign_tags = false # A bump has to be the only thing in its commit; refuse to sweep up whatever # else happens to be in the tree. allow_dirty = false # bump-my-version 1.5.1 also rewrites `project.version` unprompted, but that is # not documented configuration and a release now depends on that line being # right, so it is pinned here too. The two agree; the line is simply written # twice. Anchored to a whole line because `current_version = "0.1.0"` above # ends in the same `version = "0.1.0"` text, which a bare search would match. [[tool.bumpversion.files]] filename = "pyproject.toml" regex = true search = '^version = "{current_version}"$' replace = 'version = "{new_version}"' # uv.lock pins this project's own version alongside its dependencies. Left out, # it stays a release behind until the next `uv run` rewrites it — and that dirty # file then blocks the *following* bump, because allow_dirty is false. Bumping # it here keeps the lock in the release commit where it belongs. Anchored to the # minirag-mcp package block, since other packages in the lock have their own # `version = ` lines. [[tool.bumpversion.files]] filename = "uv.lock" regex = true search = 'name = "minirag-mcp"\nversion = "{current_version}"' replace = 'name = "minirag-mcp"\nversion = "{new_version}"' # server.json is the MCP Registry's copy of this release, and it spells the # version twice: once for the server entry and once for the PyPI package it # points at. Both must equal the tag — publish-mcp.yml refuses a tag that # disagrees with either, and the registry itself rejects a re-publish of a # version it already holds, so a stale server.json fails the publish of a tag # whose wheel is already on PyPI, and the only way out of that is another # release. The two lines differ only by indentation, so they get one entry # each, anchored to whole lines; a single unanchored search would rewrite # whichever bump-my-version happened to reach first and silently leave the # other behind. tests/test_scaffold.py asserts both against pyproject.toml. [[tool.bumpversion.files]] filename = "server.json" regex = true search = '^ "version": "{current_version}",$' replace = ' "version": "{new_version}",' [[tool.bumpversion.files]] filename = "server.json" regex = true search = '^ "version": "{current_version}",$' replace = ' "version": "{new_version}",' [tool.pytest.ini_options] addopts = "-m 'not slow'" markers = ["slow: needs the real embedding model (downloads ~220 MB)"] asyncio_mode = "auto" testpaths = ["tests"] [tool.ruff] line-length = 100 src = ["src", "tests"] [tool.ruff.lint] select = ["E", "F", "I", "UP", "B"] [tool.ruff.lint.per-file-ignores] # Verbatim excerpts from the real corpus. Rewrapping a markdown table row or a # transcript turn would change the very shape the chunker is being tested against. "tests/corpus_samples.py" = ["E501"]