{
  "markdown": "# mcp-vl-msa-rs\n\n[![CI](https://img.shields.io/github/actions/workflow/status/DioNanos/mcp-vl-msa-rs/ci.yml?branch=main&style=flat-square&logo=githubactions&logoColor=white&label=CI)](https://github.com/DioNanos/mcp-vl-msa-rs/actions/workflows/ci.yml)\n[![Tests](https://img.shields.io/badge/tests-203%20passing-2ea44f?style=flat-square)](https://github.com/DioNanos/mcp-vl-msa-rs/actions/workflows/ci.yml)\n[![Benchmarks](https://img.shields.io/badge/benchmarks-pre--registered%20gates-8a2be2?style=flat-square)](docs/NEGATIVE_RESULTS.md)\n[![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue?style=flat-square)](LICENSE)\n[![Rust](https://img.shields.io/badge/rust-stable-orange?style=flat-square&logo=rust)](https://www.rust-lang.org)\n\nA searchable long-term memory for AI agents, exposed as an MCP stdio server.\nIndex documents, notes and past conversations into collections; retrieve the\ntop-k relevant chunks for a query and inject the original text back to the\nmodel; add or drop agent memories with `msa_remember` / `msa_forget`. Pure\nRust, BM25 over [tantivy](https://github.com/quickwit-oss/tantivy), zero ML\ndeps in the default build; optional in-process dense rerank.\n\nAny MCP client (Claude Code, Codex, or anything speaking MCP stdio) gets the\nsame memory: a queryable corpus that survives across sessions and model swaps,\nwith no cloud account and no embedding service required. Use it to give an\nagent durable recall over a knowledge base, a docs tree, or its own chat\nhistory — retrieval that returns the original text, not just embeddings.\n\nIt is one half of a two-part memory: this server is the **library** (corpus\nrecall), its companion [mcp-memory-rs](https://github.com/DioNanos/mcp-memory-rs)\nis the **notebook** (curated state). An agent that swaps models loses neither.\n\n```mermaid\nflowchart LR\n    A[\"AI agent<br/>(any MCP client)\"]\n    A -->|\"curated state<br/>read / write / sync\"| M[\"mcp-memory-rs<br/><i>the notebook</i>\"]\n    A -->|\"corpus recall<br/>index / search / fetch\"| V[\"mcp-vl-msa-rs<br/><i>the library</i>\"]\n    M --- D1[(\"JSON categories<br/>SQLite FTS5\")]\n    V --- D2[(\"tantivy BM25<br/>collections\")]\n```\n\n**The name**: `msa` is the retrieval pattern it borrows from the Memory Sparse\nAttention paper (arXiv:2603.23516) — an *extrinsic* approximation, not the\nneural model; distinct from MiniMax's MSA-architecture LLMs, which are\n*intrinsic* (in-model) generators. `vl` is for Vivling (`codex-vl`), its first\nadopter — but the server is fully AI-agnostic and depends on nothing from it.\n\n**Status**: v0.4 — hybrid sparse+dense optional.\n\n## Why\n\nThe original [Memory Sparse Attention](https://arxiv.org/abs/2603.23516) paper (EverMind-AI) describes an end-to-end trainable sparse attention layer over chunk-pooled KV caches. That is a neural artifact and is not portable to a pure-Rust MCP server. What *is* portable, and what this repo aims to deliver, is the MSA macro pattern:\n\n1. **Chunked storage** of long-form text with a small fixed pool size (`P=64` words by default, mirroring the paper).\n2. **Top-k sparse routing** over chunks (BM25 surrogate; learned routing is out of scope).\n3. **Original text injection** (paper §4.3, ablation -37.1% without): `msa_search` returns chunks, `msa_fetch_doc` returns the full document.\n4. **Memory Interleave** as a *protocol* (planned v0.4): the AI client orchestrates multi-hop retrieval through repeated tool calls with a server-side cursor.\n\nDesign and rationale are documented in the project notes (negative results, gate methodology); see [`docs/NEGATIVE_RESULTS.md`](docs/NEGATIVE_RESULTS.md).\n\n## Benchmarks\n\nRetrieval changes are decided on **pre-registered, paired deltas** with\nbootstrap confidence intervals — not on absolute scores. Workloads: HotpotQA\n(extractive QA), MLDR-it (long-doc retrieval, Italian), LongMemEval-S (500\nconversational-memory questions). Full methodology, acceptance gates and\n*refuted* hypotheses live in [`docs/NEGATIVE_RESULTS.md`](docs/NEGATIVE_RESULTS.md).\n\nHeadline measurements:\n\n- **BM25 is the engine, not a placeholder.** Three pre-registered attempts; no\n  hybrid (BM25 + dense rerank) configuration beat the gate on these workloads.\n  Dense rerank stays available (`dense_alpha`, off by default) for re-testing as\n  encoders improve.\n- **Rich capsules at ingestion** (deterministic enrich, no LLM): **+7 to +20\n  recall@5** across every category.\n- **Original-text injection** (`msa_fetch_doc` after `msa_search`): **+14.6 F1**\n  exactly on the stratum where snippets miss the content.\n- **Recency priors lose** — handle time at serving, not in the retrieval score.\n\nReproduce:\n\n```bash\ncrates/msa-bench/scripts/download-bench-datasets.sh   # fetch datasets\nscripts/run-baseline-bench.sh                         # BM25 vs BM25+dense sweep\n# results land under crates/msa-bench/results/ as JSON\n```\n\n## Tool surface\n\n| Tool | Since | Description |\n|---|---|---|\n| `msa_index` | v0.1 | Index a document; existing chunks for `doc_id` are replaced. |\n| `msa_search` | v0.1 | Top-k chunks, score normalized 0.0–1.0. |\n| `msa_fetch_doc` | v0.1 | Full original text of a document. |\n| `msa_delete` | v0.1 | Remove a document and all its chunks. |\n| `msa_list_collections` | v0.1 | Collections open in the registry. |\n| `msa_stats` | v0.1 | Per-collection statistics (exact `num_documents` / `total_tokens`). |\n| `SearchFilter` | v0.2 | Metadata filter (`where_eq`/`where_in`/`created_*`), post-retrieval. |\n| `msa_search_iterative` | v0.3 | Memory Interleave with server-side cursor; dedups across rounds. |\n| `msa_drop_session` | v0.3 | Force-evict a Memory Interleave session before TTL. |\n| `dense_alpha` on `msa_search` | v0.4 | Hybrid BM25 + cosine rerank. Requires `--features embeddings` + `[embeddings]` config. |\n| `msa_remember` / `msa_forget` | v0.4 | Agent-memory surface: enrich + low-signal gate + content-hash dedup; standard metadata (`kind` / `source_id` / `created_at`). |\n| `msa_sync_path` | v0.4 | Mirror a directory into a collection (filesystem source; blake3 delta sync). |\n\n## Install\n\n**Prebuilt binary** (recommended) — download the archive for your platform from\nthe [latest release](https://github.com/DioNanos/mcp-vl-msa-rs/releases/latest),\nextract, and point your MCP client at the binary:\n\n```bash\ntar xzf mcp-vl-msa-rs-x86_64-unknown-linux-gnu.tar.gz\ninstall -m755 mcp-vl-msa-rs-*/mcp-vl-msa-rs ~/.local/bin/\n```\n\nPrebuilt targets (Linux + Android): `x86_64-unknown-linux-gnu`,\n`x86_64-unknown-linux-musl`, `aarch64-unknown-linux-gnu`,\n`aarch64-unknown-linux-musl` (edge / ARM / Termux), `aarch64-linux-android`.\n\n**macOS**: no prebuilt binary is shipped (it would need Apple code-signing).\nInstall from source instead — `cargo install` below compiles it on your Mac in\none command, no signing needed.\n\n**From source** (Rust toolchain) — `--locked` is required (the workspace\n`Cargo.lock` pins a working `time` / `tantivy-common` resolution; a fresh\nresolve breaks the build), and `mcp-msa-server` is the package name (the\nbinary it installs is `mcp-vl-msa-rs`):\n\n```bash\ncargo install --git https://github.com/DioNanos/mcp-vl-msa-rs \\\n  --locked --features source-fs mcp-msa-server\n```\n\n## Build & test\n\n```bash\ncd mcp-vl-msa-rs\n\n# Default: pure BM25, zero network deps\ncargo build --release\ncargo test\n\n# Hybrid sparse + dense (in-process Candle rerank, no external service)\ncargo build --release --features embeddings\ncargo test  --features embeddings\n```\n\n### Hybrid mode config\n\nAdd `[embeddings]` to `MCP_MSA_CONFIG` to activate dense rerank. Without\nthis section the server stays in BM25-only mode even when the binary was\nbuilt with `--features embeddings`.\n\nThe production backend is `candle-modernbert`: the encoder runs **in-process**\n(Candle), offline-deterministic, from a local model bundle — no daemon, no\nnetwork at runtime, no automatic downloads. Prepare the bundle once with\n`scripts/prepare-granite-r2-97m.sh`.\n\n```toml\n[storage]\nstorage_dir = \"~/.local/state/mcp-vl-msa-rs\"\n\n[chunking]\nchunk_size = 64\noverlap = 0\n\n[embeddings]\nbackend   = \"candle-modernbert\"\nmodel_dir = \"~/.local/share/mcp-vl-msa-rs/models/granite-r2-97m\"\ndim       = 768\nmodel_id  = \"granite-r2-97m\"\n```\n\nA transitional `backend = \"ollama\"` (HTTP to an Ollama-compatible service)\nstill exists but is **deprecated and scheduled for removal in v0.6** — do not\nbuild new setups on it.\n\nThe AI client opts into hybrid scoring per-call by passing `dense_alpha`\nto `msa_search` (or any future tool that supports it). `dense_alpha = 1.0`\n(default) is BM25-only; `0.0` is dense-only; intermediate values are a\nlinear blend `α·bm25 + (1-α)·((cos+1)/2)`. Cosine is shifted to `[0,1]`\nso it composes linearly with the already max-normalized BM25 score.\n\n## Run as MCP stdio\n\n```bash\n# Default storage: ~/.local/state/mcp-vl-msa-rs/\n./target/release/mcp-vl-msa-rs\n\n# With explicit config\nMCP_VL_MSA_CONFIG=~/.config/mcp-vl-msa-rs/config.toml \\\nMCP_DEVICE=my-node \\\n./target/release/mcp-vl-msa-rs\n```\n\nExample `~/.codex/config.toml` entry:\n\n```toml\n[mcp_servers.vl_msa]\ncommand = \"/path/to/mcp-vl-msa-rs/target/release/mcp-vl-msa-rs\"\nenv = { MCP_DEVICE = \"my-node\" }\n# let the model call tools without a per-call approval prompt\ndefault_tools_approval_mode = \"approve\"\n```\n\nEquivalent `~/.claude.json` entry for Claude Code:\n\n```json\n{\n  \"mcpServers\": {\n    \"vl_msa\": {\n      \"command\": \"/path/to/mcp-vl-msa-rs/target/release/mcp-vl-msa-rs\",\n      \"env\": { \"MCP_DEVICE\": \"my-node\" }\n    }\n  }\n}\n```\n\n### AI client compatibility\n\n- Clients with partial MCP support may not surface the server's `instructions`\n  text. The tool descriptions and request-field descriptions are self-contained,\n  so a model can work from those alone.\n- Read-only tools (`msa_search`, `msa_fetch_doc`, `msa_stats`,\n  `msa_list_collections`, `msa_manifest`, `msa_search_iterative`,\n  `msa_interleave_round`) carry the `readOnlyHint` annotation, which lets a\n  gating client auto-approve them.\n- If a model reports an \"unsupported call\" or \"user cancelled\" on codex, that is\n  the approval gate, not a server fault — set `default_tools_approval_mode`\n  (above) so tool calls are not blocked on a prompt.\n\n## Storage layout\n\n```\n~/.local/state/mcp-vl-msa-rs/\n├── <collection_a>/        ← tantivy index directory\n├── <collection_b>/\n└── ...\n```\n\nEach collection is an independent tantivy index. Collection names are validated\n(rejected if they contain path separators, `..`, etc.) so a collection cannot\nescape the root.\n\n## Roadmap\n\nShipped:\n\n- **v0.2** — `SearchFilter` (where_eq / where_in / created range), post-retrieval.\n- **v0.3** — `msa_search_iterative` Memory Interleave with server-side cursor + TTL'd `MsaSession` registry.\n- **v0.4** — hybrid BM25 + dense rerank behind feature flag `embeddings`, Ollama backend, per-call `dense_alpha`; agent-memory surface (`msa_remember` / `msa_forget`); filesystem source metadata (`created_at` / `source` / `ext` / `dir`) at index time; exact `num_documents` / `total_tokens` in `msa_stats`; `msa-bench` reproducible benchmark crate; prebuilt-binary packaging.\n\nNext (not yet built):\n\n- Query-time tantivy filter (today `SearchFilter` runs post-retrieval; fine for\n  normal corpora, but a pre-filter would help when selectivity is high on a very\n  large index).\n- ACL for multi-tenant collections.\n- Tool-description tuning.\n\n## Related work\n\n- **MSA paper** ([arXiv:2603.23516](https://arxiv.org/abs/2603.23516)) — the\n  architectural inspiration (neural, intrinsic); this repo is an extrinsic,\n  pure-Rust approximation of the macro pattern.\n- **Vivling** (in `codex-vl`) — the first downstream consumer: this server is\n  its long-term memory.\n- **[mcp-memory-rs](https://github.com/DioNanos/mcp-memory-rs)** — the companion\n  server for *curated* agent state (named JSON categories, per-device ACL,\n  fleet sync). This server does corpus recall; together they cover both halves\n  of agent memory: the curated notebook and the queryable library.\n\n## License\n\nApache-2.0. See [LICENSE](./LICENSE).\n",
  "bytes": 11990,
  "sha": "c370328b6699f86e15180356c3f094557dea318b83763a2234140ad3f129b216",
  "repo_slug": "dionanos/mcp-vl-msa-rs",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_dionanos_mcp_vl_msa_rs_cd06c0fb/readme"
}