{
  "markdown": "# code-index\n\n<!-- mcp-name: io.github.achreftlili/code-index -->\n\nA local, SQLite-backed code index for Claude Code, exposed over MCP. It\nreplaces blind `Read` / `Grep` / `Glob` exploration with targeted retrieval —\n\"where is `parseAuthToken` defined\", \"what calls `Indexer.reindex_all`\", \"find\nthe rate-limiting code\" — answered in milliseconds against an offline index.\n\n**No API keys. No external services. The embedder runs locally on your machine.**\n\n## How it works (30-second tour)\n\n1. **Parse** your repo with tree-sitter (Python, TypeScript/JavaScript, Go, Rust).\n2. **Chunk** code per symbol and expand identifiers (`getUserAuthToken` → `get user auth token`) so search matches both styles.\n3. **Embed** each chunk locally with `jina-embeddings-v2-base-code` (768-dim) via sentence-transformers.\n4. **Store** symbols, chunks, vectors, and call/import edges in `.claude/index.db` (SQLite + sqlite-vec + FTS5).\n5. **Serve** 14 retrieval tools + 1 admin tool over MCP (see [Tools](#tools)).\n6. **Stay fresh** via an optional `PostToolUse` hook that incrementally re-indexes touched files.\n\n## Tools\n\n### Retrieval\n\n| Tool                | Purpose                                                                                                |\n| ------------------- | ------------------------------------------------------------------------------------------------------ |\n| `code_search`       | Hybrid (vector + FTS) search for **conceptual** queries (e.g., \"auth flow\", \"where do we parse JSON\"). |\n| `symbol_lookup`     | Exact-name lookup of functions / classes / methods / types. Prefer over `code_search` for identifiers. |\n| `file_outline`      | Symbols (with signatures) in a file, in source order. Use instead of `Read` when you only need shape.  |\n| `module_outline`    | Symbols across a directory subtree in one call. Use instead of looping `file_outline`.                 |\n| `where_am_i`        | Given `path` + `line`, returns the innermost symbol and the full enclosing chain.                      |\n| `get_symbol_body`   | Full chunk for a `symbol_id` from `symbol_lookup` / `code_search` / `file_outline`.                    |\n| `get_symbol_bodies` | Batch version of `get_symbol_body` (up to 20 ids per call).                                            |\n| `callers`           | Symbols that CALL the given symbol. `depth` (1-5) expands transitively.                                |\n| `callees`           | Symbols that the given symbol CALLS. `depth` (1-5) expands transitively.                               |\n| `references`        | Non-call uses (subclasses, free identifier references). Companion to `callers` / `callees`.            |\n| `trace`             | Build a call-graph tree from an entry symbol; `flat=true` returns nodes/edges for cheap LLM scans.     |\n| `file_imports`      | Files this file imports (`direction=imports`) or that import it (`direction=imported_by`).             |\n| `recent_changes`    | Files touched in the last N git commits.                                                               |\n| `propose_rename`    | v1: same-file rename. Returns an edit list the agent applies via its own `Edit` tool; refuses on clash. |\n\n### Admin\n\n| Tool / op                  | Purpose                                                                                          |\n| -------------------------- | ------------------------------------------------------------------------------------------------ |\n| `admin op=init`            | Build or refresh the index. Incremental by default; `force=true` rebuilds from scratch.          |\n| `admin op=setup_check`     | Diagnose hook wiring + embedder + host. Round-trip-tests the hook end-to-end.                    |\n| `admin op=install_hook`    | Wire the auto-reindex `PostToolUse` hook into `.claude/settings.json`. Idempotent.               |\n| `admin op=stats`           | Read-only: file counts by language, symbol totals, embed model fingerprint, last-index time.     |\n| `admin op=verify`          | Integrity sweep: orphan rows, parse-failure files, dangling edges.                               |\n\n`embed_query_debug` is a dev-only ranking diagnostic, hidden from `list_tools`\nunless `CODE_INDEX_DEBUG=1` is set.\n\nAll tools return bounded JSON; large bodies use `get_symbol_body` rather than\ninlining whole files.\n\n## Requirements\n\n- **Python 3.10+** with **loadable SQLite extension support** (required by `sqlite-vec`).\n  - Python 3.13 has this enabled by default.\n  - On 3.10–3.12, install via the python.org installer **or** via pyenv with\n    `PYTHON_CONFIGURE_OPTS=--enable-loadable-sqlite-extensions pyenv install 3.12.x`.\n  - Homebrew Python often ships **without** the extension hook — use one of the\n    two methods above instead.\n- **`uv` / `uvx`** ([install](https://docs.astral.sh/uv/getting-started/installation/)) — recommended runner. Or `pip` if you prefer a permanent install.\n- **~600 MB free disk** for the embedding model on first init.\n\n## Quick start (Claude Code)\n\nOne command, no API keys:\n\n```bash\nclaude mcp add-json -s user code-index \"$(cat <<'JSON'\n{\n  \"type\": \"stdio\",\n  \"command\": \"uvx\",\n  \"args\": [\"--refresh\", \"--from\", \"mcp-code-index\", \"code-index-mcp\"]\n}\nJSON\n)\"\n```\n\nThen open Claude Code in any repo and ask:\n\n> _\"Build the code index for this repo.\"_\n\nClaude calls the `init` MCP tool, which writes `.claude/index.db`. From then on,\nask things like _\"where is `parseAuthToken` defined?\"_ or _\"what calls\n`Indexer.reindex_all`?\"_ — Claude routes them through `symbol_lookup` /\n`callers` / `code_search` instead of grepping.\n\n> **What `--refresh` does** — fetches the latest PyPI release on every Claude\n> Code launch. Convenient during preview; drop it once you want to pin a\n> version (saves ~1s of startup).\n>\n> **Project-only install** — drop `-s user` to register the server in the\n> current project's `.claude/settings.json` instead of the global `~/.claude.json`.\n>\n> **First-run model download** — the first `init` pulls\n> `jina-embeddings-v2-base-code` (~600 MB) into `~/.cache/huggingface` and\n> caches it forever. Subsequent runs are fully offline. If your network\n> blocks Hugging Face, pre-warm the cache from a machine that has access.\n>\n> **Already installed without `--refresh`?** Run `claude mcp remove code-index`\n> first, then re-run the command above.\n\n### Alternative: permanent install (no uvx)\n\n```bash\npip install mcp-code-index\nclaude mcp add -s user code-index -- code-index-mcp\n```\n\n### Optional: keep the index live as you edit\n\nWithout a hook, the index drifts when files change outside the agent (`mv`,\n`git checkout`, IDE saves) until you call `init` again. With one, every\n`Edit` / `Write` / `MultiEdit` Claude performs triggers an incremental reindex\nof the touched file.\n\n**Easiest path: ask Claude.** On first use in a new project, ask _\"set up the\ncode-index\"_ — Claude calls `setup_check` → `install_hook` → `init`. The hook\ncommand is derived from how the MCP server was launched (uvx-aware), so it\nuses the same Python toolchain. Hook output goes to `.claude/code-index-hook.log`\nso failures are debuggable.\n\n**Manual install** — add this block to the project's `.claude/settings.json`\nunder `hooks.PostToolUse` (the version you want depends on how you launch the\nserver — `install_hook` derives the right one for you):\n\n```json\n{\n  \"matcher\": \"Edit|Write|MultiEdit\",\n  \"hooks\": [\n    {\n      \"type\": \"command\",\n      \"command\": \"uvx --with 'sentence-transformers<5' --with 'numpy<2' --from mcp-code-index code-index-hook\"\n    }\n  ]\n}\n```\n\n### In other MCP-compatible agents\n\nThe server speaks standard MCP over stdio, so any client that supports MCP\nservers works (Cursor, Continue, Cody, Zed, etc.). Configure the client to\nlaunch `uvx --refresh --from mcp-code-index code-index-mcp` (or\n`code-index-mcp` after `pip install mcp-code-index`). Once connected, call the\n`init` tool from inside the client to bootstrap the index. Drop `--refresh`\nwhen you want to pin to a stable version instead of always pulling latest.\n\n### From source (development)\n\n```bash\ngit clone https://github.com/achreftlili/code-index\ncd code-index\npip install -e .\ncode-index init        # CLI alternative to the `init` MCP tool\ncode-index-mcp         # starts the MCP server on stdio (for manual wiring)\n```\n\n## Configuration\n\nAll settings are optional — the defaults work out of the box. Override them via\nenvironment variables. Inside Claude Code, set them in the `env` block of your\n`code-index` server entry in `~/.claude.json` (then reconnect the MCP server).\n\n**Common knobs (most users only ever touch these):**\n\n| Var | Default | When to set it |\n|---|---|---|\n| `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**. |\n| `CODE_INDEX_EMBED_BATCH` | `32`   | Encode batch size. Lower (e.g. `8` or `4`) to cut peak GPU memory while staying on `mps`/`cuda`. |\n| `CODE_INDEX_DB`          | `.claude/index.db` | Override the SQLite index path (e.g. to share an index across sibling worktrees). |\n\n**Advanced (rarely needed):**\n\n| Var | Default | Notes |\n|---|---|---|\n| `CODE_INDEX_EMBEDDER`    | `jina` | Only `jina` (local sentence-transformers) is supported today; the variable exists for future expansion. |\n| `CODE_INDEX_EMBED_MODEL` | `jinaai/jina-embeddings-v2-base-code` | HuggingFace model id. Only override if you know the model is dim-compatible (768d). |\n| `CODE_INDEX_EMBED_DIM`   | `768` | Must match the embedding model's output dimension. |\n\n## Troubleshooting\n\n**`init` fails with `MPS backend out of memory` on Apple Silicon.** A large\nfile produced a chunk batch bigger than your GPU's free VRAM. Quickest fix —\nre-run on CPU (slower but bulletproof):\n\n```json\n\"env\": {\n  \"CODE_INDEX_EMBED_DEVICE\": \"cpu\"\n}\n```\n\nTo stay on the GPU, shrink the batch instead: `\"CODE_INDEX_EMBED_BATCH\": \"8\"`.\nReconnect the MCP server (`/mcp` → reconnect, or restart Claude Code) so the\nnew env takes effect. `init` is incremental — already-embedded files are\nskipped on the retry.\n\n**`init` fails with a Hugging Face network error on first run.** Your network\nis blocking model downloads. Pre-warm the cache on a machine that has access:\n\n```bash\nhuggingface-cli download jinaai/jina-embeddings-v2-base-code\n# then copy ~/.cache/huggingface/ to the offline machine\n```\n\n**`sqlite3.OperationalError: not authorized` or `sqlite-vec` fails to load.**\nYour Python build doesn't have loadable SQLite extensions. See\n[Requirements](#requirements) — install via python.org or a pyenv build with\n`PYTHON_CONFIGURE_OPTS=--enable-loadable-sqlite-extensions`.\n\n**`code_search` / `symbol_lookup` returns stale paths after a refactor or\nbranch checkout.** The auto-reindex hook only fires on Claude's `Edit` /\n`Write` / `MultiEdit`. After bulk file moves outside the agent (`mv`,\n`git checkout`, IDE rename), re-run `init` (it's incremental). Or wire up the\n[hook](#optional-keep-the-index-live-as-you-edit) so the index keeps up with\nagent edits automatically.\n\n## Layout\n\n```\nsrc/code_index/\n  db.py           SQLite schema, connection, sqlite-vec loading\n  parser.py       Tree-sitter wrapper, symbol + edge extraction\n  imports.py      Per-language import target → file path resolution\n  chunker.py      Per-symbol chunks, identifier expansion\n  embedder.py     Local Jina (sentence-transformers) backend\n  indexer.py      Pipeline: walk → parse → chunk → embed → write\n  reindexer.py    Per-root engine cache; one entry point for \"reindex one file\"\n  retriever.py    Hybrid search (vector + FTS5) with RRF\n  watcher.py      File watcher (watchdog)\n  admin.py        setup_check / install_hook / init logic (pure, no MCP state)\n  mcp_server.py   MCP wiring, shared helpers, schema fragments\n  tool_registry.py  Shared `@_tool` decorator + `_TOOLS` registry\n  tools/          Per-domain MCP handlers (graph, paths, refactor, …)\n  hook.py         `code-index-hook` console script — the PostToolUse entry point\n  cli.py          init / reindex / watch / stats\n```\n",
  "bytes": 12027,
  "sha": "24d85ca5708ae8044da8428da466c341faa628d2c89114bc83810e7af55f706a",
  "repo_slug": "achreftlili/code-index",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_achreftlili_code_index_106ce092/readme"
}