Building a Hybrid Semantic + Keyword Search RAG System
0. Background
Certain pieces of business logic (payments, authentication, campaigns, and so on) are implemented across multiple repositories spanning frontend and backend services, which creates an inefficiency: to find the relevant code, you have to search each repository individually.
Conventional GitHub search works purely on keywords, so meaning-based queries such as "payment processing logic" or "campaign creation flow" are not possible. In addition, when using Claude AI for code analysis, the AI cannot access the internal codebase directly, so providing context requires manual work.
To solve these problems, we built a RAG system that indexes code across multiple repositories in a unified way and provides semantic search along with Claude AI integration.
1. Overview
code-retrieval is a RAG (Retrieval-Augmented Generation) system that provides hybrid semantic + keyword search over code from multiple internal GitHub repositories.
Developers can use natural-language queries to find related code scattered across several repositories all at once, and it integrates directly with Claude AI to support code understanding and analysis tasks.
It offers two access paths.
- REST API (FastAPI): for web UIs and external service integrations
- MCP server (FastMCP): for direct code search from Claude Desktop / Claude Code
2. Goals
Core goals
- Unify code across multiple repositories into a single search engine to reduce the time developers spend exploring code
- Enable semantic code search through natural-language queries such as "payment processing function" or "campaign creation API"
- Allow Claude AI to access the internal codebase directly to perform code analysis, review, and Q&A
Technical goals
- Provide meaningful search results at the function and class level through AST-based structural code chunking
- Maximize search quality with hybrid Dense (semantic) + Sparse (keyword) search plus reranking
- Update efficiently with git diff-based incremental sync, avoiding full reindexing when code changes
3. Installation & Execution
3.1 Prerequisites
- Python 3.12
- Docker or Colima (for running Qdrant; Docker Desktop not required)
- uv (Python package manager)
3.2 Installing dependencies
uv sync --extra dev3.3 Running the Qdrant vector DB
colima start
docker compose up -d qdrant3.4 Registering and indexing a repo
# Register a GitHub repo + index it
uv run python -m src.indexer --add-repo https://github.com/org/repo.git
# Register a local repo
uv run python -m src.indexer --add-repo /local/path --local
# Private repo (specify token)
uv run python -m src.indexer --add-repo <url> --branch develop --token ghp_xxx
# Specify a service alias
uv run python -m src.indexer --add-repo /local/path --local --description "service"3.5 Running the servers
# FastAPI server (REST API)
uv run python -m src.api.app
# → http://localhost:8000
# MCP server (streamable-http)
uv run python -m src.mcp
# → http://localhost:8765/mcp
# MCP server (stdio mode)
MCP_TRANSPORT=stdio uv run python -m src.mcp3.6 Sync and operations
# Sync a specific repo (incremental)
uv run python -m src.indexer --sync org/repo
# Sync all repos
uv run python -m src.indexer --sync-all
# Reindex a specific repo (full)
uv run python -m src.indexer --reindex org/repo
# Reindex all repos
uv run python -m src.indexer --reindex
# List registered repos
uv run python -m src.indexer --list3.7 Testing
uv run pytest # All tests
uv run pytest tests/test_reranker.py -v # Specific module
uv run ruff check src/ # Lint4. Project Structure
code-retrieval/
├── src/
│ ├── config.py # Pydantic Settings — 환경변수 정의
│ ├── indexer/ # 인덱싱 파이프라인
│ │ ├── cli.py # CLI 엔트리포인트
│ │ ├── chunker.py # Python AST 기반 코드 청킹
│ │ ├── treesitter_chunker.py # Tree-sitter 다국어 구조적 청킹
│ │ ├── embedder.py # FastEmbed dense + sparse 임베딩
│ │ ├── qdrant_sync.py # Qdrant upsert/delete (insert-then-delete)
│ │ ├── qdrant_init.py # Qdrant 컬렉션 자동 생성
│ │ ├── repo_manager.py # 레포 등록/클론/동기화 + registry.json
│ │ ├── git_diff.py # Git diff 변경 감지 + 필터링
│ │ ├── context_enricher.py # Ollama 기반 컨텍스트 요약 (선택)
│ │ └── poller.py # 폴링 기반 자동 동기화
│ ├── search/ # 검색 엔진
│ │ ├── hybrid.py # 하이브리드 검색 (RRF 융합)
│ │ ├── reranker.py # BGE-Reranker-v2-M3 리랭킹
│ │ ├── query.py # 쿼리 전처리 (NFC, 키워드 추출)
│ │ └── metrics.py # 검색 품질 평가 메트릭 (MRR, NDCG 등)
│ ├── mcp/ # Claude MCP 서버
│ │ ├── server.py # FastMCP 인스턴스 + 4개 도구
│ │ ├── formatter.py # 응답 포맷터
│ │ └── __main__.py # MCP 엔트리포인트
│ └── api/ # REST API
│ ├── app.py # FastAPI 앱 팩토리
│ ├── dependencies.py # 의존성 주입
│ └── routes/ # 라우트 (search, repos, health)
├── tests/
│ ├── eval/ # 검색 품질 평가
│ │ ├── golden_queries.json # 평가용 골든 쿼리셋
│ │ └── evaluate.py # 평가 스크립트
│ ├── test_reranker.py
│ ├── test_treesitter_chunker.py
│ ├── test_contextual_chunking.py
│ └── test_metrics.py
├── data/
│ └── registry.json # 레포 메타데이터 레지스트리
├── docker-compose.yml # Qdrant + App 컨테이너
├── pyproject.toml
└── .env # 환경변수 설정5. Tech Stack
| Category | Technology | Purpose |
|---|---|---|
| Language | Python 3.12 | Entire system |
| Vector DB | Qdrant (Docker / Colima) | Storing code vectors and hybrid search |
| Dense embedding | multilingual-e5-large (1024d) | Generating vectors for semantic search |
| Sparse embedding | BM25 (Qdrant/bm25) | Generating sparse vectors for keyword search |
| Embedding runtime | FastEmbed (ONNX) | Lightweight embedding inference |
| Reranker | BGE-Reranker-v2-M3 | Reranking search results (FlagEmbedding) |
| Code parser (Python) | ast module | Python AST-based structural chunking |
| Code parser (multi-language) | tree-sitter-language-pack | JS/TS/Java/Kotlin/Go/Rust chunking |
| API framework | FastAPI + uvicorn | REST API server |
| MCP framework | FastMCP | Claude Desktop / Code integration |
| Package management | uv | Dependency management and execution |
| Container | Docker Compose | Infrastructure setup |
| LLM (optional) | Ollama (gemma4:26b) | Chunk context summary enrichment |
6. System Structure and Flow
6.1 Overall architecture
┌─────────────────────────────────────────────────────────┐
│ 접근 계층 │
│ ┌──────────────┐ ┌──────────────────────────────┐ │
│ │ REST API │ │ MCP 서버 │ │
│ │ (FastAPI) │ │ (FastMCP) │ │
│ │ :8000 │ │ :8765/mcp 또는 stdio │ │
│ └──────┬───────┘ └──────────────┬────────────────┘ │
│ │ │ │
│ └───────────┬───────────────┘ │
│ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ 검색 엔진 (HybridSearcher) │ │
│ │ 쿼리전처리 → Dense+Sparse → RRF → Rerank │ │
│ └─────────────────────┬───────────────────┘ │
│ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ Qdrant 벡터 DB (:6333) │ │
│ │ Dense 벡터 (1024d) + Sparse 벡터 (BM25) │ │
│ └─────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 인덱싱 파이프라인 │
│ │
│ GitHub URL ──→ git clone ──→ 파일 탐색 │
│ │ │ │
│ │ ▼ │
│ │ AST 청킹 (Python ast / Tree-sitter) │
│ │ │ │
│ │ ▼ │
│ │ 임베딩 생성 (Dense + Sparse) │
│ │ │ │
│ │ ▼ │
│ │ Qdrant upsert (insert-then-delete) │
│ │ │
│ 동기화: git pull → git diff → 변경 파일만 재청킹 → upsert │
└─────────────────────────────────────────────────────────┘6.2 Indexing flow
- Repo registration: register a GitHub URL or local path via the CLI, and store metadata in data/registry.json
- Code collection: git clone (or reference an existing local repo)
- File filtering: determine indexing targets based on file extension (.py, .ts, .kt, etc.), excluding node_modules/, .git/, and so on
- AST chunking: split into function/class/module units — using the ast module for Python and Tree-sitter for JS/TS/Java/Kotlin/Go/Rust
- Embedding generation: generate Dense (1024d) + Sparse (BM25) vectors simultaneously with FastEmbed
- Qdrant storage: safely upsert using the Insert-then-Delete pattern
6.3 Search flow
- Query preprocessing: Unicode NFC normalization → automatic extraction of camelCase/snake_case code keywords
- Embedding generation: convert the query into Dense + Sparse vectors
- Hybrid search: perform Dense search and Sparse search separately in Qdrant (prefetch_limit=100)
- RRF fusion: merge the two result sets into one with Reciprocal Rank Fusion
- Reranking: re-rank the top candidates with the BGE-Reranker-v2-M3 cross-encoder
- Result return: return top_k code chunks ordered by relevance (including GitHub links)
6.4 Incremental sync flow
- git fetch → git reset --hard origin/branch
- detect changed/deleted files with git diff --name-only old_sha..new_sha
- deleted files: remove that file's chunks from Qdrant
- re-chunk only the changed files → re-embed → Qdrant sync_file (insert-then-delete)
7. Key Features
7.1 AST-based structural code chunking
Rather than plain text splitting, the system analyzes the syntactic structure (AST) of the code and splits it into meaningful units.
| Language | Parser | Chunking unit |
|---|---|---|
| Python | ast module | Functions, classes, module-level code |
| JS/TS | Tree-sitter | Functions, classes, interfaces, arrow functions |
| Java | Tree-sitter | Methods, classes, interfaces |
| Kotlin | Tree-sitter | Functions, classes, object declarations |
| Go | Tree-sitter | Functions, methods, type declarations |
| Rust | Tree-sitter | Functions, impl, struct, trait, enum |
| Others (YAML, JSON, etc.) | Fixed size | ~512-token units with 50-token overlap |
Handling large classes: classes exceeding 100 lines are split into individual per-method chunks plus a class-header chunk
7.2 Hybrid search (Dense + Sparse + RRF)
- Dense search: semantic similarity search with multilingual-e5-large (supports both Korean and English)
- Sparse search: exact keyword matching with BM25 (strong on code identifiers such as function and variable names)
- RRF fusion: combines the two result sets with Reciprocal Rank Fusion so they complement each other
7.3 BGE-Reranker reranking
The first-stage search results (top_k x 3 candidates) are precisely re-ranked with a cross-encoder model.
- FP16 inference: reduces memory use and improves speed
- Batch processing: batch scoring in groups of 32
- Metadata boost (optional): adds bonus points when function name, file path, or language match
7.4 MCP tools (Claude integration)
Provides four tools that can be called directly from Claude Desktop / Claude Code.
| Tool | Function | Example |
|---|---|---|
| code_search | Search code across multiple repos with a natural-language query | "payment processing function" → returns related code |
| find_function | Look up the exact definition location by function name | "payment" → function code + location |
| get_file_context | Retrieve all chunks of a specific file | "src/auth/login.ts" → all functions in the file |
| list_repos | List and show statistics for indexed repos | Per-repo status, chunk count, last sync time |
7.5 Optional features (Feature Flags) — details
The following three features can be enabled individually via environment variables, and the system works correctly even with all of them disabled.
| Feature | Environment variable | Default | When applied |
|---|---|---|---|
| Contextual Chunking | ENABLE_CONTEXTUAL_CHUNKING | false | At indexing time |
| Ollama context enrichment | ENABLE_OLLAMA_CONTEXT | false | At indexing time |
| Metadata boost | ENABLE_RERANKER_METADATA_BOOST | false | At search time |
7.5.1 Contextual Chunking (heuristic context prefix)
Purpose: automatically enrich context that a code chunk alone lacks (which file the code lives in, what it imports, and who calls it) to improve search accuracy.
Enable: ENABLE_CONTEXTUAL_CHUNKING=true
Full flow
┌─────────────────────────────────────────────────────────────┐
│ Contextual Chunking 흐름 │
│ │
│ 소스 파일 읽기 │
│ │ │
│ ▼ │
│ AST 청킹 (함수/클래스/모듈 분할) │
│ │ │
│ ▼ │
│ [ENABLE_CONTEXTUAL_CHUNKING=true?] ──── false ──→ 임베딩 │
│ │ true │
│ ▼ │
│ 각 청크에 대해 build_context_prefix() 실행 │
│ │ │
│ ├── (1) 파일 경로 추출: "File: src/auth/login.py" │
│ ├── (2) 언어 식별: "Language: python" │
│ ├── (3) 클래스 스코프: "Class: AuthService" (메서드인 경우)│
│ ├── (4) import 추출 (상위 5줄): "Imports: from x import y"│
│ ├── (5) docstring 추출 (첫 줄, 최대 120자) │
│ └── (6) 호출자 탐지 (최대 3개): "Callers: main, handler" │
│ │ │
│ ▼ │
│ 적응형 토큰 예산 계산 │
│ formula: min(80, (512 - chunk_tokens) * 0.2) │
│ → 예산 초과 시 prefix 절삭 │
│ │ │
│ ▼ │
│ chunk.contextual_prefix에 저장 │
│ │ │
│ ▼ │
│ 임베딩 시 prefix + raw_content 결합하여 벡터 생성 │
│ (embedding_content = contextual_prefix + "\n" + raw_content) │
└─────────────────────────────────────────────────────────────┘Strategy by symbol_type
| symbol_type | Prefix information generated |
|---|---|
| function / class | File + Language + Class scope + Imports (5 lines) + Docstring + Callers (3) |
| module / config | File + Language only (minimal information) |
Caller detection logic (_find_callers)
- Track def/function/const/let/var/fun/func declarations within the same file to determine the current function scope
- Extract up to 3 functions outside the chunk's own range (start_line~end_line) that reference the given symbol
- Skip generic names shorter than 3 characters, or names such as <module> or <anonymous>
Docstring extraction logic (extract_docstring)
- Python: extract the first line of the docstring of the first function/class via ast.get_docstring()
- JS/TS/Java/Kotlin: extract text from a preceding // comment or JSDoc /** ... */ block (up to 120 characters)
7.5.2 Ollama context enrichment (LLM summary)
Purpose: in addition to the heuristic prefix, a local LLM summarizes the meaning of a code chunk in a single sentence to further improve semantic search quality.
Enable: ENABLE_CONTEXTUAL_CHUNKING=true AND ENABLE_OLLAMA_CONTEXT=true (Contextual Chunking must be enabled first)
Prerequisites: a running Ollama server + the gemma4:26b model downloaded
# Ollama 설치 후
ollama pull gemma4:26b
ollama serve # http://localhost:11434Full flow
┌───────────────────────────────────────────────────────────────┐
│ Ollama 컨텍스트 보강 흐름 │
│ │
│ [Contextual Chunking 완료된 청크 목록] │
│ │ │
│ ▼ │
│ [ENABLE_OLLAMA_CONTEXT=true?] ──── false ──→ 임베딩 단계로 │
│ │ true │
│ ▼ │
│ OllamaContextEnricher 초기화 │
│ │ │
│ ▼ │
│ Ollama 서버 가용성 확인 (GET /api/tags) │
│ → 모델(gemma4:26b) 존재 여부 확인 │
│ │ │
│ ├── 사용 불가 → WARNING 로그, 휴리스틱 프리픽스만 사용 │
│ │ │
│ ▼ 사용 가능 │
│ 각 청크에 대해 (function/class 타입 + prefix 보유한 것만): │
│ │ │
│ ▼ │
│ LLM 프롬프트 생성: │
│ "Summarize this code chunk in one concise sentence (max 80 │
│ chars). Focus on WHAT it does and WHY, not implementation │
│ details." │
│ + Context: {기존 휴리스틱 prefix} │
│ + Code: {raw_content 앞 1500자} │
│ │ │
│ ▼ │
│ POST /api/generate │
│ model: gemma4:26b │
│ options: num_predict=60, temperature=0.1 │
│ timeout: 30초 │
│ │ │
│ ▼ │
│ 응답 후처리: 첫 줄만 추출, 최대 120자 절삭 │
│ │ │
│ ▼ │
│ 기존 prefix에 "Summary: {요약}" 줄 추가 │
│ chunk.contextual_prefix += "\nSummary: {summary}" │
│ │ │
│ ▼ │
│ 임베딩 단계로 (prefix + summary + raw_content 결합) │
└───────────────────────────────────────────────────────────────┘Graceful fallback design
- If the Ollama server is down or the model is missing: log a WARNING and continue operating normally with only the heuristic prefix
- Failure to summarize an individual chunk: skip just that chunk and continue processing the rest
- Timeout (over 30 seconds): skip that chunk
Related environment variables
| Variable | Default | Description |
|---|---|---|
| ENABLE_OLLAMA_CONTEXT | false | Enable Ollama enrichment |
| OLLAMA_URL | http://localhost:11434 | Ollama server address |
| OLLAMA_MODEL | gemma4:26b | Model to use |
| OLLAMA_CONTEXT_TIMEOUT | 30 | Per-chunk timeout (seconds) |
7.5.3 Metadata boost (Reranker Metadata Boost)
Purpose: at the reranking stage, add bonus points not only for semantic similarity of the code but also for direct matches between the query and metadata (function name, file name, language), pushing the exact code to the top.
Enable: ENABLE_RERANKER_METADATA_BOOST=true
Full flow
┌───────────────────────────────────────────────────────────────┐
│ 메타데이터 부스트 흐름 │
│ │
│ 검색 쿼리: "payment 결제 함수 typescript" │
│ │ │
│ ▼ │
│ 1차 하이브리드 검색 (Dense + Sparse + RRF) │
│ → 후보 30개 (top_k x 3) │
│ │ │
│ ▼ │
│ [ENABLE_RERANKER_METADATA_BOOST=true?] │
│ │ │
│ ├── false → 일반 rerank (코드 텍스트만으로 스코어링) │
│ │ │
│ ▼ true │
│ BGE-Reranker로 base_score 산출 (코드 텍스트 기반) │
│ │ │
│ ▼ │
│ 각 후보에 대해 메타데이터 부스트 계산: │
│ │ │
│ ├── symbol_name 완전 매칭 │
│ │ "payment" in query → +boost x 2.0 │
│ │ │
│ ├── symbol_name 부분 매칭 (클래스.메서드 중 메서드 부분) │
│ │ "payment.api" → 마지막 부분 매칭 │
│ │ → +boost x 1.0 │
│ │ │
│ ├── file_path stem 매칭 (파일명 >= 3자) │
│ │ "payment.api.ts" stem = "payment" in query │
│ │ → +boost x 0.5 │
│ │ │
│ └── language 매칭 │
│ "typescript" in query → +boost x 0.3 │
│ │ │
│ ▼ │
│ final_score = base_score + total_boost │
│ │ │
│ ▼ │
│ final_score 기준 내림차순 정렬 │
│ │ │
│ ▼ │
│ score_threshold 필터링 → top_k 결과 반환 │
└───────────────────────────────────────────────────────────────┘Boost weight details
| Match condition | Weight | Example |
|---|---|---|
| symbol_name exact match | boost x 2.0 | "payment" in query → +0.2 to a chunk whose symbol_name is "processPayment" |
| symbol_name partial match | boost x 1.0 | "payment" in query → +0.1 when the last part of "payment.api" matches |
| file_path stem match | boost x 0.5 | "login" in query → +0.05 when the stem "login" matches for file_path "src/auth/login.ts" |
| language match | boost x 0.3 | "typescript" in query → +0.03 to a chunk whose language is "typescript" |
(default boost factor = 0.1, adjustable via the RERANKER_METADATA_BOOST environment variable)
Related environment variables
| Variable | Default | Description |
|---|---|---|
| ENABLE_RERANKER_METADATA_BOOST | false | Enable metadata boost |
| RERANKER_METADATA_BOOST | 0.1 | Base boost factor (higher = greater metadata influence) |
| RERANKER_SCORE_THRESHOLD | 0.0 | Minimum score threshold (results below are excluded) |
7.5.4 Optional feature combination matrix
The three features can be used independently or in combination. Below is a summary of the possible combinations and their effects.
| Contextual Chunking | Ollama enrichment | Metadata boost | Effect |
|---|---|---|---|
| OFF | OFF | OFF | Default mode. Search on code text only. Fastest and lightest |
| ON | OFF | OFF | Heuristic context (file path, imports, docstring, callers) is reflected in the embedding, improving search accuracy |
| ON | ON | OFF | Heuristic + LLM summary maximizes semantic embedding quality. Increases indexing time (~2-5s per chunk) |
| OFF | OFF | ON | Bonus points for function/file name matches at search time. Effective when you know the exact name |
| ON | OFF | ON | Context enrichment at indexing + metadata boost at search. A balanced, recommended combination |
| ON | ON | ON | All features enabled. Highest quality but the longest indexing time |
Recommended configurations
- Quick start: all OFF (default) — use immediately with no environment setup
- Quality improvement: ENABLE_CONTEXTUAL_CHUNKING=true + ENABLE_RERANKER_METADATA_BOOST=true — meaningful quality gains even without Ollama
- Maximum quality: all three ON — requires an Ollama server and accepts increased indexing time
7.6 Search quality evaluation framework
A Golden Query-based automatic evaluation system is built in.
- Evaluation metrics: MRR@K, NDCG@K, Recall@K, Precision@K
- Per-category evaluation: function_search, class_search, api_search, config_search, concept_search
- Baseline comparison: track changes in search quality with delta comparisons against previous results
uv run python -m tests.eval.evaluate --mode current --output results.json
uv run python -m tests.eval.evaluate --mode current --compare baseline.json8. Results
8.1 Completed items
- Unified multi-repo indexing pipeline (AST parsing support for 7 programming languages)
- Dense + Sparse hybrid search + RRF fusion + BGE-Reranker reranking
- Dual access interface: REST API (FastAPI) + MCP server (FastMCP)
- Direct Claude Desktop / Claude Code integration (4 MCP tools)
- git diff-based incremental sync (no full reindexing required)
- Data loss prevention with the Insert-then-Delete pattern
- Golden Query-based automatic search quality evaluation framework
- One-click infrastructure setup with Docker Compose
8.2 Supported languages
- AST structural parsing (7): Python, JavaScript, TypeScript, Java, Kotlin, Go, Rust
- Fixed-size chunking: Markdown, YAML, JSON, TOML, HTML, CSS, SQL, and more
8.3 Usage scenarios
- When a developer searches for "login processing logic," the system finds related code across the entire frontend and backend
- Perform code review and analysis in Claude Code, referencing internal code directly via MCP tools
- New hires quickly identify where a particular feature is implemented
- Detect code duplication and understand cross-repo dependencies
9. Getting Started
GitHub: https://github.com/seungahhong/code-retrieval This guide is based on the current project environment — Python 3.12 (auto-provisioned by
uv), Qdrant via Colima without Docker Desktop, and gemma4:26b as the optional context-enrichment LLM.
Users work with this system in two ways.
- (A) Search directly from Claude — connect to Claude Desktop / Claude Code as an MCP server (most recommended)
- (B) Call the REST API — for web UIs, scripts, and external service integrations
9.1 Prerequisites
| Item | Version / Notes |
|---|---|
| uv | Python package manager (auto-installs Python 3.12) |
| Colima + Docker CLI | brew install colima docker docker-compose (Docker Desktop not required) |
| Ollama (optional) | Only when using context enrichment (gemma4:26b) |
9.2 5-minute quick start
# 1) Clone the repo
git clone https://github.com/seungahhong/code-retrieval.git
cd code-retrieval
# 2) Install dependencies (uv auto-provisions Python 3.12)
uv sync --extra dev
# 3) Start the Qdrant vector DB (Colima)
colima start
docker compose up -d qdrant
curl -s http://localhost:6333/healthz # response: ok
# Web dashboard: http://localhost:6333/dashboard
# 4) Environment variables
cp .env.example .env
# To index a private repo, set GITHUB_TOKEN in .env
# 5) Register and index the target repos
uv run python -m src.indexer --add-repo https://github.com/org/repo.git
uv run python -m src.indexer --add-repo /local/path --local --description "payment service"
uv run python -m src.indexer --list # Check per-repo chunk count and status9.3 Method A — Search directly from Claude (MCP)
Once indexing is done, you can connect to Claude Desktop or Claude Code as an MCP server and search and reference internal code directly during a conversation. Because Claude spawns the process itself, MCP_TRANSPORT=stdio is required.
Claude Desktop — ~/Library/Application Support/Claude/claude_desktop_config.json (macOS)
{
"mcpServers": {
"code-retrieval": {
"command": "uv",
"args": ["--directory", "/path/to/code-retrieval", "run", "python", "-m", "src.mcp"],
"env": { "QDRANT_URL": "http://localhost:6333", "MCP_TRANSPORT": "stdio" }
}
}
}Claude Code — register with a single line from the project root
claude mcp add code-retrieval \
--command "uv" \
--args "--directory" "/path/to/code-retrieval" "run" "python" "-m" "src.mcp"Change /path/to/code-retrieval to your actual clone path. When you restart Claude Desktop, the MCP tools activate automatically.
After connecting, ask Claude questions like these (internally invoking the 4 MCP tools):
| Example question | Tool invoked |
|---|---|
| "Search the payment processing logic" | code_search |
| "Where is the processPayment function defined?" | find_function |
| "Show me the entire src/auth/login.ts file" | get_file_context |
| "Show me the list of indexed repos" | list_repos |
9.4 Method B — REST API
# Run the server → http://localhost:8000 (interactive docs: /docs)
uv run python -m src.api.appMain endpoints:
| Method / Path | Description | Key request body (JSON) fields |
|---|---|---|
POST /api/search | Natural-language code search | query (required), top_k, repo, language |
GET /api/repos | List of registered repos | — |
GET /api/repos/{repo}/stats | Per-repo chunk/vector statistics | — |
POST /api/repos | Register a repo (background indexing) | url or local_path, branch, description |
POST /api/repos/{repo}/sync | Incremental sync of a repo | — |
The search response contains results[] (including code, file path, line, and github_url), total_found, query_time_ms, and repos_searched. You can review the request/response schema and try it directly at /docs (Swagger UI).
9.5 Operations — Applying code changes (incremental sync)
As new commits pile up in a repo, only the changed files are updated based on git diff, without a full reindex.
uv run python -m src.indexer --sync org/repo # Incremental sync of a specific repo
uv run python -m src.indexer --sync-all # Sync all repos
uv run python -m src.indexer --reindex org/repo # Full reindex (when needed)You can also sync via the REST API (POST /api/repos/{repo}/sync).
9.6 (Optional) Enhancing search quality
Turn on flags in .env. Everything works correctly even with them all off.
# Recommended combination (quality boost without Ollama) — already on by default in .env.example
ENABLE_RERANKER_METADATA_BOOST=true # Bonus points for function/file name matches at search time
RERANKER_SCORE_THRESHOLD=0.3 # Cut off low-quality results
# Maximum quality (LLM summary enrichment at indexing) — requires Ollama
ollama pull gemma4:26b && ollama serve
ENABLE_CONTEXTUAL_CHUNKING=true
ENABLE_OLLAMA_CONTEXT=true
OLLAMA_MODEL=gemma4:26bAfter changing a flag, you must
--reindexthe target repo for the effect to take hold. For the effect of each combination, see the optional feature combination matrix in 7.5.4.
9.7 Troubleshooting
| Symptom | Check / Fix |
|---|---|
| MCP tools not visible in Claude | Fully quit and restart Claude Desktop, verify the config JSON syntax, and set command to the absolute path from which uv |
| Qdrant connection failure | Verify the order: colima start → docker compose up -d qdrant → curl localhost:6333/healthz |
| Empty search results | Check the chunk count with --list and verify that indexing is complete (status=indexed) |
| Private repo clone failure | Set GITHUB_TOKEN in .env or specify --token ghp_xxx |
406 when accessing GET /mcp | Normal response (a request without handshake headers). Can be used as a server liveness signal |