A high-performance MCP (Model Context Protocol) server for semantic code search, written in Rust.
- Hybrid Search: Combines BM25 (keyword-based) + Vector embeddings (semantic) with RRF reranking
- AST-Based Chunking: Uses tree-sitter to intelligently split code into semantic units (functions, classes, methods)
- Character-based fallback: For files where AST parsing fails or isn't available
- Comprehensive language support: 60+ file extensions supported out of the box
- Smart File Filtering:
- Automatic .gitignore support (respects .gitignore, .ignore, .git/info/exclude)
- Custom file extensions support for project-specific file types
- No configuration needed - works out of the box
- Embedded Storage: Zero external dependencies - all data stored locally
- USearch for vector similarity search
- Tantivy for BM25 full-text search
- Sled for metadata storage
- Multiple Embedding Providers:
- OpenAI (text-embedding-3-small, text-embedding-3-large)
- LM Studio (Recommended) - OpenAI-compatible local embeddings with better stability
- Ollama (local embeddings) - Note: Unstable on macOS M1 with some models
- MCP Compatible: Works with Claude Desktop, Cursor, and other MCP clients
- Multi-Language Support:
- Programming Languages (AST): Rust, Python, JavaScript/TypeScript, Java, C/C++, Go, C#, Swift, Kotlin, Ruby, Elixir, Objective-C, PHP, Scala
- Config/Markup (AST): JSON, YAML, XML, HTML, CSS, SCSS, TOML, Markdown
- iOS/macOS: .xib, .storyboard, .plist (via XML parser), .xcconfig (via TOML parser)
- Android/Java: .xml (layouts, manifests), .gradle, .properties
- Build Systems: .cmake, .sbt, .make, Makefile, CMakeLists.txt
- Shell Scripts: .sh, .bash, .zsh, .fish
- Character-based fallback: .ini, .txt, .rst, and any extension added via
custom_extensions
See ARCHITECTURE.md for detailed architecture documentation.
Key Design Decisions:
- Hybrid Search over Pure Semantic: Combines keyword and semantic search for better results
- Embedded over Client-Server: Everything runs locally, no vector DB server needed
- AST-First with Fallback: Semantic chunking when possible, character-based when needed
- Rust for Performance: Efficient memory usage and fast processing
- Rust 1.70+ (edition 2021)
- OpenAI API key (or Ollama running locally)
git clone https://github.com/faxioman/code-sage.git
cd code-sage
cargo build --releaseThe binary will be in target/release/code-sage
Add to your MCP client configuration (e.g., Claude Desktop):
{
"mcpServers": {
"code-sage": {
"command": "/path/to/code-sage",
"env": {
"EMBEDDING_PROVIDER": "openai",
"OPENAI_API_KEY": "lm-studio",
"EMBEDDING_BASE_URL": "http://localhost:1234/v1",
"EMBEDDING_MODEL": "nomic-embed-text",
"DATA_DIR": "./data"
}
}
}
}{
"mcpServers": {
"code-sage": {
"command": "/path/to/code-sage",
"env": {
"EMBEDDING_PROVIDER": "openai",
"OPENAI_API_KEY": "sk-your-key-here",
"EMBEDDING_MODEL": "text-embedding-3-small",
"DATA_DIR": "./data"
}
}
}
}{
"mcpServers": {
"code-sage": {
"command": "/path/to/code-sage",
"env": {
"EMBEDDING_PROVIDER": "ollama",
"EMBEDDING_BASE_URL": "http://localhost:11434",
"EMBEDDING_MODEL": "nomic-embed-text",
"DATA_DIR": "./data"
}
}
}
}- Download LM Studio
- Search and download
nomic-embed-textmodel - Go to "Local Server" tab
- Click "Start Server" (default port: 1234)
- Use the configuration above
- Install Ollama
- Run:
ollama pull nomic-embed-text - Start Ollama service
- Use the configuration above
Optional parameters can be added to the env section:
{
"mcpServers": {
"code-sage": {
"command": "/path/to/code-sage",
"env": {
"EMBEDDING_PROVIDER": "openai",
"OPENAI_API_KEY": "sk-your-key-here",
"EMBEDDING_MODEL": "text-embedding-3-small",
"DATA_DIR": "./data",
"DEFAULT_TOP_K": "10",
"MIN_SCORE": "0.3",
"RRF_K": "100",
"CHUNK_SIZE": "2500",
"CHUNK_OVERLAP": "300",
"BATCH_SIZE": "100",
"MAX_CHUNKS": "450000"
}
}
}
}Create a searchable index of your code by analyzing functions, classes, and methods:
{
"path": "/absolute/path/to/codebase",
"force": false,
"splitter": "ast",
"custom_extensions": [".proto", ".sql"],
"ignore_patterns": ["*.test.ts", "tmp/*"]
}Parameters:
path(required): Absolute path to codebase directoryforce(optional): Force re-analysis if already analyzed (default: false)splitter(optional): Chunking strategy - "ast" or "langchain" (default: "ast")custom_extensions(optional): Additional file extensions to analyze beyond the 60+ defaults (e.g., [".proto", ".graphql"])ignore_patterns(optional): Additional patterns to ignore (complements .gitignore)
How File Selection Works:
- Extension Filtering: Only files with supported extensions are analyzed (60+ defaults)
- Gitignore Respecting: Automatically respects
.gitignore,.ignore, and.git/info/exclude - Custom Extensions: Use
custom_extensionsto add project-specific file types not in defaults - Hidden Files: Skipped by default
Supported Extensions by Default (60+ total):
- Core Languages: .rs, .py, .js, .jsx, .ts, .tsx, .java, .c, .h, .cpp, .hpp, .go, .cs, .swift, .kt, .rb, .ex, .exs, .m, .mm, .php, .scala
- JS/TS Variants: .mjs, .cjs
- Config Formats: .json, .yaml, .yml, .toml, .xml, .ini
- iOS/macOS: .xib, .storyboard, .plist, .xcconfig
- Android/Java: .gradle, .properties
- Build Systems: .cmake, .sbt, .make
- Web/Styling: .html, .htm, .css, .scss, .sass, .less
- Shell Scripts: .sh, .bash, .zsh, .fish
- .NET: .csproj, .sln, .config, .props, .targets
- Ruby: .gemspec, .rake
- Elixir: .ex, .exs
- Docs: .md, .markdown, .txt, .rst
- Notebooks: .ipynb
Example - Adding Custom Extensions:
{
"path": "/path/to/project",
"custom_extensions": [".proto", ".graphql", ".vue", ".svelte"]
}Returns: JSON with success/error message
Find code using natural language questions:
{
"path": "/absolute/path/to/codebase",
"query": "authentication logic",
"limit": 10,
"extension_filter": [".ts", ".js"]
}Returns: JSON with search results and formatted code snippets
Delete the search index for a codebase:
{
"path": "/absolute/path/to/codebase"
}Returns: JSON with confirmation message
Check if code analysis is complete, in progress, or failed:
{
"path": "/absolute/path/to/codebase"
}Returns: JSON with status (analyzed, analyzing with %, failed, or not found)
Progress Tracking (Updated 2025-11-10): The analysis progress is divided into granular phases for accurate feedback:
- 0-30%: File processing (scanning and chunking)
- 30-60%: Embedding generation (updated per batch)
- 60-85%: Vector database storage
- 85-95%: BM25 full-text indexing
- 95-100%: Metadata storage
This ensures smooth progress updates with no sudden jumps, providing better visibility into the analysis process.
Code Files
↓
AST Parsing (tree-sitter)
↓
Semantic Chunks (functions, classes)
↓
Embeddings (OpenAI/Ollama)
↓
Storage (USearch + Tantivy + Sled)
Query
↓
├─→ Vector Search (USearch) → Top 50 results
│
└─→ BM25 Search (Tantivy) → Top 50 results
↓
RRF Reranking (merge with k=100)
↓
Final Results (Top K)
# Install dependencies
cargo build
# Run tests
cargo test
# Run with logging
RUST_LOG=debug cargo run
# Format code
cargo fmt
# Lint
cargo clippyThis project is inspired by:
- claude-context - Original TypeScript implementation
- Design decisions around hybrid search and AST chunking
- MCP protocol implementation patterns
Key Differences:
- Written in Rust for performance
- Embedded storage (no Milvus/Qdrant server needed)
- Simplified architecture
- Native binary (easier deployment)
- Simpler handler responses (JSON strings)
MIT License - see LICENSE
- File Size Limit: 1MB per file
- Extension Filtering: Files must have a supported extension or be added via
custom_extensionsto be analyzed - Storage: No compression yet (working on it)
- Switching Providers: When changing embedding providers with different dimensions (e.g., from OpenAI 1536 to LM Studio 768), delete the
data/folder before re-indexing to avoid dimension mismatch errors
- Issues: GitHub Issues
- Discussions: GitHub Discussions
Built with ❤️ in Rust 🦀