Skip to main content
Glama
README.md
# Nexus-MCP

[![PyPI version](https://img.shields.io/pypi/v/nexus-mcp-ci)](https://pypi.org/project/nexus-mcp-ci/)
[![PyPI downloads](https://static.pepy.tech/badge/nexus-mcp-ci)](https://pepy.tech/projects/nexus-mcp-ci)
[![PyPI downloads (pypistats)](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. About 90 MB idle and 460 MB with the embedding model loaded ([measurements](https://github.com/jaggernaut007/Nexus-MCP/blob/main/docs/MEMORY.md)).**

<!-- mcp-name: io.github.jaggernaut007/nexus-mcp -->

Nexus-MCP is a local code-intelligence MCP server for Claude Code, Codex, Cursor and any [Model Context Protocol](https://modelcontextprotocol.io) client. It gives AI agents precise, token-efficient answers about your codebase without cloud dependencies: no API keys, no data egress, no subscriptions.

**Not sure yet? Measure it on your own code first.** It is free, runs offline, calls no LLM and takes a few minutes:

```bash
uvx nexus-mcp-ci eval            # the current folder
uvx nexus-mcp-ci eval --example  # or the Nexus-MCP source, if you have no project at hand
```

You get a verdict ("Strong fit", "Likely fit" or "Weak fit") and the tokens an agent needs to reach the right file, with and without Nexus-MCP. Nothing leaves your machine. See [Evaluate on your project](#evaluate-on-your-project).

**Ready?**

```bash
pip install nexus-mcp-ci
claude mcp add nexus-mcp -- nexus-mcp-ci
```

### Results at a glance

<!-- Generated from benchmarks/published/results.json by scripts/publish_results.py. Do not edit. -->
<!-- results:start -->
| Project | Cost: Grep and Read → Nexus-MCP | Turns | Correct answers |
|---|---:|---:|---:|
| Private Python project (460 files) | $0.571 → $0.446 (**-22%**) | 55 → 35 | 36 → 35 of 36 |
| [Django](https://github.com/django/django) (3,000 files) | $0.894 → $0.863 (**-3%**) | 93 → 71 | 34 → 36 of 36 |

Live runs: Claude Code, Claude Sonnet, headless (claude -p), 12 questions per project, 3 runs each, version 2.1.0 (2026-10-28). The same code can vary by about 20% in cost with 3 runs per question (measured 2026-10-28). Search hit@1 on the four retrieval suites: `jobscout` 0.81, `nexus_mcp` 0.80, `shop_repo` 0.50, `flask` 0.64.

`nexus-mcp-ci eval` (the free offline check, no LLM) on 3 projects:

| Project | Verdict | Median tokens to the right file | Right file first |
|---|---|---:|---:|
| [Chromium base/](https://github.com/chromium/chromium/tree/599789097af11286fca074380e8a3634cea2fa25/base) (3,638 files) | Strong fit | 17,998 → **950** | 40% → 70% |
| [pallets/click](https://github.com/pallets/click) (91 files) | Strong fit | 24,774 → **1,301** | 27% → 97% |
| Nexus-MCP source (59 files) | Likely fit | 5,385 → **1,234** | 70% → 93% |

All numbers: [benchmarks page](https://jaggernaut007.github.io/nexus-mcp/benchmarks.html) · [JSON](https://jaggernaut007.github.io/nexus-mcp/benchmarks.json) · [CSV](https://jaggernaut007.github.io/nexus-mcp/benchmarks.csv).
<!-- results:end -->

---

## The Problem It Solves

An agent that explores a codebase with Grep and Read pays for every call: each call is one more
turn, and each turn reads the whole conversation again. Nexus-MCP answers a question about code
in fewer calls. `search` returns the whole code of its top results, `find_symbol` returns the
code of several names in one call, and `graph` returns every text reference beside the call
edges.

The north star of the project is lower token use with the same accuracy. It is measured, not
estimated: see [Token efficiency](#token-efficiency) and `benchmarks/`.

---

## Evaluate on your project

Decide with your own numbers. The quick check is free, offline and calls no LLM:

```bash
uvx nexus-mcp-ci eval                                   # the current folder
uvx nexus-mcp-ci eval path/to/project                   # another folder
uvx nexus-mcp-ci eval https://github.com/pallets/flask  # a public repo (shallow clone)
uvx nexus-mcp-ci eval --example                         # the Nexus-MCP source, offline
```

It asks about 30 questions made from your own code and compares Grep and Read with one
Nexus-MCP search. The right answer is the file that defines the symbol, from the parser, never
from the Nexus-MCP ranking. On this repo's source (59 files) it gave "Likely fit": 1,234 median tokens to reach the right file against 5,385, and the right file first in 93% of
questions against 70%. On Chromium's `base/` (3,638 files of C++, Java and Python) it gave
"Strong fit": 950 median tokens against 17,998 (95% fewer), and the right file first in 70% of questions against 40%. On [pallets/click](https://github.com/pallets/click) (91 files, from its
URL) it gave "Strong fit": 1,301 median tokens against 24,774 (95% fewer), and the right file first in 97% of questions against 27%. On a tiny project Grep and Read win, and the report says
so.

Want the real agent cost? The agent tier runs your own Claude Code or Codex login on tasks from
your code, with and without Nexus-MCP. It shows the cost first and asks:

```bash
nexus-mcp-ci eval --agent claude                       # Claude Sonnet
nexus-mcp-ci eval --preset efficient --agent claude:haiku,claude:sonnet
```

Results are saved in `~/.nexus-mcp/evals/` (`eval list`, `eval show <id>`). `eval share <id>`
posts the numbers (no code, no paths) to the community table, only after you say yes. Full
guide: [docs/EVALUATE.md](https://github.com/jaggernaut007/Nexus-MCP/blob/main/docs/EVALUATE.md). In Claude Code, the plugin adds
`/nexus-mcp:evaluate`.

---

## Privacy and Data

Your code stays on your machine. The server makes no network calls and has no telemetry.

- **In:** the package from PyPI, and the embedding model from Hugging Face once (set
  `NEXUS_OFFLINE=1` after that). `eval <url>` also runs a `git clone`.
- **Out:** nothing, unless you run `eval --agent` (prompts and code go to Anthropic or OpenAI
  through your own CLI) or `eval share` (numbers only). Both ask first.
- **Stored:** the index, memories and graph in `<project>/.nexus/` (it holds code: add it to
  `.gitignore`), the model in `~/.cache/huggingface/`, eval results in `~/.nexus-mcp/evals/`.

```bash
nexus-mcp-ci data where              # every location and its size
nexus-mcp-ci data clear --all        # dry run; add --yes to delete
```

Every location, the shell commands that do the same, and the uninstall steps:
[docs/PRIVACY.md](https://github.com/jaggernaut007/Nexus-MCP/blob/main/docs/PRIVACY.md).

---

## Quickstart (60 seconds)

```bash
# 1. Install
pip install nexus-mcp-ci

# 2. Register with Claude Code
claude mcp add nexus-mcp -- nexus-mcp-ci

# 3. Verify: start Claude Code in your project and run /mcp
#    nexus-mcp should be listed as connected
```

The server sends its own usage instructions when it connects, so Claude knows which tool
answers which question without any extra setup. Ask something like "where do we retry
failed payments?" and it will call `search` (and `index` once, the first time in a project).

### Claude Code plugin (recommended)

The plugin registers the server and adds a short routing skill that teaches Claude when to
reach for each tool. Install the Python package first, then:

```
/plugin marketplace add jaggernaut007/Nexus-MCP
/plugin install nexus-mcp@nexus-mcp
```

Do not register the server twice. If you use the plugin, skip the `claude mcp add` line.
Tool names differ by route: `mcp__nexus-mcp__search` after `claude mcp add nexus-mcp`,
`mcp__plugin_nexus-mcp_nexus-mcp__search` through the plugin. The rest of this README
writes bare names (`search`) and you do not need the prefix in prompts.

### Optional: project instructions

If you want the rules in your own project file, a short block is enough:

```markdown
## Code Navigation

The nexus-mcp server is connected. For questions about how this codebase works:
- No setup call is needed; run `index` only if a tool says the project is not indexed.
- Use `search` to find code, `find_symbol` for symbols, `map` for structure.
- Use `graph` with `transitive=true` before you change a shared function. The call graph
  is static, so also use `search` for dynamic call sites.
- Use plain grep or read when you already know the file or the exact string.
```

The full routing guide is in [docs/AGENT_ROUTING.md](https://github.com/jaggernaut007/Nexus-MCP/blob/main/docs/AGENT_ROUTING.md).

---

## 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, callee names of each function
    │                     (indexing/call_resolver.py makes CALLS edges after the last batch;
    │                      no inheritance edges yet)
    │           output: UniversalGraph — nodes: modules, functions, classes;
    │                                    edges: CONTAINS, IMPORTS, CALLS
    │
    ├─ 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
    │           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) 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)
                  │
                  │  Reciprocal Rank Fusion: score = Σ weight_i / (k + rank_i)
                  │  default weights: vector=0.5, bm25=0.3
                  │
                  └─► 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](https://github.com/jaggernaut007/Nexus-MCP/blob/main/docs/adr/ADR-002-lancedb-over-chromadb.md)) |
| **Embeddings** | bge-small-en | Lightweight (384-dim, no trust_remote_code). Lazy-load/unload keeps RAM flat after indexing. ([ADR-003](https://github.com/jaggernaut007/Nexus-MCP/blob/main/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](https://github.com/jaggernaut007/Nexus-MCP/blob/main/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](https://github.com/jaggernaut007/Nexus-MCP/blob/main/docs/adr/ADR-005-dual-parser-strategy.md)) |
| **Graph parser** | ast-grep | Structural matching for containment and import edges (call edges are resolved after indexing; inheritance edges are not extracted yet). Sequential run for graph consistency. ([ADR-005](https://github.com/jaggernaut007/Nexus-MCP/blob/main/docs/adr/ADR-005-dual-parser-strategy.md)) |
| **Chunking** | Symbol-based | One chunk per function/class. Deterministic SHA256 IDs prevent duplicate inserts. ([ADR-008](https://github.com/jaggernaut007/Nexus-MCP/blob/main/docs/adr/ADR-008-code-chunk-strategy.md)) |
| **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

Two live runs, 12 questions each, with Claude Sonnet, 3 runs for each question and condition
(72 runs each) and the prompt cache warm for both conditions. **The result depends on the
project.**

**Latest (commit `a67e5b4`, `find_symbol` shows an outline of a long class):** the numbers are
in [Results at a glance](#results-at-a-glance), generated from
`benchmarks/published/results.json`.

Only the nexus condition ran again for this commit; the Grep and Read numbers are the stored
runs below. On django nexus is cheaper in total but still dearer on 8 of 12 questions (the short
lookups). The one wrong answer on the private project came from a run that did not call
`find_symbol`. With 3 runs for each question the same code can vary by about 20% in cost
(measured 2026-10-28), so the change against the runs before the outline is inside the noise;
all three recent nexus runs on the private project cost less than Grep and Read ($0.446 to
$0.531 against $0.571), and on django nexus is about equal ($0.863 to $0.973 against $0.894). Details:
`docs/EVALS.md`. The sections below are the runs before the outline.

**Private 460-file Python project** (2026-10-08, commit `91a338e`):

| | Grep and Read | Nexus-MCP |
|---|---:|---:|
| Turns (sum of the median of each question) | 55 | 34 (-38%) |
| Output tokens | 11,917 | 8,881 (-25%) |
| Cost | $0.571 | $0.459 (**-20%**) |
| Correct answers | 36 of 36 | 36 of 36 |
| Prompt on the first turn | 12,700 tokens | 16,300 tokens |

Nexus is cheaper on 7 questions, equal on 3 and dearer on 2 (the two impact questions, by
$0.005 and $0.004). The saving comes from fewer turns and from smaller tool results than the
whole-file reads of Grep and Read (see the cost split below). Per question numbers are in
`docs/EVALS.md`. The same questions on 2026-10-07 (before exact names, code in `find_symbol`,
the smaller tool list and the warm-up call) gave nexus 6% more cost than Grep and Read.

**Django, a public repository** (3,000 files; 2026-10-27, commit `3fa8810`):

| | Grep and Read | Nexus-MCP |
|---|---:|---:|
| Turns (sum of the median of each question) | 93 | 78 (-16%) |
| Output tokens | 32,291 | 27,513 (-15%) |
| Cost | $0.894 | $0.973 (**+8.9%**) |
| Correct answers | 34 of 36 | 35 of 36 |

On django nexus uses fewer turns but costs more. It is cheaper on the questions where Grep and
Read need 18 to 24 turns (`SQLCompiler.as_sql` -21%, `URLResolver.resolve` -10%) and dearer on
short lookups that take 2 or 3 turns. Django is a repository that the model knows from
training, so part of this measures the model. One ground truth was corrected after the run (a
question asked for a value, and the scorer also required a file name); details in
`docs/EVALS.md`.

**Why: the cost split of the traces.** The parts below add up to the reported cost of each run.

| Change, nexus against Grep and Read | django | jobscout |
|---|---:|---:|
| Cache read (the prompt, on each turn) | +$0.011 | -$0.002 |
| Cache write (tool results and the first message) | **+$0.111** | -$0.078 |
| Output | -$0.042 | -$0.033 |
| **Total** | **+$0.079** | **-$0.112** |

- The 3,600 extra prompt tokens on each turn cost little: a cache read is $0.20 per million.
- Tool results are written to the cache at $4 per million, 20 times the read price. So the size
  of a result matters more than the size of the prompt.
- On jobscout the agent does not know the code and reads whole files (10,000 characters for each
  Read). A `find_symbol` result of 9,700 characters replaces a Read and saves a turn.
- On django the agent knows the code and reads short line ranges (3,400 characters for each
  Read). A `find_symbol` result of 9,200 characters is larger than the read that it replaces. A
  class lookup sends up to 6,000 characters of the class body: `CharField` cost $0.023 with nexus
  and $0.013 with Grep.

This led to the outline for a class in `find_symbol` (methods and lines, not the body). With it,
nexus on django went from $0.973 to $0.863 (see "Latest" above). Details and the other
candidates are in `docs/EVALS.md`.

The saving is large on a project that the model does not know and small on one that it knows.
Run `benchmarks/` on your own project before you rely on it.

---

## The 8 Tools

**New in v2.2.0 (no breaking change):** `nexus-mcp-ci eval` measures Nexus-MCP on your own
code ([Evaluate](https://github.com/jaggernaut007/Nexus-MCP/blob/main/docs/EVALUATE.md)), and
`nexus-mcp-ci data` shows and clears every stored file ([Privacy](https://github.com/jaggernaut007/Nexus-MCP/blob/main/docs/PRIVACY.md)).
The MCP tools do not change.

**v2.1.0 breaking change:** `explain` and `health` are removed from the MCP tool list, and
several rarely used parameters are removed from the schemas (see [CHANGELOG](https://github.com/jaggernaut007/Nexus-MCP/blob/main/CHANGELOG.md)).
`find_symbol` now returns the code with callers and callees.

**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](https://github.com/jaggernaut007/Nexus-MCP/blob/main/CHANGELOG.md) for the old→new mapping and
[ADR-017](https://github.com/jaggernaut007/Nexus-MCP/blob/main/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. |
| `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)` | Primary code discovery. A query can hold exact names (`parse_config RetryPolicy`): their definitions come first. The top 3 results carry the whole symbol; the others show the signature. Falls back to live grep if results are sparse. Returns a `warning` if the index looked stale (a background reindex starts). |

### Graph Analysis

| Tool | Use When |
|------|----------|
| `find_symbol(symbol_name, exact)` | Look up one or more symbols by name (several names in one call): the code of each, with its callers and callees by name. `exact=False` for fuzzy matching. |
| `graph(symbol_name, direction, transitive)` | `direction="callers"` (who calls this, was `find_callers`) or `"callees"` (what this calls, was `find_callees`). **`transitive=True`** (was `impact()`) gives the transitive change blast radius before a refactor (a lower bound: static edges only). It requires `direction="callers"`; `direction="callees"` with `transitive=True` returns an error. Call edges are static and name-based, so use `search` for call sites the graph cannot see (see [Known Limitations](#known-limitations)). |
| `analyze(path)` | Code quality: cyclomatic complexity, cognitive complexity, code smells, dependency metrics. |

### Memory

| Tool | Use When |
|------|----------|
| `memory(action, content, query, tags, memory_id)` | `action="store"` to persist a decision or note across sessions; `"search"` for semantic retrieval; `"delete"` to remove by ID or tag. With `NEXUS_PERMISSION_LEVEL=read` only `search` is allowed. |

---

## Install

### From PyPI (recommended)

```bash
pip install nexus-mcp-ci

# GPU (CUDA) support — adds ONNX CUDA execution provider
pip install nexus-mcp-ci[gpu]

```

### 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 deprecated `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 -- nexus-mcp-ci

# GPU embeddings
claude mcp add nexus-mcp -e NEXUS_EMBEDDING_DEVICE=cuda -- nexus-mcp-ci

# Virtualenv install — pass the full binary path
claude mcp add nexus-mcp -- /path/to/.venv/bin/nexus-mcp-ci
```

### Claude Desktop

`~/Library/Application Support/Claude/claude_desktop_config.json` (macOS):

```json
{
  "mcpServers": {
    "nexus-mcp": {
      "command": "nexus-mcp-ci",
      "args": [],
      "env": {
        "NEXUS_EMBEDDING_DEVICE": "cpu"
      }
    }
  }
}
```

### Cursor / Windsurf / Cline / Any MCP Client

```json
{
  "nexus-mcp": {
    "command": "nexus-mcp-ci",
    "transport": "stdio"
  }
}
```

---

## Agent Integration Patterns

### CLAUDE.md boilerplate (drop into project root)

```markdown
## Code Intelligence — nexus-mcp

1. **Session start**: no setup call. Run `index` only if a tool says "No codebase indexed"
2. **To find code you cannot name**: `search`, before reading files one by one
3. **To see symbols you can name**: `find_symbol`, with all the names in one call
4. **Before refactoring**: `graph` with `transitive=true` to assess blast radius. The call graph is static (no dynamic dispatch), so also use `search` to find call sites.
5. **For project orientation**: `map` with `detail="summary"` or `detail="architecture"`
6. **When you know the exact file or string**: plain grep or read is fine
```

### Typical agent tool-call sequence

```
# Session start
status()               → "indexed: True, 8,412 chunks, 1,203 symbols, 87 MB"

# Code discovery
search("JWT token validation", 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

# Symbols you can name
find_symbol("validate_token, require_auth")
  → the code of each, file and lines
  → callers: [post_order, ...], callees: [...]

# Pre-refactor safety check
graph("validate_token", direction="callers", transitive=True)
  → total_impacted: N          # a lower bound: dynamic calls are not visible

# 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). `jina-code` is deprecated |
| `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_AUTO_RESTORE` | `true` | A new server process reattaches to the stored index, so `status` is already `indexed: true` |
| `NEXUS_WARM_START` | `true` | Attach the index and load the embedding model in the background while the server starts, so the first `search` does not wait about 7 s. The model then uses its memory (about 370 MB) from the start of each session |
| `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_EMBEDDING_BATCH_SIZE` | `32` | Chunks per embedding batch. Lower it to cut peak RAM while indexing |
| `NEXUS_INDEX_FILE_BATCH_SIZE` | `50` | Files per streaming indexing batch |
| `NEXUS_GRAPH_MAX_DEPTH` | `10` | Max depth for transitive graph queries |
| `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_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` | Needed only by the deprecated 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`, `smithery.yaml` and `glama.json` use the same default. `jina-code` still loads, with a warning, so an existing index keeps working. It is deprecated and will be removed in a future major release.

### 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 (deprecated) | `jina-code` | 768 | 8,192 | ONNX | Yes |

**After changing model, re-index.** Embeddings from different models are incompatible.

---

## Comparison

### vs. other code-intelligence MCP servers

Features only, from each project's own documentation. We make no performance claims about
other tools: run `nexus-mcp-ci eval` on your code instead.

| Feature | Nexus-MCP | Serena | claude-context |
|---|:---:|:---:|:---:|
| How it finds code | hybrid: vectors + BM25 | language servers (LSP) | hybrid: vectors + BM25 |
| Where the index lives | local LanceDB | none (live LSP) | a vector database (Milvus or Zilliz Cloud) |
| Embeddings | local model, no API key | none | an embedding provider (OpenAI by default; Ollama possible) |
| Call graph | static, 6 languages | LSP references | no |
| Project memory | yes | yes | no |
| Edits code | no (read only) | yes | no |
| Measure it on your repo | `nexus-mcp-ci eval` | — | — |

Other servers in this space include codebase-memory-mcp, code-graph-rag, code-index-mcp and
ast-grep-mcp. Check each project's docs. Corrections to this table are welcome.

### 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) | static: containment, imports, calls | ✅ SCIP | ❌ | ❌ | ❌ |
| Semantic memory (persistent) | ✅ 6 types | ❌ | ❌ | ❌ | ❌ |
| Change impact analysis | static callers only | 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                    # all tests (-m "not slow" skips the performance ones)
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
├── 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 the 8 tools and the Python API
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("<tool>")` 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 the 8 tools and the Python API 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: the 8 tools and the Python API 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 default model runs on PyTorch, not ONNX.
- **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 static and name-based.** `graph()` and the `callers`/`callees` fields of `find_symbol()` come from parsing, not runtime tracing. Edges exist for Python, JavaScript, TypeScript, Go, Java and Rust. A call resolves only when the callee is in the same file, in an imported file, or has a unique name in the index. A name shared by several definitions gets no edge, so results are a lower bound. Dynamic dispatch, monkey-patching, callbacks and reflection do not show up, so treat `graph(transitive=True)` as a lower bound on blast radius.
- **Complexity is approximate**: cyclomatic complexity counts branch points per function. Only Python counts `and`/`or`, and only Python functions get docstrings on graph nodes.
- **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/](https://github.com/jaggernaut007/Nexus-MCP/blob/main/docs/adr/):

| ADR | Decision |
|-----|----------|
| [ADR-001](https://github.com/jaggernaut007/Nexus-MCP/blob/main/docs/adr/ADR-001-single-mcp-consolidation.md) | Merge two MCP servers into one |
| [ADR-002](https://github.com/jaggernaut007/Nexus-MCP/blob/main/docs/adr/ADR-002-lancedb-over-chromadb.md) | LanceDB over ChromaDB |
| [ADR-003](https://github.com/jaggernaut007/Nexus-MCP/blob/main/docs/adr/ADR-003-onnx-runtime-over-pytorch.md) | ONNX Runtime over PyTorch for embeddings |
| [ADR-004](https://github.com/jaggernaut007/Nexus-MCP/blob/main/docs/adr/ADR-004-bge-small-default-model.md) | bge-small-en as default embedding model |
| [ADR-005](https://github.com/jaggernaut007/Nexus-MCP/blob/main/docs/adr/ADR-005-dual-parser-strategy.md) | Dual parser: tree-sitter + ast-grep |
| [ADR-006](https://github.com/jaggernaut007/Nexus-MCP/blob/main/docs/adr/ADR-006-rustworkx-graph-engine.md) | rustworkx for graph algorithms |
| [ADR-007](https://github.com/jaggernaut007/Nexus-MCP/blob/main/docs/adr/ADR-007-lancedb-schema-design.md) | 12-column PyArrow schema for LanceDB |
| [ADR-008](https://github.com/jaggernaut007/Nexus-MCP/blob/main/docs/adr/ADR-008-code-chunk-strategy.md) | Symbol-based chunking with deterministic IDs |
| [ADR-009](https://github.com/jaggernaut007/Nexus-MCP/blob/main/docs/adr/ADR-009-indexing-pipeline-architecture.md) | 8-step indexing pipeline |
| [ADR-010](https://github.com/jaggernaut007/Nexus-MCP/blob/main/docs/adr/ADR-010-graph-tools-api-design.md) | Graph tools API: serialization, ambiguity handling |
| [ADR-011](https://github.com/jaggernaut007/Nexus-MCP/blob/main/docs/adr/ADR-011-hardening-decisions.md) | Graceful shutdown, corruption recovery, JSON logging |
| [ADR-012](https://github.com/jaggernaut007/Nexus-MCP/blob/main/docs/adr/ADR-012-tool-permission-model.md) | READ/MUTATE/WRITE permission categories |
| ~~[ADR-013](https://github.com/jaggernaut007/Nexus-MCP/blob/main/docs/adr/ADR-013-pydantic-schemas.md)~~ | Pydantic v2 I/O schemas — superseded by ADR-016 (never wired in, deleted) |
| [ADR-014](https://github.com/jaggernaut007/Nexus-MCP/blob/main/docs/adr/ADR-014-rate-limiting.md) | Token-bucket rate limiting (off by default) |
| [ADR-015](https://github.com/jaggernaut007/Nexus-MCP/blob/main/docs/adr/ADR-015-auto-watch-and-staleness-detection.md) | Auto-watch + throttled staleness detection |
| [ADR-016](https://github.com/jaggernaut007/Nexus-MCP/blob/main/docs/adr/ADR-016-remove-unused-pydantic-schemas.md) | Removal of unused Pydantic schemas (supersedes ADR-013) |
| [ADR-017](https://github.com/jaggernaut007/Nexus-MCP/blob/main/docs/adr/ADR-017-tool-consolidation.md) | Tool consolidation 15→10, action-aware permission categories |
| [ADR-018](https://github.com/jaggernaut007/Nexus-MCP/blob/main/docs/adr/ADR-018-token-efficiency-benchmark.md) | Token-efficiency benchmark harness (`benchmarks/`) |

---

## Documentation

Start at the [docs index](https://github.com/jaggernaut007/Nexus-MCP/blob/main/docs/README.md).

- [Evaluate on your project](https://github.com/jaggernaut007/Nexus-MCP/blob/main/docs/EVALUATE.md) — The free offline check, the agent tier, saving and sharing results
- [Privacy and data](https://github.com/jaggernaut007/Nexus-MCP/blob/main/docs/PRIVACY.md) — What goes in and out, where data is stored, how to remove it
- [Benchmarks](https://jaggernaut007.github.io/nexus-mcp/benchmarks.html) — Every published number, with JSON and CSV
- [Installation Guide](https://github.com/jaggernaut007/Nexus-MCP/blob/main/docs/INSTALLATION.md) — Prerequisites, client-specific setup, troubleshooting
- [Architecture](https://github.com/jaggernaut007/Nexus-MCP/blob/main/docs/ARCHITECTURE.md) — Data flow, component design, memory budget analysis
- [Usage Guide](https://github.com/jaggernaut007/Nexus-MCP/blob/main/docs/USAGE_GUIDE.md) — Full tool reference with examples
- [Developer Guide](https://github.com/jaggernaut007/Nexus-MCP/blob/main/docs/DEVELOPER_GUIDE.md) — Contributing, adding tools/engines/languages
- [Research Notes](https://github.com/jaggernaut007/Nexus-MCP/blob/main/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](https://github.com/jaggernaut007/Nexus-MCP/blob/main/docs/adr/ADR-001-single-mcp-consolidation.md) for the consolidation rationale.

---

## License

[PolyForm Noncommercial License 1.0.0](https://github.com/jaggernaut007/Nexus-MCP/blob/main/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.

TDQS

A4.3/5.0

Scored across 8 tools

Disambiguation5/5

Each tool targets a distinct facet of code intelligence: status vs index (state vs build), search vs find_symbol (semantic vs name lookup), graph (call relations), analyze (quality), map (overview), memory (notes). Descriptions explicitly cross-reference each other to steer selection, leaving no real overlap.

Naming Consistency4/5

Names are consistently short, lowercase single tokens, but they mix noun-style (status, graph, map, memory) with verb-style (index, search, analyze) and one snake_case outlier (find_symbol). Mostly predictable, minor deviations only.

Tool Count5/5

Eight tools is well-scoped for a code-index server, and each earns its place with a clear, non-redundant role spanning indexing, search, navigation, analysis and memory.

Completeness5/5

Covers the full lifecycle: index status/build, semantic and name search, call-graph tracing, quality analysis, architectural overview, and persistent project memory. Literal grep is delegated to built-ins, which is a reasonable boundary, leaving no notable gaps.

Maintenance

ActivityActive
ResponsivenessSlow