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.2
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.2.tar.gz | 1.3 MB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| ruvector-0.1.2-cp39-abi3-win_amd64.whl | CPython 3.9 | abi3 | Windows x86-64 | Details |
| ruvector-0.1.2-cp39-abi3-manylinux_2_28_x86_64.whl | CPython 3.9 | abi3 | Linux glibc 2.28+ x86-64 | Details |
| ruvector-0.1.2-cp39-abi3-manylinux_2_28_aarch64.whl | CPython 3.9 | abi3 | Linux glibc 2.28+ ARM64 | Details |
| ruvector-0.1.2-cp39-abi3-macosx_11_0_arm64.whl | CPython 3.9 | abi3 | macOS 11.0+ ARM64 | Details |
| ruvector-0.1.2-cp39-abi3-macosx_10_12_x86_64.whl | CPython 3.9 | abi3 | macOS 10.12+ x86-64 | Details |
Total release size: 13.5 MB
Release files / ruvector-0.1.2.tar.gz
| Download URL | ruvector-0.1.2.tar.gz |
|---|---|
| Size | 1.3 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
73f8895ad98f74d941520c5848beaa552810f953e6b3064e1dac8acaa4d375f5
|
|
BLAKE2b-256 checksum How to use checksums |
8cfee37de2fec649c2cc3c3f7161d4816c1b3b7bcd75b392cf29951d2aa3b53f
|
| 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.2-cp39-abi3-win_amd64.whl
| Download URL | ruvector-0.1.2-cp39-abi3-win_amd64.whl |
|---|---|
| Size | 2.6 MB |
| Tags | CPython 3.9 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
b7da877c0df5958a76acadc1b9f1b0851551f927d28259af0b20a5be4ef542b8
|
|
BLAKE2b-256 checksum How to use checksums |
35e0c5eafdcba2c1258b41146492fbd79442625654539045ae73679d18ce41a6
|
| 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.2-cp39-abi3-manylinux_2_28_x86_64.whl
| Download URL | ruvector-0.1.2-cp39-abi3-manylinux_2_28_x86_64.whl |
|---|---|
| Size | 2.6 MB |
| Tags | CPython 3.9 Linux glibc 2.28+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
08e7cbf02fe640e921d2e8b78f7a5fd5c9379343b34c689fb283474280ac4da0
|
|
BLAKE2b-256 checksum How to use checksums |
408e6e7d3036767d94bd23c5b1f80f5d2e397e496d37bdfc1684295ce9908692
|
| 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.2-cp39-abi3-manylinux_2_28_aarch64.whl
| Download URL | ruvector-0.1.2-cp39-abi3-manylinux_2_28_aarch64.whl |
|---|---|
| Size | 2.3 MB |
| Tags | CPython 3.9 Linux glibc 2.28+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
8014f69221b8da10ee7d2039ba30979370b78629028fc8c1dd333958d7f95960
|
|
BLAKE2b-256 checksum How to use checksums |
4362a223762b39f22b2cb5bb5b75fcfaf48ef1477907350b4e80f8af1923ac3f
|
| 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.2-cp39-abi3-macosx_11_0_arm64.whl
| Download URL | ruvector-0.1.2-cp39-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 2.2 MB |
| Tags | CPython 3.9 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
c9438ced8aa54f7fa5ddc7be8038e6cdc3f771587f1fdf5b50926eb4b3bf9316
|
|
BLAKE2b-256 checksum How to use checksums |
212f0ed599792dc1ad10c2a676d69f8b4bedfa665b16939a2217f3282d224f24
|
| 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.2-cp39-abi3-macosx_10_12_x86_64.whl
| Download URL | ruvector-0.1.2-cp39-abi3-macosx_10_12_x86_64.whl |
|---|---|
| Size | 2.5 MB |
| Tags | CPython 3.9 abi3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
d2b82c87b26760b60bec58cbb948ed09f2ba457594817308c48b8e01bd5156cc
|
|
BLAKE2b-256 checksum How to use checksums |
f6f171388775712139389cd731e16cf0e205f18b57838dd74d851cad97754adc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.3
|