Mcp Code Index — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Mcp Code Index (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Self-hosted hybrid code search as an MCP server for Kilo CLI / OpenCode. Everything runs locally on your machine — no third-party MCP servers, no LSP, no SaaS.
Three search layers, one server:
| Layer | Engine | Good for |
|---|---|---|
| text | SQLite FTS5 | exact strings, identifiers, config keys, errors |
| symbols | tree-sitter | "where is X defined", file outline |
| semantic | fastembed (local) → Qdrant | fuzzy "where do we do Y" |
You don't need to learn the env vars, registry file, or Qdrant. Just tell your coding agent (Kilo CLI / OpenCode) to set it up from the runbook:
"Set up code-index for me, following the instructions at https://github.com/Krat12/mcp-code-index/blob/main/docs/AGENT_SETUP.md"
The agent reads docs/AGENT_SETUP.md, checks your environment, asks you only the few things it can't decide (which repos, your embeddings API key, what to ignore), wires everything up, builds the index, and tells you where everything lives — without writing anything into your repos. The rest of this README is the manual reference for those who prefer to do it by hand.
http://localhost:6333embedding model downloads once and then runs offline).
# from the project folder
uv venv
uv pip install -e .
# or: pip install -e .All state lives outside the indexed repositories:
~/.cache/code-index/<service-id>.sqlite3code_<service-id> (derived from the repo's absolute path)~/.config/code-index/projects.tomlSo you never commit anything, never edit .gitignore, and never add git hooks inside the service repos — no PR noise.
code-index index # incremental index of CWD (or --path DIR)
code-index index --full # full rebuild
code-index statsRegister services in the external registry (no files in the repos):
# Register each service explicitly...
code-index add C:\work\billing --name billing
code-index add C:\work\payments --name payments
# ...or point at a parent folder and auto-discover every git repo inside:
code-index add-workspace C:\work --depth 1
code-index list # show resolved services
code-index index-all # (re)index every service (per-service index)
code-index stats-allThe registry file (~/.config/code-index/projects.toml) looks like:
[[service]]
name = "billing"
path = "C:/work/billing"
[[workspace]]
path = "C:/work"
depth = 1Each [[service]] (and [[workspace]]) can declare what to skip — all in the external registry, so nothing is written into the service repo:
[[service]]
name = "billing"
path = "C:/work/billing"
ignore = ["**/generated/**", "*.pb.go", "docs/legacy/**"] # extra ignore globs
use_gitignore = true # ALSO skip whatever the repo-root .gitignore listsignore is a list of glob patterns matched against repo-relative POSIX paths.Supported glob/.gitignore-like syntax: * (no /), ** (any depth), ?, a trailing / (directories only), and a leading/embedded / (anchored to the repo root). These are layered on top of the built-in ignores (build dirs, lock files, minified bundles, etc.).
use_gitignore = true additionally reads the repo's top-level .gitignore(read-only — never written). Negation rules (!pattern) and nested per-directory .gitignore files are intentionally not supported, to keep the matcher tiny and predictable.
[[workspace]], ignore/use_gitignore are inherited by everyauto-discovered repo under it.
code-index index # shows a live rich progress bar (TTY)
code-index index --plain # plain stderr logging instead
code-index status # one-shot table: phase / progress / files / symbols
code-index status --watch # live dashboard, refreshes ~1/s (Ctrl+C to stop)
code-index web # tiny local web dashboard at http://127.0.0.1:8765
code-index web --port 9000 # pick another portProgress is shared across processes via tiny JSON files in ~/.cache/code-index/status/<id>.json (atomic writes). So you can run code-index status --watch (or open the web page) in one terminal and watch the background code-index-watch daemon — or an index-all running elsewhere — make progress in real time. The web UI is opt-in (started only by code-index web), single-threaded, and does work only when the browser polls — deliberately light for a low-power machine.
A background daemon keeps every registered service fresh using filesystem events (watchdog) plus a periodic safety sweep — all outside the repos:
code-index-watch # FS events + sweep every 600s
code-index-watch --interval 300 # sweep every 5 min
code-index-watch --no-periodic # FS events onlyLeave it running (e.g. as a startup task / Windows service). It does an initial incremental index of each service, then re-indexes only what changes.
Add a local MCP server to your config (~/.config/kilo/opencode.json for Kilo CLI, or ~/.config/opencode/opencode.json for OpenCode; a per-project opencode.json works too):
One MCP server can serve all your microservices — the agent picks the service per call (or uses the default CWD service). Put this in the global config (~/.config/kilo/opencode.json for Kilo, ~/.config/opencode/opencode.json for OpenCode):
{
"$schema": "https://app.kilo.ai/config.json",
"mcp": {
"code-index": {
"type": "local",
"command": ["code-index-mcp"],
"enabled": true,
"environment": {
"CODE_INDEX_ROOT": "${cwd}",
"QDRANT_URL": "http://localhost:6333",
"CODE_INDEX_EMBED_MODEL": "BAAI/bge-small-en-v1.5",
"CODE_INDEX_SEMANTIC": "1"
}
}
}
}Ifcode-index-mcpisn't on PATH, use the absolute path to the venv script, e.g.["C:/Users/you/PycharmProjects/code-index-mcp/.venv/Scripts/code-index-mcp.exe"], or["python", "-m", "code_index.server"]with the venv's python.
Restart the CLI. The agent now sees tools: search_text, search_symbol, file_symbols, read_span, search_semantic, search_hybrid, list_services, reindex, index_stats. Every search tool takes an optional service (name or id from list_services).
read_span(path, start_line, end_line, context=0, service=...) returns theactual source at a location — the natural follow-up to a search hit, so the agent can read code without a separate file tool (path is confined to the repo; falls back to the index if the file changed/vanished on disk).
search_text/search_symbol/search_semantic/search_hybrid acceptpath_glob and exclude_glob (globs, comma-separated or a list) to narrow results by repo-relative path, e.g. path_glob="backend/**", exclude_glob="**/tests/**". search_text also degrades gracefully on malformed FTS5 input (it retries the query as a literal phrase).
code-index-watch (see above).reindex tool.code-index index / code-index index-all.CODE_INDEX_REINDEX_ON_START=1 for a quick backgroundincremental of the default service when the MCP server launches.
| Var | Default | Meaning |
|---|---|---|
CODE_INDEX_ROOT | . | default project root (single-project / fallback) |
CODE_INDEX_CONFIG_HOME | ~/.config/code-index | where projects.toml lives |
CODE_INDEX_CACHE_HOME | ~/.cache/code-index | where SQLite indexes live |
QDRANT_URL | http://localhost:6333 | Qdrant endpoint |
QDRANT_API_KEY | – | optional Qdrant key |
CODE_INDEX_EMBED_MODEL | BAAI/bge-small-en-v1.5 | fastembed model |
CODE_INDEX_SEMANTIC | 1 | set 0 to disable semantic layer |
CODE_INDEX_REINDEX_ON_START | 0 | 1 = background reindex on server start |
Live indexing status is written to~/.cache/code-index/status/<id>.json(underCODE_INDEX_CACHE_HOME). Inspect it withcode-index status/code-index status --watch, or thecode-index webdashboard.
Degradation is visible, not silent:
index_stats reports the semantic layer's health: ok (points=N),disabled (turned off), or unavailable (API/Qdrant unreachable).
search_semantic / search_hybrid distinguish **disabled vs unavailable vs"no matches"**, so the agent knows to fall back to text/symbols instead of treating a down layer as an empty result.
vectors that failed to upsert — and surfaces the totals in the run log, in status.json, and in the status / web dashboards (a ⚠ marker).
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.