CODE_INDEX_EMBEDDER — Embedder backend. Only 'jina' (local jina-embeddings-v2-base-code via sentence-transformers) is supported; the variable exists for future expansion.
CODE_INDEX_EMBED_MODEL — HuggingFace model id. Defaults to 'jinaai/jina-embeddings-v2-base-code'. Override only if you know the model is dim-compatible.
CODE_INDEX_EMBED_DIM — Embedding dimension. Defaults to 768 (matches jina-embeddings-v2-base-code). Must match the model's output dim.
CODE_INDEX_DB — Path to the SQLite index database. Defaults to .claude/index.db relative to the working directory.
CODE_INDEX_EMBED_BATCH — Encode batch size (default 32). Lower (e.g. 8 or 4) to reduce peak memory if the GPU OOMs on large files.
CODE_INDEX_EMBED_DEVICE — Torch device override: 'cpu', 'mps', or 'cuda'. Unset = auto-detect. Set 'cpu' to avoid Apple-Silicon MPS out-of-memory errors.
A local, SQLite-backed code index for Claude Code, exposed over MCP. It
replaces blind Read / Grep / Glob exploration with targeted retrieval —
"where is parseAuthToken defined", "what calls Indexer.reindex_all", "find
the rate-limiting code" — answered in milliseconds against an offline index.
No API keys. No external services. The embedder runs locally on your machine.
SQLite-backed hybrid (vector + FTS) code search index for Claude Code over MCP.
Claude calls the init MCP tool, which writes .claude/index.db. From then on,
ask things like "where is parseAuthToken defined?" or "what calls
Indexer.reindex_all?" — Claude routes them through symbol_lookup /
callers / code_search instead of grepping.
What --refresh does — fetches the latest PyPI release on every Claude
Code launch. Convenient during preview; drop it once you want to pin a
version (saves ~1s of startup).
Project-only install — drop -s user to register the server in the
current project's .claude/settings.json instead of the global ~/.claude.json.
First-run model download — the first init pulls
jina-embeddings-v2-base-code (~600 MB) into ~/.cache/huggingface and
caches it forever. Subsequent runs are fully offline. If your network
blocks Hugging Face, pre-warm the cache from a machine that has access.
Already installed without --refresh? Run claude mcp remove code-index
first, then re-run the command above.
Alternative: permanent install (no uvx)
pip install mcp-code-index
claude mcp add -s user code-index -- code-index-mcp
Optional: keep the index live as you edit
Without a hook, the index drifts when files change outside the agent (mv,
git checkout, IDE saves) until you call init again. With one, every
Edit / Write / MultiEdit Claude performs triggers an incremental reindex
of the touched file.
Easiest path: ask Claude. On first use in a new project, ask "set up the
code-index" — Claude calls setup_check → install_hook → init. The hook
command is derived from how the MCP server was launched (uvx-aware), so it
uses the same Python toolchain. Hook output goes to .claude/code-index-hook.log
so failures are debuggable.
Manual install — add this block to the project's .claude/settings.json
under hooks.PostToolUse (the version you want depends on how you launch the
server — install_hook derives the right one for you):
The server speaks standard MCP over stdio, so any client that supports MCP
servers works (Cursor, Continue, Cody, Zed, etc.). Configure the client to
launch uvx --refresh --from mcp-code-index code-index-mcp (or
code-index-mcp after pip install mcp-code-index). Once connected, call the
init tool from inside the client to bootstrap the index. Drop --refresh
when you want to pin to a stable version instead of always pulling latest.
From source (development)
git clone https://github.com/achreftlili/code-index
cd code-index
pip install -e .
code-index init # CLI alternative to the `init` MCP tool
code-index-mcp # starts the MCP server on stdio (for manual wiring)
Configuration
All settings are optional — the defaults work out of the box. Override them via
environment variables. Inside Claude Code, set them in the env block of your
code-index server entry in ~/.claude.json (then reconnect the MCP server).
Common knobs (most users only ever touch these):
Var
Default
When to set it
CODE_INDEX_EMBED_DEVICE
auto
Force the torch device: cpu, mps, or cuda. Set cpu on Apple Silicon if init fails with MPS out-of-memory.
CODE_INDEX_EMBED_BATCH
32
Encode batch size. Lower (e.g. 8 or 4) to cut peak GPU memory while staying on mps/cuda.
CODE_INDEX_DB
.claude/index.db
Override the SQLite index path (e.g. to share an index across sibling worktrees).
Advanced (rarely needed):
Var
Default
Notes
CODE_INDEX_EMBEDDER
jina
Only jina (local sentence-transformers) is supported today; the variable exists for future expansion.
CODE_INDEX_EMBED_MODEL
jinaai/jina-embeddings-v2-base-code
HuggingFace model id. Only override if you know the model is dim-compatible (768d).
CODE_INDEX_EMBED_DIM
768
Must match the embedding model's output dimension.
Troubleshooting
init fails with MPS backend out of memory on Apple Silicon. A large
file produced a chunk batch bigger than your GPU's free VRAM. Quickest fix —
re-run on CPU (slower but bulletproof):
"env": {
"CODE_INDEX_EMBED_DEVICE": "cpu"
}
To stay on the GPU, shrink the batch instead: "CODE_INDEX_EMBED_BATCH": "8".
Reconnect the MCP server (/mcp → reconnect, or restart Claude Code) so the
new env takes effect. init is incremental — already-embedded files are
skipped on the retry.
init fails with a Hugging Face network error on first run. Your network
is blocking model downloads. Pre-warm the cache on a machine that has access:
huggingface-cli download jinaai/jina-embeddings-v2-base-code
# then copy ~/.cache/huggingface/ to the offline machine
sqlite3.OperationalError: not authorized or sqlite-vec fails to load.
Your Python build doesn't have loadable SQLite extensions. See
Requirements — install via python.org or a pyenv build with
PYTHON_CONFIGURE_OPTS=--enable-loadable-sqlite-extensions.
code_search / symbol_lookup returns stale paths after a refactor or
branch checkout. The auto-reindex hook only fires on Claude's Edit /
Write / MultiEdit. After bulk file moves outside the agent (mv,
git checkout, IDE rename), re-run init (it's incremental). Or wire up the
hook so the index keeps up with
agent edits automatically.
Layout
src/code_index/
db.py SQLite schema, connection, sqlite-vec loading
parser.py Tree-sitter wrapper, symbol + edge extraction
imports.py Per-language import target → file path resolution
chunker.py Per-symbol chunks, identifier expansion
embedder.py Local Jina (sentence-transformers) backend
indexer.py Pipeline: walk → parse → chunk → embed → write
reindexer.py Per-root engine cache; one entry point for "reindex one file"
retriever.py Hybrid search (vector + FTS5) with RRF
watcher.py File watcher (watchdog)
admin.py setup_check / install_hook / init logic (pure, no MCP state)
mcp_server.py MCP wiring, shared helpers, schema fragments
tool_registry.py Shared `@_tool` decorator + `_TOOLS` registry
tools/ Per-domain MCP handlers (graph, paths, refactor, …)
hook.py `code-index-hook` console script — the PostToolUse entry point
cli.py init / reindex / watch / stats