ruvector — Python SDK, CLI, and MCP server
Ultra-low-latency vector similarity search, backed by a Rust RaBitQ 1-bit
quantization core (ruvector_rabitq::RabitqPlusIndex) with native NumPy
interop — zero-copy reads, GIL released on every heavy call. Ships three
things from one pip install ruvector:
ruvector— the Python library (Collection,RabitqIndex).ruvectorconsole script — a CLI for scripting/ops (create/insert-batch/search/delete/export/import/info/benchmark/serve), installed with the base package.ruvector serve— an MCP server (stdio or streamable-HTTP) exposing the same surface as tools, plus a ChatGPT Apps SDKui://widget for visual search exploration, install with themcpextra.
This crate is the Python half of the ruvector workspace; the underlying
algorithm lives in crates/ruvector-rabitq/ and is unchanged by this
binding. Scope, benchmarks, and the milestone roadmap (M1 here → M2 ruLake/
HNSW → M3 embeddings → M4 A2A client) are in
docs/adr/ADR-352-ruvector-python-sdk-cli-mcp.md
and docs/sdk/.
Install
pip install ruvector # library + the `ruvector` console script (numpy, click, rich)
pip install ruvector[mcp] # + `ruvector serve` (MCP server)
pip install ruvector[all] # everything
For local development from a checkout:
cd crates/ruvector-py
maturin develop --release # builds the Rust cdylib in-place, links as ruvector._native
pip install -e '.[all]' # pulls click/rich/mcp for the cli/mcp extras
pytest tests/
The --release flag matters: a debug build is dramatically slower on the
search loop and will fail the latency acceptance tests.
30-second example (library)
import numpy as np
from ruvector import Collection
vectors = np.random.default_rng(42).standard_normal((100_000, 128)).astype(np.float32)
coll = Collection.from_vectors(vectors, rerank_factor=20)
hits = coll.search(vectors[0], k=10)
for h in hits:
print(h.id, h.score) # ascending squared-L2 distance
coll.save("my.rbpx") # writes my.rbpx + my.rbpx.meta.json
coll2 = Collection.load("my.rbpx")
Collection adds ids, per-vector metadata, soft delete, and filtering on
top of the lower-level RabitqIndex (which is still available directly
for the ~50 lines/row case where you don't need any of that):
coll = Collection.create(dim=128)
coll.insert(vectors[0], metadata={"category": "news"})
coll.insert_batch(vectors[1:100], metadatas=[{"category": "sports"}] * 99)
hits = coll.search(vectors[0], k=5, filter={"category": "news"}) # client-side filter
coll.delete(hits[0].id) # soft delete (tombstone)
coll.vacuum() # physically rebuild without tombstoned rows
Filtering is applied client-side (the Rust scan has no filter pushdown in
M1.5) — documented in Collection.search's docstring, not hidden.
CLI
ruvector create --path my.rbpx --dim 128
ruvector insert-batch --path my.rbpx --vectors vecs.npy --metadata meta.json
ruvector search --path my.rbpx --query q.npy -k 10 --json
ruvector delete --path my.rbpx --id 42 --vacuum
ruvector export --path my.rbpx --out snapshot
ruvector import --path new.rbpx --vectors vecs.npy
ruvector info --path my.rbpx
ruvector benchmark -n 100000 --dim 128 # in-process latency/QPS, no external comparator
ruvector serve # MCP server over stdio
ruvector serve --http --port 8420 # MCP server over streamable-HTTP (loopback)
ruvector serve --read-only # no create/insert/delete tools
ruvector serve --max-vectors 50000 # cap vectors per collection (default 1,000,000)
serve --http on a non-loopback --host refuses to start unless
RUVECTOR_MCP_TOKEN is set to a non-empty secret (bearer auth); an empty or
whitespace-only value counts as unset.
ruvector --help starts in ~20ms — every subcommand lazily imports numpy/
click/rich inside its own function body (see ruvector/cli.py's module
docstring), so nothing pays for them until it actually runs.
MCP server
ruvector serve exposes: vector_create_collection, vector_insert,
vector_insert_batch, vector_search, vector_delete, vector_stats,
vector_list_collections, and vector_explore — a widget-backed tool that
renders results in an HTML table (ui://ruvector/explore.html), following
the ChatGPT Apps SDK convention (_meta["openai/outputTemplate"]).
Tool arguments name a collection by name, never by filesystem path —
names are restricted to [A-Za-z0-9_-] and resolved under
RUVECTOR_MCP_DATA_DIR (default ~/.ruvector/collections), so there is no
directory-traversal surface regardless of what a remote MCP client sends.
See ruvector/mcp_server.py's module docstring for the full security
model.
Write size is capped. Per call: 10,000 rows, 2,000,000 floats in total, and
64 KiB of serialized metadata per row. Per collection: 1,000,000 vectors by
default, counting deleted-but-not-vacuumed rows; vector_insert and
vector_insert_batch fail with a ToolError that names the limit once it
would be exceeded. Change it with ruvector serve --max-vectors N or
RUVECTOR_MCP_MAX_VECTORS=N (the option wins; a non-positive or non-integer
value stops the server at startup). The server re-saves the whole collection
on every write, so keep this cap well below what your disk and latency budget
can take.
# point an MCP client at: ruvector serve (stdio)
# or: ruvector serve --http --port 8420
API summary (library)
| Call | Returns | Notes |
|---|---|---|
Collection.create(dim, *, rerank_factor=20, seed=42) |
Collection |
empty; lazily builds on first insert |
Collection.from_vectors(vectors, *, ids=None, metadatas=None, ...) |
Collection |
bulk build, parallel rotate+pack |
coll.insert(vector, *, metadata=None) / coll.insert_batch(vectors, *, metadatas=None) |
int / list[int] |
true incremental add, no rebuild |
coll.search(query, k, *, filter=None, rerank_factor=None) |
list[SearchHit] |
filter: exact-match dict or predicate callable |
coll.delete(id) / coll.vacuum() |
None / int |
soft delete / physical rebuild |
coll.save(path) / Collection.load(path) |
— | .rbpx + .rbpx.meta.json sidecar |
coll.stats() |
CollectionStats |
count, dim, rerank_factor, memory_bytes, tombstoned |
RabitqIndex.build(vectors, *, ids=None, rerank_factor=20, seed=42) |
RabitqIndex |
lower-level index, no metadata/filter/delete |
idx.search(query, k, *, rerank_factor=None) |
list[(int, float)] |
(id, score²) ascending |
idx.save(path) / RabitqIndex.load(path) |
— | .rbpx v1 format |
ruvector.RuVectorError / ruvector.CollectionError |
exceptions | base / Collection-level |
Non-contiguous or wrong-dtype inputs raise TypeError at the boundary
rather than silently copying — predictable beats fast. Ids must fit in
u32 (the index's storage width); a larger id raises CollectionError
(library) or ValueError (RabitqIndex directly) rather than silently
truncating.
Benchmark
Real numbers (this host, random-Gaussian data — adversarial for ANN in
general, see the caveat in ADR-352) are in
docs/adr/ADR-352-ruvector-python-sdk-cli-mcp.md.
ruvector benchmark runs an in-process build+search latency check with no
external comparator.
Links
- ADR-352 — scope, benchmark, security, publishing status
- SDK plan and milestones — binding strategy + M1-M4 roadmap
ruvector-rabitq— the Rust crate this wraps
License
MIT, matching the rest of the ruvector workspace (see LICENSE).
Metadata
Release files for ruvector 0.1.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| ruvector-0.1.3.tar.gz | 2.9 MB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| ruvector-0.1.3-cp39-abi3-win_amd64.whl | CPython 3.9 | abi3 | Windows x86-64 | Details |
| ruvector-0.1.3-cp39-abi3-manylinux_2_28_x86_64.whl | CPython 3.9 | abi3 | Linux glibc 2.28+ x86-64 | Details |
| ruvector-0.1.3-cp39-abi3-manylinux_2_28_aarch64.whl | CPython 3.9 | abi3 | Linux glibc 2.28+ ARM64 | Details |
| ruvector-0.1.3-cp39-abi3-macosx_11_0_arm64.whl | CPython 3.9 | abi3 | macOS 11.0+ ARM64 | Details |
| ruvector-0.1.3-cp39-abi3-macosx_10_12_x86_64.whl | CPython 3.9 | abi3 | macOS 10.12+ x86-64 | Details |
Total release size: 16.8 MB
Release files / ruvector-0.1.3.tar.gz
| Download URL | ruvector-0.1.3.tar.gz |
|---|---|
| Size | 2.9 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b88ac46fe5496e1ddf5d43fc9ffacddb19266e887a713574228de3d93c6cb838
|
|
BLAKE2b-256 checksum How to use checksums |
28a77a8957be9f80bb0b0c405a5e69f557e55c6ad4a0ee3c406d68a46d228189
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / ruvector-0.1.3-cp39-abi3-win_amd64.whl
| Download URL | ruvector-0.1.3-cp39-abi3-win_amd64.whl |
|---|---|
| Size | 3.0 MB |
| Tags | CPython 3.9 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
1ce486d39e8e0229d673e3f80d61d5dcbe4c2f89fbaa269a483f64566d374c22
|
|
BLAKE2b-256 checksum How to use checksums |
1330dec8e77ff81babb7cc7504e6494059f0a906a0d24794a42554292a7d5999
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / ruvector-0.1.3-cp39-abi3-manylinux_2_28_x86_64.whl
| Download URL | ruvector-0.1.3-cp39-abi3-manylinux_2_28_x86_64.whl |
|---|---|
| Size | 2.9 MB |
| Tags | CPython 3.9 Linux glibc 2.28+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
abe7d2cda31ca1c261e5dda05ed71ecfa750f87a3703ef43ec4f258211c73aeb
|
|
BLAKE2b-256 checksum How to use checksums |
9eac7e1823072ef4b0e6f67fae339f210d6bd9dfc2f950d7374d05ea6dda0f18
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / ruvector-0.1.3-cp39-abi3-manylinux_2_28_aarch64.whl
| Download URL | ruvector-0.1.3-cp39-abi3-manylinux_2_28_aarch64.whl |
|---|---|
| Size | 2.7 MB |
| Tags | CPython 3.9 Linux glibc 2.28+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
0b236cbb547850114db740a1d7494392ff3ba17ae20610d66a1a34db3df8d711
|
|
BLAKE2b-256 checksum How to use checksums |
f0eb7dc443d900ac9fc90129161527f2a70334d1d2d141680cf93328bbe369fd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / ruvector-0.1.3-cp39-abi3-macosx_11_0_arm64.whl
| Download URL | ruvector-0.1.3-cp39-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 2.6 MB |
| Tags | CPython 3.9 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
d3993697250f9d4a79a28653afa2db5f0ec7894792aabb35c7bc6c21b91f9c8f
|
|
BLAKE2b-256 checksum How to use checksums |
fd2c73c257740adb05cf243951660a0994df0fab9f9c662641ffb7a265845706
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / ruvector-0.1.3-cp39-abi3-macosx_10_12_x86_64.whl
| Download URL | ruvector-0.1.3-cp39-abi3-macosx_10_12_x86_64.whl |
|---|---|
| Size | 2.8 MB |
| Tags | CPython 3.9 abi3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
bbcd7feb6c61532312895d2b4bf0db42dc9e607afb6d8f0930b4272f657bdc5d
|
|
BLAKE2b-256 checksum How to use checksums |
cf21077058833a46da0a3a44954ea35497e4995ece66b1763aed18fef3086ae0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|