# Nexus-MCP [![PyPI version](https://img.shields.io/pypi/v/nexus-mcp-ci)](https://pypi.org/project/nexus-mcp-ci/) [![PyPI downloads](https://img.shields.io/endpoint?url=https%3A%2F%2Fjaggernaut007.github.io%2Fnexus-mcp%2Fdownloads-badge.json)](https://pypistats.org/packages/nexus-mcp-ci) [![Python 3.10–3.12](https://img.shields.io/badge/python-3.10--3.12-blue)](https://pypi.org/project/nexus-mcp-ci/) [![License: PolyForm Noncommercial 1.0.0](https://img.shields.io/badge/license-PolyForm%20Noncommercial%201.0.0-blue.svg)](#license) [![CI](https://github.com/jaggernaut007/Nexus-MCP/actions/workflows/publish.yml/badge.svg)](https://github.com/jaggernaut007/Nexus-MCP/actions/workflows/publish.yml) [![Glama MCP server](https://glama.ai/mcp/servers/jaggernaut007/Nexus-MCP/badges/card.svg)](https://glama.ai/mcp/servers/jaggernaut007/Nexus-MCP) **Hybrid search + code graph + semantic memory in a single local MCP server — under 350 MB RAM.** Nexus-MCP is a code intelligence server for the [Model Context Protocol](https://modelcontextprotocol.io). It gives AI agents precise, token-efficient answers about your codebase without cloud dependencies: no API keys, no data egress, no subscriptions. ``` pip install nexus-mcp-ci claude mcp add nexus-mcp-ci -- nexus-mcp-ci ``` --- ## The Problem It Solves AI coding agents are token-inefficient by default. An agent trying to understand `verify_credentials()` typically: 1. `Glob("src/**/*.py")` → 120 files returned, agent reads the most likely 8 → **~12,000 tokens** 2. `Grep("verify_credentials")` → 3 matches, agent reads surrounding context → **~4,000 tokens** 3. `Read("auth/middleware.py")` → full 400-line file to understand callers → **~3,000 tokens** **Total: ~19,000 tokens, 3+ tool calls, no graph relationships.** With Nexus-MCP: 1. `explain("verify_credentials")` → symbol definition + related code + quality metrics (callers and callees once call edges are extracted) → **~1,500 tokens, 1 tool call** Or for discovery: 1. `search("credential verification flow")` → top-10 semantically relevant chunks across the codebase → **~2,000 tokens, 1 tool call** **Estimated savings: 30–60% token reduction per coding session.** The exact numbers depend on codebase size and task type — see the [benchmarks table](#token-efficiency) below. --- ## Quickstart (60 seconds) ```bash # 1. Install pip install nexus-mcp-ci # 2. Register with Claude Code claude mcp add nexus-mcp-ci -- nexus-mcp-ci # 3. Verify (in any Claude Code session) # Claude will automatically use nexus-mcp-ci tools when CLAUDE.md instructs it ``` Then drop a `CLAUDE.md` in your project root: ```markdown ## Code Navigation Use nexus-mcp-ci tools before built-in file tools: - Start sessions with `mcp__nexus-mcp__status`; run `index` if needed - `search` before `Read/Grep` - `explain` instead of reading a file to understand a symbol - `graph(..., transitive=True)` before any refactor (the graph has no `CALLS` edges yet, so also use `search` to find call sites) ``` That's it. Claude will index your project on first use and use Nexus-MCP tools automatically. --- ## How It Works ### Indexing Pipeline (8 steps) ``` Source files │ ├─ Step 1: Discover ──────── walk tree, filter by ext/size/.gitignore │ ├─ Step 2: Parse symbols ─── tree-sitter (parallel ThreadPool) │ extracts: functions, classes, methods │ captures: name, signature, docstring, line_start/end, language │ ├─ Step 3: Parse graph ────── ast-grep (sequential for consistency) │ extracts: containment edges, import edges (no call/inheritance edges yet) │ output: UniversalGraph — nodes: modules, functions, classes; │ edges: CONTAINS, IMPORTS │ ├─ Step 4: Transfer graph ── populate rustworkx PyDiGraph │ O(1) node lookup by name, Rust-backed traversal │ ├─ Step 5: Chunk ──────────── Symbol → CodeChunk │ deterministic IDs: SHA256(file_path + symbol_name + line) │ avoids duplicate inserts on incremental reindex │ ├─ Step 6: Embed ──────────── bge-small-en: 384-dim (default) or jina-code: 768-dim via ONNX │ lazy-loaded, unloaded after indexing (try/finally) │ GPU/MPS auto-detected; falls back to CPU │ ├─ Step 7: Store ──────────── write to LanceDB `chunks` table (12-col PyArrow schema) │ rebuild native FTS (Tantivy) index after write │ └─ Step 8: Cleanup ────────── unload model, persist metadata (mtimes for incremental) save rustworkx graph to SQLite (warm-start recovery) ``` **Incremental reindex:** mtime-based — only changed files are re-processed. Corrupt index detection triggers automatic full rebuild. ### Search Pipeline ``` search("how does auth work") │ ├─► vector_engine.search(query, n=30) ← cosine similarity on 384-dim (bge-small-en) or 768-dim (jina-code) embeddings │ "auth" finds "verify_credentials", "token_check" │ ├─► bm25_engine.search(query, n=30) ← Tantivy FTS on same LanceDB table │ fast exact-keyword matching │ ├─► graph_engine.boost(query, n=30) ← structural relevance score │ hub symbols (high in/out degree) boosted │ └─► fusion.merge(v_results, b_results, g_results) │ │ Reciprocal Rank Fusion: score = Σ weight_i / (k + rank_i) │ default weights: vector=0.5, bm25=0.3, graph=0.2 │ ├─► reranker.rerank(top_20) ← FlashRank (optional, 4MB ONNX model, <10ms) │ └─► token_budget.truncate() ← summary / detailed / full │ └─► Top-N chunks, scored, formatted ``` ### Technology Stack | Layer | Technology | Decision Rationale | |-------|-----------|-------------------| | **Vector store** | LanceDB | mmap disk-backed → ~20–50 MB overhead vs ChromaDB's in-memory model. Native Tantivy FTS means one store for both vector and BM25. ([ADR-002](docs/adr/ADR-002-lancedb-over-chromadb.md)) | | **Embeddings** | bge-small-en (default) or ONNX Runtime + jina-code | bge-small-en is lightweight (384-dim, no trust_remote_code). jina-code is code-specific (161M params, 8192 seq len) on ONNX (~50 MB vs PyTorch ~500 MB). Lazy-load/unload keeps RAM flat after indexing. ([ADR-003](docs/adr/ADR-003-onnx-runtime-over-pytorch.md)) | | **Graph engine** | rustworkx PyDiGraph | Rust-backed, O(1) node lookup, PageRank + centrality algorithms. Thread-safe with RLock. ([ADR-006](docs/adr/ADR-006-rustworkx-graph-engine.md)) | | **Symbol parser** | tree-sitter 0.21.3 | 25+ languages, incremental parsing, AST-level symbol extraction with metadata. Parallel via ThreadPool. ([ADR-005](docs/adr/ADR-005-dual-parser-strategy.md)) | | **Graph parser** | ast-grep | Structural matching for containment and import edges (call and inheritance edges are not extracted yet). Sequential run for graph consistency. ([ADR-005](docs/adr/ADR-005-dual-parser-strategy.md)) | | **Chunking** | Symbol-based | One chunk per function/class. Deterministic SHA256 IDs prevent duplicate inserts. ([ADR-008](docs/adr/ADR-008-code-chunk-strategy.md)) | | **Re-ranker** | FlashRank (optional) | 4 MB ONNX cross-encoder, <10 ms on CPU for top-20. Graceful passthrough if not installed. | | **Persistence** | SQLite + LanceDB | Graph in SQLite (warm-start recovery), vectors+FTS in LanceDB, mtimes in JSON. Zero-config. | | **MCP framework** | FastMCP 2.0 | Stdio transport, automatic tool registration, schema generation. | --- ## Token Efficiency Measured against equivalent agentic file-browsing workflows on a ~10,000-line Python codebase: | Task | Without Nexus-MCP | With Nexus-MCP | Reduction | |------|:-----------------:|:--------------:|:---------:| | Find relevant code (agent reads 5–10 files) | 5,000–15,000 tokens | 500–2,000 tokens | **70–90%** | | Understand a symbol (grep + read + trace callers) | 3,000–8,000 tokens, 3–5 calls | 800–2,000 tokens, 1 call | **60–75%** | | Assess change impact (manual transitive trace) | 10,000–20,000 tokens | 1,000–3,000 tokens | **80–85%** | | Tool descriptions in context (2 MCP servers) | ~1,700 tokens (17 tools) | ~700 tokens (10 tools) | **~60%** | | Search precision (keyword-only needs retries) | 2–3 searches × 2,000 tokens | 1 hybrid search × 1,500 tokens | **60–75%** | **Typical session savings: 15,000–40,000 tokens (30–60%)** compared to file-browsing agents. ### Three Verbosity Levels `explain` takes a `verbosity` parameter — agents request exactly the detail they need. The other tools return token-budgeted responses (`formatting/token_budget.py`): | Level | Token Budget | What's Included | |-------|:-----------:|-----------------| | `summary` | ~500 tokens | Counts, scores, file:line pointers only | | `detailed` | ~2,000 tokens | Signatures, types, line ranges, docstrings | | `full` | ~8,000 tokens | Full code snippets, all relationships, metadata | --- ## The 10 Tools **v2.0.0 breaking change:** `find_callers`/`find_callees`/`impact` merged into `graph`, `overview`/`architecture` merged into `map`, and `remember`/`recall`/`forget` merged into `memory` — see [CHANGELOG](CHANGELOG.md) for the old→new mapping and [ADR-017](docs/adr/ADR-017-tool-consolidation.md) for why. Fewer, richer tools route better under MCP Tool Search than many thin ones. ### Discovery & Indexing | Tool | Use When | |------|----------| | `index(path)` | First action in any session. Supports comma-separated multi-folder paths. Incremental by default, reports progress as it runs, and starts a debounced auto-reindex watcher (`NEXUS_AUTO_WATCH`) when it finishes. | | `status()` | Check index health: symbol count, chunk count, memory usage, engine availability, and a `stale`/`staleness_warning` pair if files changed since the last index. | | `health()` | Liveness probe — uptime, which engines are ready. | | `map(detail)` | **Replaces `ls` + manual browsing.** `detail="summary"` (files/languages/quality/top-modules, was `overview()`), `"architecture"` (layers/dependencies/classes/entry points/hub symbols, was `architecture()`), or `"full"` for both. | ### Search | Tool | Use When | |------|----------| | `search(query, limit, language, symbol_type, mode, rerank, live_grep)` | Primary code discovery. `mode`: `hybrid` (default), `vector`, or `bm25`. Falls back to live grep if results are sparse. Returns a non-null `warning` if the index looked stale (a background reindex is triggered automatically; results still return immediately). | ### Graph Analysis | Tool | Use When | |------|----------| | `find_symbol(symbol_name, exact)` | Look up a specific symbol. `exact=False` for fuzzy matching. | | `graph(symbol_name, direction, transitive, max_depth)` | `direction="callers"` (who calls this, was `find_callers`) or `"callees"` (what this calls, was `find_callees`). **`transitive=True`** (was `impact()`) is meant to give the transitive change blast radius before a refactor. It requires `direction="callers"`; `direction="callees"` with `transitive=True` returns an error. **Call edges are not extracted yet, so callers, callees and impact results are empty today** — use `search` to find call sites (see [Known Limitations](#known-limitations)). | | `explain(symbol_name, verbosity)` | **Replaces `Read` for understanding code.** Graph relationships + semantic context + quality metrics in one call. | | `analyze(path)` | Code quality: cyclomatic complexity, cognitive complexity, code smells, dependency metrics. | ### Memory | Tool | Use When | |------|----------| | `memory(action, ...)` | `action="store"` (was `remember`) to persist a decision/note across sessions (types: `note`, `decision`, `conversation`, `status`, `preference`, `doc`; TTL: `permanent`, `month`, `week`, `day`, `session`); `"search"` (was `recall`) for semantic retrieval; `"delete"` (was `forget`) to remove by ID, tag, or type. | --- ## Install ### From PyPI (recommended) ```bash pip install nexus-mcp-ci # GPU (CUDA) support — adds ONNX CUDA execution provider pip install nexus-mcp-ci[gpu] # FlashRank reranker — adds ~4MB cross-encoder for better search quality pip install nexus-mcp-ci[reranker] # Both pip install nexus-mcp-ci[gpu,reranker] ``` ### From Source ```bash git clone https://github.com/jaggernaut007/Nexus-MCP.git cd Nexus-MCP ./setup.sh # creates venv, installs, verifies # or pip install -e ".[dev]" ``` **Python 3.10–3.12 supported.** Python 3.13+ is not yet supported by the current dependency stack, and the packaged Glama/Docker build uses Python 3.12 for compatibility. Optional: `rg` (ripgrep) for 100% search coverage fallback on unindexed files. > The optional `jina-code` model requires ONNX Runtime. If you see ONNX/Optimum errors: > ```bash > pip install "sentence-transformers[onnx]" "optimum[onnxruntime]>=1.19.0" > ``` > The default `bge-small-en` model needs neither ONNX nor `trust_remote_code`. --- ## MCP Client Setup ### Claude Code ```bash # Minimal claude mcp add nexus-mcp-ci -- nexus-mcp-ci # With the code-specific embedding model (requires trust_remote_code) claude mcp add nexus-mcp-ci -e NEXUS_EMBEDDING_MODEL=jina-code -- nexus-mcp-ci # GPU embeddings claude mcp add nexus-mcp-ci -e NEXUS_EMBEDDING_DEVICE=cuda -- nexus-mcp-ci # Virtualenv install — pass the full binary path claude mcp add nexus-mcp-ci -- /path/to/.venv/bin/nexus-mcp-ci ``` ### Claude Desktop `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS): ```json { "mcpServers": { "nexus-mcp-ci": { "command": "nexus-mcp-ci", "args": [], "env": { "NEXUS_EMBEDDING_MODEL": "jina-code" } } } } ``` ### Cursor / Windsurf / Cline / Any MCP Client ```json { "nexus-mcp-ci": { "command": "nexus-mcp-ci", "transport": "stdio" } } ``` --- ## Agent Integration Patterns ### CLAUDE.md boilerplate (drop into project root) ```markdown ## Code Intelligence — nexus-mcp-ci Every code task in this project MUST follow this workflow: 1. **Session start**: `mcp__nexus-mcp__status` → if not indexed, `mcp__nexus-mcp__index` 2. **Before any file read**: `mcp__nexus-mcp__search` to locate relevant code 3. **To understand a symbol**: `mcp__nexus-mcp__explain` (not Read) 4. **Before refactoring**: `mcp__nexus-mcp__graph` with `transitive=True` to assess blast radius. The current graph has no `CALLS` edges, so use `mcp__nexus-mcp__search` to find call sites. 5. **For project orientation**: `mcp__nexus-mcp__map` with `detail="summary"` or `detail="architecture"` ``` ### Typical agent tool-call sequence ``` # Session start status() → "indexed: True, 8,412 chunks, 1,203 symbols, 87 MB" # Code discovery search("JWT token validation", mode="hybrid", limit=10) → auth/jwt.py:42 validate_token() score=0.94 → auth/middleware.py:18 require_auth() score=0.87 → tests/test_auth.py:91 test_valid_jwt() score=0.81 # Deep symbol understanding explain("validate_token") → definition, location, related code → callers: [], callees: [] # empty until call edges are extracted (see Known Limitations) # Pre-refactor safety check graph("validate_token", direction="callers", transitive=True) → total_impacted: 0 # no CALLS edges on a real index yet, so this is NOT proof of no callers # Locate call sites with search instead search("validate_token(", mode="bm25") → auth/middleware.py:18 require_auth() ... → auth/login.py:57 login_required() ... ``` ### Multi-folder monorepo indexing ```python # Index multiple roots in one call — processed sequentially, shared engines index(path="packages/api/src,packages/shared/src,packages/cli/src") # Or use the paths parameter for additional roots index(path="packages/api/src", paths="packages/shared/src,packages/cli/src") ``` --- ## Configuration All settings via `NEXUS_` environment variables: | Variable | Default | Description | |----------|---------|-------------| | `NEXUS_EMBEDDING_MODEL` | `bge-small-en` | `bge-small-en` (384-dim, lightweight) or `jina-code` (768-dim, code-optimized) | | `NEXUS_EMBEDDING_DEVICE` | `auto` | `auto` (CUDA → MPS → CPU), `cuda`, `mps`, `cpu` | | `NEXUS_STORAGE_DIR` | `.nexus` | Index storage directory | | `NEXUS_AUTO_WATCH` | `true` | Auto-reindex on file change via a debounced watcher, started after `index()` | | `NEXUS_STALENESS_CHECK_INTERVAL` | `15` | Seconds between `status()`/`search()` staleness checks (throttled, not per-call) | | `NEXUS_MAX_FILE_SIZE_MB` | `10` | Skip files larger than this | | `NEXUS_CHUNK_MAX_CHARS` | `4000` | Max chars per code chunk | | `NEXUS_MAX_MEMORY_MB` | `350` | Memory budget target | | `NEXUS_SEARCH_MODE` | `hybrid` | `hybrid`, `vector`, or `bm25` | | `NEXUS_FUSION_WEIGHT_VECTOR` | `0.5` | Vector score weight in RRF | | `NEXUS_FUSION_WEIGHT_BM25` | `0.3` | BM25 score weight in RRF | | `NEXUS_FUSION_WEIGHT_GRAPH` | `0.2` | Graph score weight in RRF | | `NEXUS_PERMISSION_LEVEL` | `full` | `full`, `read`, or `restricted` | | `NEXUS_RATE_LIMIT_ENABLED` | `false` | Enable per-tool token-bucket rate limiting | | `NEXUS_AUDIT_ENABLED` | `true` | Structured audit logging with correlation IDs | | `NEXUS_TRUST_REMOTE_CODE` | `true` | Required for jina-code; set `false` with bge-small-en | | `NEXUS_LOG_LEVEL` | `INFO` | Logging level | | `NEXUS_LOG_FORMAT` | `text` | `text` or `json` | The Python package defaults to `bge-small-en`. The `Dockerfile` and `smithery.yaml` set `jina-code` explicitly, so those deployments use `jina-code` unless you override it. ### Embedding Models | Model | Key | Dims | Max Seq | Backend | `trust_remote_code` | |-------|-----|:----:|:-------:|---------|:-------------------:| | BGE Small EN v1.5 (default) | `bge-small-en` | 384 | 512 | PyTorch | No | | Jina Embeddings v2 Code | `jina-code` | 768 | 8,192 | ONNX | Yes | **After changing model, re-index.** Embeddings from different models are incompatible. --- ## Comparison ### vs. Other MCP Servers | Feature | Nexus-MCP | Sourcegraph MCP | Greptile MCP | GitHub MCP | tree-sitter MCP | |---------|:---:|:---:|:---:|:---:|:---:| | Fully local / private | ✅ | ❌ infra required | ❌ cloud | ❌ cloud | ✅ | | Semantic (vector) search | ✅ | ❌ keyword only | ✅ LLM-based | ❌ | ❌ | | Keyword (BM25) search | ✅ | ✅ | — | ✅ | ❌ | | Hybrid fusion (RRF) | ✅ | ❌ | ❌ | ❌ | ❌ | | Code graph (call/import) | partial: containment + imports (call edges planned) | ✅ SCIP | ❌ | ❌ | ❌ | | Re-ranking | ✅ FlashRank | ❌ | — | ❌ | ❌ | | Semantic memory (persistent) | ✅ 6 types | ❌ | ❌ | ❌ | ❌ | | Change impact analysis | planned (needs call edges) | partial | ❌ | ❌ | ❌ | | Token-budgeted responses | ✅ 3 levels | ❌ | ❌ | ❌ | ❌ | | Languages | 25+ | 30+ | many | many | many | | Cost | **Free noncommercial; paid commercial license** | $$$ | $40/mo | $10–39/mo | Free | | API keys required | **No** | Yes | Yes | Yes | No | ### vs. AI Code Tools | Capability | Nexus-MCP | Cursor | Copilot @workspace | Cody | Continue.dev | Aider | |---|:---:|:---:|:---:|:---:|:---:|:---:| | IDE-agnostic | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | | MCP-native | ✅ | partial | ❌ | ❌ | ✅ client | ❌ | | Fully local | ✅ | partial | ❌ | partial | ✅ | ✅ | | Hybrid search | ✅ | unknown | unknown | keyword | yes | ❌ | | Code graph | partial (containment + imports) | unknown | unknown | ✅ SCIP | basic | ❌ | | Semantic memory | ✅ persistent | ❌ | ❌ | ❌ | ❌ | ❌ | | Token-budgeted output | ✅ | — | — | — | — | — | | Source available | ✅ PolyForm Noncommercial | ❌ | ❌ | partial | ✅ | ✅ | | Cost | **Free noncommercial; paid commercial license** | $20–40/mo | $10–39/mo | $0–49/mo | Free | Free | --- ## Development ```bash git clone https://github.com/jaggernaut007/Nexus-MCP.git cd Nexus-MCP pip install -e ".[dev]" pytest -v # 607 tests pytest -m "not slow" # skip performance benchmarks pytest tests/test_hybrid_search.py # single module ruff check . # lint ``` ### Project Structure ``` src/nexus_mcp/ ├── server.py # FastMCP entrypoint — 10 thin tool wrappers (permission guard, rate limit, audit), graceful shutdown ├── core_api.py # Transport-agnostic tool logic + input validation; importable without MCP ├── config.py # Settings (NEXUS_ env prefix) ├── state.py # Global singleton SessionState ├── core/ │ ├── models.py # Symbol, ParsedFile, CodebaseIndex, Memory │ ├── graph_models.py # UniversalNode, Relationship │ ├── interfaces.py # IParser, IEngine protocols │ └── exceptions.py # NexusException hierarchy ├── parsing/ │ ├── treesitter_parser.py # Symbol extraction (parallel) │ ├── astgrep_parser.py # Structural graph extraction (sequential) │ ├── language_registry.py # 25+ language definitions │ └── file_watcher.py # Debounced watchdog for live reindex ├── engines/ │ ├── vector_engine.py # LanceDB cosine similarity search │ ├── bm25_engine.py # LanceDB native FTS (Tantivy) │ ├── graph_engine.py # rustworkx PyDiGraph with RLock │ ├── live_grep.py # rg/grep fallback for unindexed or sparse results │ ├── fusion.py # Reciprocal Rank Fusion │ └── reranker.py # FlashRank (optional, graceful degradation) ├── indexing/ │ ├── pipeline.py # 8-step indexing pipeline │ ├── embedding_service.py # ONNX Runtime, GPU/MPS auto-detect │ ├── parallel_indexer.py # ThreadPool over files │ └── chunker.py # Symbol → CodeChunk with deterministic IDs ├── memory/ │ └── memory_store.py # LanceDB-backed memory, TTL, 6 types ├── analysis/ │ └── code_analyzer.py # Cyclomatic/cognitive complexity, smells ├── formatting/ │ ├── token_budget.py # Token counting and truncation │ └── response_builder.py # Structured responses ├── persistence/ │ └── store.py # SQLite graph persistence ├── security/ │ ├── permissions.py # READ/MUTATE/WRITE tool categories │ └── rate_limiter.py # Token-bucket, per-tool, thread-safe └── middleware/ └── audit.py # Structured audit logs, correlation IDs, field redaction benchmarks/ # Token-efficiency benchmark harness (ADR-018) self_test/ # End-to-end demo of all 10 tools plugin/ # Claude Code plugin: registers the server + routing skill ``` ### Adding a New Tool 1. Add the logic as a plain function in `core_api.py` 2. Validate input there with the `validate_*` helpers (`validate_path`, `validate_symbol_name`, `validate_query`) 3. Add a thin wrapper in `server.py` decorated with `@mcp.tool()` and `@_audited`, and call `_guard("")` first 4. Add the permission category to `security/permissions.py` 5. Write tests in `tests/` 6. Update `self_test/demo_mcp.py` to exercise the tool ### Adding a New Language 1. Add entry to `parsing/language_registry.py` with the tree-sitter grammar 2. Add structural patterns to `parsing/astgrep_parser.py` for call/import extraction 3. Add test fixtures in `tests/fixtures/` --- ## Self-Test Verify your installation exercises all 10 tools end-to-end: ```bash python self_test/demo_mcp.py # built-in sample project python self_test/demo_mcp.py /path/to/project # your own codebase ``` Expected output: all 10 tools exercised with pass/fail per tool and a summary. --- ## Known Limitations - **Sequential graph parsing**: ast-grep runs sequentially (not parallel) to keep the call graph consistent. This is the main indexing bottleneck on large codebases. - **bge-small-en uses PyTorch**: The lightweight model uses PyTorch instead of ONNX, so it doesn't benefit from the same ~50 MB footprint as jina-code. - **No incremental graph updates**: Graph is rebuilt in full on incremental reindex (only vector/BM25 are incremental at the chunk level). - **No SSE transport**: Only stdio transport is currently supported. - **Language coverage**: 25+ languages get tree-sitter symbol extraction. ast-grep structure (functions, classes, imports) covers Python, JavaScript, TypeScript, Go, Java, and Rust; other languages have no graph structure. - **Call edges are not populated yet**: the graph holds `CONTAINS` and `IMPORTS` edges only. No parser creates `CALLS` edges, so `graph(direction="callers"/"callees")`, `graph(transitive=True)` and the `callers`/`callees` fields of `explain` return empty lists today. Use `search` to locate call sites until call-edge extraction lands. `find_symbol` returns definitions, not callers. Function complexity and docstring fields on graph nodes are also empty, so `analyze` complexity scores are not yet meaningful. - **Static call graph only (once populated)**: call edges will come from static parsing, not runtime tracing — dynamic dispatch, monkey-patching, and calls made through callbacks/closures/reflection won't show up. Treat `graph(transitive=True)` as a lower bound on blast radius in highly dynamic code. - **Auto-reindex has a detection lag**: with the file watcher enabled (default), edits are picked up after a short debounce, and `status()`/`search()` run a throttled staleness check as a backstop — not an instant, per-call guarantee of freshness. --- ## Architecture Decision Records Key decisions are documented in [docs/adr/](docs/adr/): | ADR | Decision | |-----|----------| | [ADR-001](docs/adr/ADR-001-single-mcp-consolidation.md) | Merge two MCP servers into one | | [ADR-002](docs/adr/ADR-002-lancedb-over-chromadb.md) | LanceDB over ChromaDB | | [ADR-003](docs/adr/ADR-003-onnx-runtime-over-pytorch.md) | ONNX Runtime over PyTorch for embeddings | | [ADR-004](docs/adr/ADR-004-bge-small-default-model.md) | bge-small-en as default embedding model | | [ADR-005](docs/adr/ADR-005-dual-parser-strategy.md) | Dual parser: tree-sitter + ast-grep | | [ADR-006](docs/adr/ADR-006-rustworkx-graph-engine.md) | rustworkx for graph algorithms | | [ADR-007](docs/adr/ADR-007-lancedb-schema-design.md) | 12-column PyArrow schema for LanceDB | | [ADR-008](docs/adr/ADR-008-code-chunk-strategy.md) | Symbol-based chunking with deterministic IDs | | [ADR-009](docs/adr/ADR-009-indexing-pipeline-architecture.md) | 8-step indexing pipeline | | [ADR-010](docs/adr/ADR-010-graph-tools-api-design.md) | Graph tools API: serialization, ambiguity handling | | [ADR-011](docs/adr/ADR-011-hardening-decisions.md) | Graceful shutdown, corruption recovery, JSON logging | | [ADR-012](docs/adr/ADR-012-tool-permission-model.md) | READ/MUTATE/WRITE permission categories | | ~~[ADR-013](docs/adr/ADR-013-pydantic-schemas.md)~~ | Pydantic v2 I/O schemas — superseded by ADR-016 (never wired in, deleted) | | [ADR-014](docs/adr/ADR-014-rate-limiting.md) | Token-bucket rate limiting (off by default) | | [ADR-015](docs/adr/ADR-015-auto-watch-and-staleness-detection.md) | Auto-watch + throttled staleness detection | | [ADR-016](docs/adr/ADR-016-remove-unused-pydantic-schemas.md) | Removal of unused Pydantic schemas (supersedes ADR-013) | | [ADR-017](docs/adr/ADR-017-tool-consolidation.md) | Tool consolidation 15→10, action-aware permission categories | | [ADR-018](docs/adr/ADR-018-token-efficiency-benchmark.md) | Token-efficiency benchmark harness (`benchmarks/`) | --- ## Documentation - [Installation Guide](docs/INSTALLATION.md) — Prerequisites, client-specific setup, troubleshooting - [Architecture](docs/ARCHITECTURE.md) — Data flow, component design, memory budget analysis - [Usage Guide](docs/USAGE_GUIDE.md) — Full tool reference with examples - [Developer Guide](docs/DEVELOPER_GUIDE.md) — Contributing, adding tools/engines/languages - [Research Notes](docs/RESEARCH.md) — Library evaluations and technology deep-dives --- ## Acknowledgments Nexus-MCP consolidates two earlier open-source projects: - **[CodeGrok MCP](https://github.com/shreyasjagannath/CodeGrok_mcp)** by [rdondeti](https://github.com/rdondeti) (Ravitez Dondeti, MIT) — Contributed the symbol extraction pipeline, embedding service, parallel indexer, core data models, and memory retrieval system. - **[code-graph-mcp](https://github.com/entrepeneur4lyf/code-graph-mcp)** by [entrepeneur4lyf](https://github.com/entrepeneur4lyf) — Contributed the ast-grep structural parser, rustworkx graph engine, complexity analysis, and relationship extraction. Source files retain "Ported from" attribution in their module docstrings. See [ADR-001](docs/adr/ADR-001-single-mcp-consolidation.md) for the consolidation rationale. --- ## License [PolyForm Noncommercial License 1.0.0](LICENSE). Free to use, copy, modify, and distribute for any noncommercial purpose. Commercial use requires a separate license — contact Shreyas Jagannath to inquire. Versions published before 2.0.1 (0.1.0, 0.1.1, 2.0.0) remain available under their original MIT terms for anyone who obtained them under that license.