Skip to main content
Glama
wenn-id

Local-first MCP server that indexes a codebase into SQLite once and then lets AI agents search it instantly — offline, private, and with zero dependencies via literal substring search, incremental indexing, per-language stats, and root listing.

by wenn-id
README.md
# locidx

**Local-first code indexer and search.** Index a codebase once, then search it
instantly — offline, private, and with **zero dependencies**.

`locidx` builds a SQLite index of your project, respects `.gitignore`-style
rules, updates incrementally, and gives you three ways to query it: a CLI, an
interactive TUI, and a [Model Context Protocol](https://modelcontextprotocol.io)
server so AI agents can search your code too.

---

## Why locidx?

* **Zero dependencies.** Python 3.9+ standard library only. No `pip install`
  battle royale — the indexer, CLI, TUI, and MCP server all use `sqlite3`,
  `curses`, and `json`.
* **Private and local-first.** Everything lives in SQLite files on your
  machine. No network, no telemetry, no "send us your code" to get search.
* **Fast and incremental.** Only files whose mtime changed are re-read.
  Searching reads the database, never the filesystem.
* **gitignore-aware.** Real `.gitignore` semantics: negation, anchoring,
  `**`, directory-only patterns — plus optional `--ignore` globs and
  per-directory `.locidxignore` files.
* **One tool, three interfaces.** Terminal, TUI, and MCP — the same index
  serves all three.

## Install

```bash
# From source (no install needed to run — just clone and use ./locidx)
git clone https://github.com/wenn-id/locidx
cd locidx

# Or install the package (creates the `locidx` command)
python -m pip install .
```

You can also run it straight from the checkout without installing:

```bash
python -m locidx.cli --help
```

## Quick start

```bash
# 1. Index a project (creates .locidx.sqlite inside it)
locidx index ~/src/myproject --stats

# 2. Search it
locidx find "def parse_" ~/src/myproject

# 3. Language stats
locidx stats --json ~/src/myproject
```

### Global database

By default `locidx index` creates a **per-project** database
(`.locidx.sqlite` in the indexed folder). That keeps everything self-contained
and shareable.

For a single machine-wide index across many projects, add `--global`:

```bash
locidx index ~/src/project-a --global
locidx index ~/src/project-b --global
locidx search "TODO" --global          # searches every indexed root
locidx roots                           # list all indexed roots
locidx clear                           # wipe the global index
```

## CLI reference

| Command | Description |
| --- | --- |
| `locidx index [PATH] [--global] [--stats] [--no-ignore] [--ignore GLOB] [--size-limit MB]` | Build or update the index |
| `locidx find PATTERN [PATH]` | Search inside a project's own index |
| `locidx search PATTERN [PATH] [--global] [--max N] [--case]` | Search the global index |
| `locidx stats [PATH] [--json]` | Per-language file/line stats |
| `locidx roots` | List indexed roots |
| `locidx clear` | Wipe the global index |

## Interactive TUI

```bash
locidx tui [PATH]
```

* `/` — enter a search query
* `j` / `k` — move through results
* `o` — open the selected file in `$VISUAL`/`$EDITOR`
* `g` / `G` — jump to top / bottom
* `q` — quit

> The TUI is a thin wrapper over the same search core, so results in the
> terminal and the TUI are always identical.

## MCP server

`locidx` speaks the Model Context Protocol over stdio, so any MCP client
(Claude, code agents, custom tooling) can search your indexed code with
structured results.

```bash
# Run the server directly
locidx mcp
```

Configure it in your MCP client, e.g. Claude Desktop:

```json
{
  "mcpServers": {
    "locidx": {
      "command": "locidx",
      "args": ["mcp"]
    }
  }
}
```

### MCP tools

| Tool | Description |
| --- | --- |
| `search(query, root?, max_results?)` | Literal substring search; returns matching lines with paths and line numbers |
| `index(root, extra_ignores?, no_ignore?)` | Incrementally index a directory |
| `stats(root?)` | Per-language file/line statistics |
| `roots()` | List every indexed root |

The server is a minimal JSON-RPC 2.0 implementation over stdio with zero
dependencies — no `mcp` SDK required.

## How it works

```
                    ┌─────────────────────────────┐
  .gitignore ──►    │                             │
                    │   locidx index PATH         │
  source tree ──►   │   ┌──────────────────┐      │
                    │   │  incremental walk │─────┼──►  .locidx.sqlite
                    │   └──────────────────┘      │        (SQLite)
                    └─────────────────────────────┘
                             │
               ┌─────────────┼──────────────┐
               ▼             ▼              ▼
           locidx find   locidx stats    locidx mcp
           (CLI)         (CLI/TUI)       (MCP server)
```

1. `locidx index` walks the tree, applies ignore rules, and stores each text
   file's path, size, mtime, a SHA-256 content hash, and the text itself in
   SQLite.
2. Unchanged files (same size + mtime) are skipped on subsequent runs.
3. Queries (`find`, `stats`, TUI, MCP) read only from SQLite — the
   filesystem is never touched again.

## Ignore rules

`locidx` honors `.gitignore` and `.locidxignore` files at any depth, plus
`--ignore` globs:

```
# .gitignore
*.log                # everywhere
build/               # directory only
/src/generated/      # anchored
!keep.log            # re-include
node_modules/        # handled out of the box
```

`--no-ignore` disables all ignore handling.

## Project layout

```
locidx/
├── locidx/
│   ├── cli.py       # argparse CLI
│   ├── tui.py       # curses TUI
│   ├── indexer.py   # incremental walk + content hashing
│   ├── search.py    # literal substring search
│   ├── stats.py     # language detection + aggregation
│   ├── ignore.py    # gitignore-style matching
│   ├── db.py        # tiny SQLite wrapper (schema + helpers)
│   ├── mcp.py       # MCP entrypoint
│   ├── server.py    # JSON-RPC 2.0 stdio server
│   └── tools.py     # agent-friendly tool wrappers
└── tests/           # stdlib unittest suite (runs in CI without install)
```

## Development

```bash
# Run the test suite (stdlib only)
python -m unittest discover -s tests -v

# Or with pytest, if you have it
python -m pytest -q
```

CI runs the same unittest command across Python 3.9–3.12.

## License

MIT — do whatever you want with it, but please star it if it saves you time.

TDQS

A4/5.0

Scored across 4 tools

Disambiguation5/5

Each tool has a clear, distinct purpose: search for content, index for building, stats for metrics, and roots for listing indexed directories. No overlap or ambiguity exists.

Naming Consistency5/5

All tool names are single, lowercase words (search, index, stats, roots) with a consistent style. They are short, memorable, and follow a predictable pattern.

Tool Count5/5

The server has exactly 4 tools, which is well within the ideal 3–15 range. Each tool is essential and earns its place for the server's indexing-and-search purpose.

Completeness5/5

The tool surface covers the full lifecycle: indexing a directory, searching it, getting statistics, and listing all indexed roots. There are no obvious dead ends or missing core operations.

Maintenance

ActivityMaintained
ResponsivenessUnresponsive