Skip to main content
Glama
Bismut Vector MCP

Bismut Vector MCP

by QkartBismuth

Bismut Vector MCP

A local vector database exposed over the Model Context Protocol. Works with any MCP client: opencode, Claude Code/Desktop, Hermes, Cursor, Zed, Windsurf, and anything else that speaks MCP over stdio.

Everything runs on your machine. Embeddings are computed in-process with a local ONNX model, vectors live in a single SQLite file, and nothing is sent over the network.

Why it is built this way

Choice

Reason

node:sqlite + sqlite-vec

Zero native compilation. The extension ships as a prebuilt npm optional dependency, so npm install is enough on every platform.

Local ONNX embeddings

No API keys, no network, no data leaving the machine. Works offline.

Xenova/paraphrase-multilingual-MiniLM-L12-v2

384 dimensions, handles 50+ languages well. Measured much better separation than multilingual-e5-small (0.66 vs -0.01 for related/unrelated pairs, against 0.90 vs 0.78).

Cosine distance

Vectors are L2-normalized, so cosine similarity is exact and scores are directly interpretable: 1.0 is identical, 0.0 is orthogonal.

One vec0 table per collection

Lets different collections use different models and dimensions.

Related MCP server: lore-mcp

Install

npm install
npm run build

Requires Node 22.5+ (uses the built-in node:sqlite). Developed and tested on Node 24.

The embedding model downloads once on first use (~120 MB) into .bismut/cache. Later runs start instantly and work offline.

Connect an editor

Registered in opencode's global config (~/.config/opencode/opencode.jsonc) as bismut-vector. Verify any time:

npm run verify:opencode
npm run verify:opencode -- /path/to/some-project

That reads your live opencode config, walks every registered server, and performs a real MCP handshake with the exact command and environment values it finds there.

For other editors, copy the block from examples/:

  • examples/opencode.json — opencode scope and overrides

  • examples/claude.md — Claude Code and Desktop

  • examples/other-editors.md — Cursor, Zed, Windsurf, Hermes, others

The common shape is:

{
  "mcpServers": {
    "bismut-vector": {
      "command": "node",
      "args": ["/absolute/path/to/Bismut_MCP_Vector/dist/index.js"],
      "env": { "BISMUT_COLLECTION": "myproject" }
    }
  }
}

opencode config is reloaded on restart, not hot-reloaded.

Tools

Tool

Purpose

index_paths

Index files or directories. Re-running skips unchanged files.

index_text

Index arbitrary text: notes, memory, logs, pasted context.

search

Semantic search with path, language, and kind filters.

get_context

Expand a search hit into surrounding chunks.

get_chunk

Fetch one chunk in full.

list_sources

List what is indexed.

list_collections

List collections with model, dimension, and counts.

delete_sources

Remove documents by id, exact path, or path prefix.

drop_collection

Delete a whole collection. Requires confirm: true.

stats

Server, database, and embedding model diagnostics.

A typical agent session:

  1. index_paths on the repository once.

  2. search with a path filter instead of guessing filenames.

  3. get_context when a result looks truncated mid-function.

Chunking

Chunking is language-aware, which matters more for retrieval quality than embedding model choice:

  • Markdown splits on headings and carries the heading breadcrumb (Architecture > Embeddings) into every chunk.

  • Code splits at top-level symbol boundaries, tracking brace depth while skipping strings, template literals, and comments. One function or class per chunk, named in the chunk header. Python and other indent languages split on def/class blocks instead.

  • Prose and config files pack paragraphs up to a token target with overlap.

Every chunk embeds with a header (file:, title:, lang:, and the symbol name) prepended to its body. This measurably improves retrieval for short chunks, where the body alone gives the embedder too little to work with.

Line ranges are preserved on every chunk, so results can be opened directly.

Configuration

All settings are environment variables. Optionally use a .bismutrc.json in the working directory instead.

Variable

Default

Meaning

BISMUT_DB_PATH

./.bismut/vectors.db

SQLite database file

BISMUT_CACHE_DIR

<db dir>/cache

Model download cache

BISMUT_COLLECTION

default

Default collection name

BISMUT_EMBED_MODEL

Xenova/paraphrase-multilingual-MiniLM-L12-v2

Any transformers.js model

BISMUT_EMBED_DTYPE

q8

q8, fp16, fp32, quantized

BISMUT_EMBED_DEVICE

cpu

cpu, gpu, auto

BISMUT_EMBED_PROVIDER

local

local or openai

BISMUT_BATCH_SIZE

16

Embeddings per forward pass

BISMUT_CHUNK_TOKENS

320

Target chunk size

BISMUT_CHUNK_OVERLAP

60

Overlap between chunks

BISMUT_MAX_FILE_BYTES

1048576

Skip files larger than this

BISMUT_LOG_LEVEL

info

silent, error, warn, info, debug, trace

To use a cloud embedding endpoint instead:

{
  "BISMUT_EMBED_PROVIDER": "openai",
  "BISMUT_EMBED_MODEL": "text-embedding-3-small",
  "BISMUT_API_KEY": "sk-...",
  "BISMUT_API_BASE": "https://api.openai.com/v1"
}

BISMUT_API_BASE works with any OpenAI-compatible endpoint (Ollama, LM Studio, vLLM).

Changing the model changes the vector dimensionality, so it requires a new collection. The server refuses to mix models within one collection rather than silently producing nonsense results.

Storage layout

A collection's vectors live in its own vec0 virtual table. Chunk text and document metadata live in ordinary tables, joined on the vector row id.

Filters are resolved to a doc_id list before the vector query, so path and language filters are applied during the ANN traversal rather than after k is taken. Applying them afterwards is the classic sqlite-vec mistake: it returns empty results whenever the nearest k neighbours happen to fall outside the filter.

Embeddings are cached by content hash, so re-indexing only re-embeds what actually changed.

Development

npm run typecheck     # tsc --noEmit with strict + unused checks
npm test              # 49 end-to-end assertions over the real MCP protocol
npm run test:chunks   # print chunk boundaries per language
node scripts/probe-embed.mjs   # check embedding quality for a model

npm test spawns the built server as a subprocess and drives it with a real MCP client, so it exercises the actual stdio transport, JSON-RPC framing, and zod schema conversion rather than calling internals.

Known limits

  • Chunking uses structural heuristics, not a full parser. It is accurate for normal source files but not a substitute for an AST. Nested blocks inside a large impl or class body stay in one chunk until they exceed the token target.

  • index_paths walks directories serially, which is fast enough for typical repositories but noticeable on very large monorepos.

  • PDF indexing uses unpdf and only reads text layers. Scanned PDFs yield nothing; there is no OCR.

  • There is no hybrid keyword/vector search. Pure semantic search can miss an exact identifier string, so pass minScore deliberately and use path filters to narrow.

Available Tools

10 tools
delete_sourcesDelete indexed documentsA
DestructiveIdempotent

Remove documents from a collection by document id, exact uri, or path prefix. Vectors and chunks are deleted together. This is irreversible and requires at least one selector.

Inputs: all, uris, collection, uriPrefixes, +1 more.

drop_collectionDrop a collectionA
DestructiveIdempotent

Permanently delete an entire collection and all its vectors, chunks, and documents. Irreversible. Prefer delete_sources when removing only some documents.

Inputs: confirm (required), collection (required).

get_chunkFetch a single chunkA
Read-onlyIdempotent

Return the full untruncated text of one chunk by its id, with its document metadata.

Inputs: chunk_id (required).

get_contextExpand a search hit into surrounding contextA
Read-onlyIdempotent

Return contiguous chunks around a search hit (by chunk_id or document_id + ordinal) so you can read a whole function/section instead of an isolated fragment. Use after search when a result looks cut off.

Inputs: after, before, ordinal, chunk_id, +2 more.

index_pathsIndex files and foldersA
Idempotent

Index files or directories into a vector collection for semantic search. Safe to re-run: files whose content hash is unchanged are skipped. Honors .gitignore, skips binaries/oversized files, and uses language-aware chunking (markdown sections, code symbols, prose paragraphs).

Inputs: paths (required), force, prune, exclude, +5 more.

index_textIndex raw textA
Idempotent

Index arbitrary text (notes, snippets, memory entries, error logs, user-provided context) into a collection. Use a stable uri to replace previous content for the same uri, or pass replace:false to append a new version.

Inputs: uri (required), text (required), kind, meta, +3 more.

list_collectionsList collectionsA
Read-onlyIdempotent

List all vector collections with their embedding model, dimensionality, document/chunk counts, and on-disk size.

Inputs: no named parameters.

list_sourcesList indexed documentsA
Read-onlyIdempotent

List documents already indexed in a collection, with chunk counts and sizes. Use to check what is available before searching, or to confirm an index_paths run covered what you expected.

Inputs: limit, offset, uriPrefix, collection.

statsServer and database statisticsA
Read-onlyIdempotent

Show database path, embedding model/provider, collection count, and totals. Useful for verifying the server is healthy and which model is active before diagnosing empty search results.

Inputs: no named parameters.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4.1/5.0

    Scored across 10 tools

    Disambiguation4/5

    Most tools have clearly distinct roles (index_paths vs index_text differ by source type, and list_sources vs list_collections differ by resource). The only mild overlap is between get_context and get_chunk, which both retrieve fuller content, though their descriptions distinguish fragment-level vs surrounding-context use.

    Naming Consistency4/5

    Nearly all names follow a verb_noun pattern (list_collections, index_paths, delete_sources, drop_collection, get_chunk). Minor deviations are the bare single-word tools `search` and `stats`, but the overall convention is readable and predictable.

    Tool Count5/5

    Ten tools is well within the ideal range and each one maps to a distinct stage of the index/search/retrieve lifecycle. No tool feels redundant or superfluous.

    Completeness4/5

    The surface covers the full lifecycle: indexing (paths/text), discovery (collections/sources), search, context retrieval (get_context/get_chunk), and deletion (delete_sources/drop_collection) plus health via stats. The main gap is an explicit create_collection or metadata-update operation, but these are likely handled implicitly by indexing.

    Maintenance

    ActivitySlowing
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      A
      quality
      D
      maintenance
      Local-first RAG indexing and semantic search MCP server. Enables document retrieval and context-aware queries using local embedding models.
      3
      14 npm
      MIT
    • A
      license
      A
      quality
      B
      maintenance
      Indexes local Markdown/text files into a SQLite database with vector embeddings and provides MCP tools for semantic search without cloud dependencies.
      3
      1
      AGPL 3.0
    • A
      license
      Not graded
      quality
      C
      maintenance
      Provides MCP tools for semantic search over personal knowledge sources using pluggable embeddings and local vector indexing.
      1
      MIT
    • A
      license
      A
      quality
      B
      maintenance
      Enables natural-language semantic search over your own local files through an MCP server, fully offline without API keys or a server daemon, with optional LLM-grounded answers and exact file:line sources.
      5
      MIT