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).
Python capability packages
The Python portfolio maps the ten most downloaded RuVector npm distributions to scoped Python APIs. Version 0.1.3 adds native KVCache (full precision cache attention, no text generation) and RvfStore (trusted local RVF vector storage). Existing vector, graph, GNN, attention and SONA APIs remain available.
from ruvector import KVCache
cache = KVCache(2, max_tokens=8)
cache.append([1.0, 0.0], [0.25, 0.75])
assert cache.attend([1.0, 0.0]) == [0.25, 0.75]
Native version changes merged into main automatically build, audit, test and publish all supported wheel platforms. A failed gate blocks upload.
Metadata
Release files for ruvector 0.1.4
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.4.tar.gz | 2.9 MB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| ruvector-0.1.4-cp39-abi3-win_amd64.whl | CPython 3.9 | abi3 | Windows x86-64 | Details |
| ruvector-0.1.4-cp39-abi3-manylinux_2_28_x86_64.whl | CPython 3.9 | abi3 | Linux glibc 2.28+ x86-64 | Details |
| ruvector-0.1.4-cp39-abi3-manylinux_2_28_aarch64.whl | CPython 3.9 | abi3 | Linux glibc 2.28+ ARM64 | Details |
| ruvector-0.1.4-cp39-abi3-macosx_11_0_arm64.whl | CPython 3.9 | abi3 | macOS 11.0+ ARM64 | Details |
| ruvector-0.1.4-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.4.tar.gz
| Download URL | ruvector-0.1.4.tar.gz |
|---|---|
| Size | 2.9 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
614697f60b038486372fdd7f6feef819f89938b955b38e7fe5fb608d14406e5f
|
|
BLAKE2b-256 checksum How to use checksums |
e07ef317ae2ec6a164de0326dde8e14ab32efa13cf4583b13215133465494075
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.3
|
Release files / ruvector-0.1.4-cp39-abi3-win_amd64.whl
| Download URL | ruvector-0.1.4-cp39-abi3-win_amd64.whl |
|---|---|
| Size | 3.0 MB |
| Tags | CPython 3.9 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
6032dd22b1f74d0d4fa8c23142f88ca9275bcb5f31cd114c3e875e164356d5ee
|
|
BLAKE2b-256 checksum How to use checksums |
4b08fa9b09d984afc968514a93d02d4d51211df1a5ae56ff8f9a30949eb5d7dc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.3
|
Release files / ruvector-0.1.4-cp39-abi3-manylinux_2_28_x86_64.whl
| Download URL | ruvector-0.1.4-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 |
2189bf262fab1533f40ac654afa4bcb580b87ad2e33eee1decd0b7a42f469ebe
|
|
BLAKE2b-256 checksum How to use checksums |
5b33b6b42573b552e0f88feefa85a5c8f6db084816ab2037ccdb7079607ae434
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.3
|
Release files / ruvector-0.1.4-cp39-abi3-manylinux_2_28_aarch64.whl
| Download URL | ruvector-0.1.4-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 |
d6dabb8f3d5add92e8721adcfc28dcaaaadb70e62ac7e729c625ed3870497a7c
|
|
BLAKE2b-256 checksum How to use checksums |
c91712c36f3c768cb05b518365af3b7a15e359e94f6ac8df54658e269b4189cc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.3
|
Release files / ruvector-0.1.4-cp39-abi3-macosx_11_0_arm64.whl
| Download URL | ruvector-0.1.4-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 |
28aeac25bb3151b4afb0b06bc07ec259137432387c7c2cb3316842bc55e82006
|
|
BLAKE2b-256 checksum How to use checksums |
af5d3bf35e6d8a1936068de281b2840b46c69b614b372370cb60f81793c3ea16
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.3
|
Release files / ruvector-0.1.4-cp39-abi3-macosx_10_12_x86_64.whl
| Download URL | ruvector-0.1.4-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 |
3da743e96235ef90d2b95e10f13b42da81374f1844daf3d1fabb9ab011b8670d
|
|
BLAKE2b-256 checksum How to use checksums |
f0681514bad3289938903d405d746883feb6ae4a127070563b6f47080f4943ff
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.3
|