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://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("<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 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.

TDQS

A4.4/5.0

Scored across 10 tools

Disambiguation5/5

Each tool has a clearly distinct responsibility: index/status/health manage the index lifecycle, map/search/find_symbol/graph/explain serve different code-comprehension needs, analyze covers quality, and memory handles persistent context. Even the potentially overlapping status/health pair is explicitly differentiated by freshness vs liveness.

Naming Consistency4/5

Tool names are consistently lowercase and concise, mostly single imperative verbs. The pattern is slightly uneven because find_symbol uses verb_noun and memory is a noun rather than an action, but there are no mixed casing conventions or vague generic names.

Tool Count5/5

Ten tools is well within the ideal scope for a code-intelligence server. Each tool covers a meaningful capability without redundancy, and the count feels neither thin nor bloated.

Completeness4/5

The set covers the full workflow of indexing, monitoring, searching, navigating, analyzing, explaining, and retaining project context. Minor gaps exist—there is no explicit unindex/forget tool or a way to list all indexed codebases—but core workflows have no dead ends.

Maintenance

ActivityMaintained
ResponsivenessUnresponsive