code-index-mcp
Self-hosted hybrid code search MCP server with text, symbol, and semantic search layers. Runs locally, no third-party MCP servers, LSP, or SaaS.
README
code-index-mcp
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" |
Don't want to configure this yourself? Let your agent do it
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.
Requirements
- Python 3.10+
- Qdrant running locally (you already have it) at
http://localhost:6333 - The rest is pip-installed (tree-sitter grammars + fastembed are local; the embedding model downloads once and then runs offline).
Install
# from the project folder
uv venv
uv pip install -e .
# or: pip install -e .
Nothing is written into your repos
All state lives outside the indexed repositories:
- SQLite index →
~/.cache/code-index/<service-id>.sqlite3 - Qdrant collection →
code_<service-id>(derived from the repo's absolute path) - Project registry →
~/.config/code-index/projects.toml
So you never commit anything, never edit .gitignore, and never add git hooks
inside the service repos — no PR noise.
Single project
code-index index # incremental index of CWD (or --path DIR)
code-index index --full # full rebuild
code-index stats
Microservices (many repos)
Register 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-all
The registry file (~/.config/code-index/projects.toml) looks like:
[[service]]
name = "billing"
path = "C:/work/billing"
[[workspace]]
path = "C:/work"
depth = 1
Per-project ignore (no files in the repo)
Each [[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 lists
ignoreis 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 = trueadditionally reads the repo's top-level.gitignore(read-only — never written). Negation rules (!pattern) and nested per-directory.gitignorefiles are intentionally not supported, to keep the matcher tiny and predictable.- For a
[[workspace]],ignore/use_gitignoreare inherited by every auto-discovered repo under it.
Watch indexing progress
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 port
Progress 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.
Auto re-index (no git hooks)
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 only
Leave 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.
Wire it into Kilo CLI / OpenCode
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"
}
}
}
}
If
code-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 the actual 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_hybridacceptpath_globandexclude_glob(globs, comma-separated or a list) to narrow results by repo-relative path, e.g.path_glob="backend/**",exclude_glob="**/tests/**".search_textalso degrades gracefully on malformed FTS5 input (it retries the query as a literal phrase).
Keeping the index fresh
- Background daemon (recommended):
code-index-watch(see above). - On demand from the agent: it can call the
reindextool. - Manual:
code-index index/code-index index-all. - At server start: set
CODE_INDEX_REINDEX_ON_START=1for a quick background incremental of the default service when the MCP server launches.
Environment variables
| 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.
Graceful degradation
- No tree-sitter? → symbols layer off, text + semantic still work.
- Qdrant down / fastembed missing? → semantic off, text + symbols still work.
- The text (FTS5) layer always works as long as the SQLite index exists.
Degradation is visible, not silent:
index_statsreports the semantic layer's health:ok (points=N),disabled(turned off), orunavailable(API/Qdrant unreachable).search_semantic/search_hybriddistinguish 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.- Indexing counts what it couldn't store — chunks that failed to embed and
vectors that failed to upsert — and surfaces the totals in the run log, in
status.json, and in thestatus/ web dashboards (a ⚠ marker).
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。