An MCP (Model Context Protocol) server for semantic code search using vector embeddings. Index your code folders and perform semantic searches to find code by meaning, not just keywords.
- Multi-folder support: Index multiple code folders simultaneously
- Semantic search: Find code by meaning, not just keywords
- Continuous file watching: Automatically re-indexes files when they change
- Multiple embedding providers: OpenAI, Ollama, Gemini, Mistral, Bedrock, OpenRouter
- Admin UI: Web dashboard to monitor queries and indexing progress
- 35+ language support: JavaScript, TypeScript, Python, Rust, Go, and more
- Embedding cache: SQLite-based caching to avoid redundant API calls
- LSP enrichment: Type signatures and documentation via Language Server Protocol
- Hierarchical context: Parent class/module context included in embeddings
Required:
- Node.js 18+
- Qdrant vector database (local or cloud)
- An embedding provider (Ollama for local, or API keys for cloud providers)
Optional (for LSP enrichment):
# TypeScript/JavaScript LSP (improves search quality for TS/JS/TSX/JSX)
npm install -g typescript-language-server typescript
# C# LSP (improves search quality for .cs files)
dotnet tool install --global csharp-ls# Clone the repository
git clone https://github.com/amolchanov/mcp-code-search.git
cd mcp-code-search
# Install dependencies
npm install
# Download tree-sitter WASM files
npm run download-wasm
# Build the project
npm run buildThe server can run in two modes:
Used when integrating with Claude CLI, Copilot, or other MCP clients:
# Run directly (waits for JSON-RPC input on stdin)
node dist/index.js
# Or with auto-indexing of specific folders
node dist/index.js --index /path/to/project1 --index /path/to/project2In stdio mode, the server communicates via stdin/stdout using the MCP protocol. This is what Claude CLI uses when you add it as an MCP server.
Used for the web-based admin interface:
# Start with default port 3100
node dist/index.js --sse
# Or specify a custom port
node dist/index.js --sse --port 8080
# With system tray icon (Windows/macOS)
node dist/index.js --sse --tray
# With auto-indexing
node dist/index.js --sse --index /path/to/projectThen open: http://localhost:3100/admin
For production use, run the server with PM2 for automatic restart if it crashes:
# Install PM2 globally (one-time)
npm install -g pm2
# Start the server with PM2
npm run pm2:start
# View logs
npm run pm2:logs
# Check status
npm run pm2:status
# Stop the server
npm run pm2:stop
# Restart the server
npm run pm2:restartPM2 will automatically restart the server if it crashes (up to 10 times). Logs are saved to logs/output.log and logs/error.log.
| Option | Description |
|---|---|
--sse |
Run in SSE mode with HTTP server and Admin UI |
--port <number> |
HTTP port for SSE mode (default: 3100) |
--tray |
Show system tray icon (SSE mode only) |
--index <path> |
Auto-index a folder on startup (can be repeated) |
When multiple MCP clients (e.g., multiple Copilot CLI windows) connect simultaneously, the server uses a client-server architecture to prevent database corruption:
┌─────────────────────────────────────────────────┐
│ SSE Server (Daemon) │
│ • Handles ALL indexing and database writes │
│ • HTTP API at localhost:3100 │
│ • Admin UI for monitoring │
└─────────────────────────────────────────────────┘
↑ HTTP
┌───────────┼───────────┐
┌────┴────┐ ┌────┴────┐ ┌───┴─────┐
│ stdio │ │ stdio │ │ stdio │
│ client │ │ client │ │ client │
└─────────┘ └─────────┘ └─────────┘
↑ ↑ ↑
Copilot 1 Copilot 2 Copilot 3
How it works:
- Each stdio instance (spawned by Copilot/Claude CLI) is a lightweight client
- Clients proxy all tool calls to the central SSE server via HTTP
- The SSE server auto-starts if not running when a client connects
- All database writes (SQLite caches, Qdrant) go through the single server
- This prevents corruption from concurrent writes
Key benefits:
- No database locking issues with multiple CLI windows
- Single indexing process per repository
- Shared embedding cache across all clients
- Consistent state visible in Admin UI
If you don't have Docker installed:
Windows/macOS:
- Download Docker Desktop from docker.com
- Install and start Docker Desktop
- Verify installation:
docker --version
Linux:
# Ubuntu/Debian
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo usermod -aG docker $USER # Add your user to docker group
# Log out and back in for group changes to take effectQdrant is the vector database that stores code embeddings. Choose one option:
Start Qdrant container:
# Run in foreground (stops when terminal closes)
docker run -p 6333:6333 qdrant/qdrant
# OR run in background (keeps running)
docker run -d -p 6333:6333 -v qdrant_storage:/qdrant/storage --name qdrant qdrant/qdrant
# Check if running
docker ps | grep qdrant
# Stop the container
docker stop qdrant
# Restart the container
docker start qdrant
# View logs
docker logs qdrantVerify Qdrant is running:
curl http://localhost:6333/collections
# Should return: {"result":{"collections":[]}, ...}Follow the Qdrant installation guide for native installation.
Ollama provides free local embeddings without API costs. Alternatively, you can use OpenAI, Gemini, Mistral, or Bedrock.
Download and Install:
- Windows/macOS/Linux: Download from ollama.ai
- Follow the installer instructions
Pull the embedding model:
# Recommended model for code (8192 token context)
ollama pull nomic-embed-text
# Verify model is downloaded
ollama list
# Test the model
ollama run nomic-embed-text# Run Ollama in Docker
docker run -d -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama
# Pull the embedding model
docker exec ollama ollama pull nomic-embed-text
# Verify
docker exec ollama ollama listStart/Stop Ollama:
# Native installation - Ollama runs as a service automatically
# Check status
curl http://localhost:11434/api/tags
# Docker
docker start ollama
docker stop ollamaBefore starting the MCP server, check:
# Check Qdrant
curl http://localhost:6333/collections
# Check Ollama (if using)
curl http://localhost:11434/api/tagsRecommended: Use the Claude CLI command:
# Add to current project (creates .mcp.json in project root)
claude mcp add code-search -s project -- node /path/to/code-search-mcp/dist/index.js
# Or add globally for all projects (user-level config)
claude mcp add code-search -s user -- node /path/to/code-search-mcp/dist/index.jsWindows example:
claude mcp add code-search -s project -- node C:/repos/code-search-mcp/dist/index.jsVerify the server is connected:
claude mcp list
# Should show: code-search: node ... - ✓ ConnectedAlternative: Manual JSON configuration
Add to .mcp.json in your project root:
{
"mcpServers": {
"code-search": {
"type": "stdio",
"command": "node",
"args": ["/path/to/code-search-mcp/dist/index.js"],
"env": {}
}
}
}Option A: Use the MCP tool (recommended)
Once connected, ask Claude to run:
Use the get_instructions tool to get the code-search instructions, then add them to my CLAUDE.md file.
Or via CLI:
# The get_instructions tool returns markdown content for CLAUDE.md
# Format options: "claude" (default), "copilot", "generic"Option B: Copy manually
# Copy to global instructions
cp examples/CLAUDE.md ~/.claude/CLAUDE.md
# Or append to existing instructions
cat examples/CLAUDE.md >> ~/.claude/CLAUDE.mdSee examples/CLAUDE.md for the full instructions template.
Key points:
- Prefer
mcp__code-search__searchover built-inGrepandGlobfor finding code - Use natural language queries: "authentication middleware that validates JWT tokens"
- Fall back to
Greponly for exact string/regex matches
You can configure the server globally (for all repos) or per-repository:
Add this to your global Copilot CLI MCP settings file:
Location: ~/.copilot/mcp-config.json (create if it doesn't exist)
Windows: C:\Users\<username>\.copilot\mcp-config.json
{
"mcpServers": {
"code-search": {
"type": "local",
"command": "node",
"args": ["/path/to/code-search-mcp/dist/index.js"],
"tools": ["*"]
}
}
}Windows example:
{
"mcpServers": {
"code-search": {
"type": "local",
"command": "node",
"args": ["c:/repos/mcp-code-search/dist/index.js"],
"tools": ["*"]
}
}
}Add .copilot/mcp-config.json to your repository root:
# In your project repository
mkdir -p .copilot
touch .copilot/mcp-config.jsonAdd the same configuration:
{
"mcpServers": {
"code-search": {
"type": "local",
"command": "node",
"args": ["/path/to/code-search-mcp/dist/index.js"],
"tools": ["*"]
}
}
}Note:
- Repository-specific config takes precedence over global config
- Don't commit
.copilot/mcp-config.jsonto version control (add to.gitignore) - If you already have MCP servers configured, add the
code-searchentry to the existingmcpServersobject
Copy the example instructions to your Copilot instructions file:
See examples/COPILOT.md for the full instructions template.
Key points:
- Prefer semantic search over grep for finding code
- Use descriptive natural language queries
- Search before implementing to find existing patterns
The Admin UI is available when running in SSE mode (see Running the Server):
node dist/index.js --sse
# Then open: http://localhost:3100/adminFolders Tab:
- View all indexed folders with status
- Add new folders via path input
- Remove folders (deletes indexed data)
- Reindex folders (useful after upgrading to get new features)
Queries Tab:
- Query history with expandable results
- Timeline or per-folder view
- Clear query logs
Ingestion Tab:
- Real-time indexing progress
- File counts and error tracking
Services Tab:
- Ollama status and controls (start/stop)
- LSP enrichment status
Settings Tab:
- Embedding cache statistics
- Cache warming controls
- File watcher configuration
Use the configure tool to set up the server after connecting.
{
"qdrantUrl": "http://localhost:6333",
"embedderProvider": "ollama",
"ollamaBaseUrl": "http://localhost:11434",
"modelId": "nomic-embed-text:latest"
}{
"embedderProvider": "openai",
"openAiApiKey": "sk-...",
"modelId": "text-embedding-3-small"
}Supported: gemini, mistral, bedrock, openrouter, openai-compatible
You can configure different embedding models for different folders via the Admin UI. This is useful when:
- Some projects need higher quality embeddings (use larger models)
- Some projects need faster indexing (use smaller models)
- Testing different models for comparison
Supported models and their context sizes:
| Model | Provider | Context (tokens) |
|---|---|---|
nomic-embed-text |
Ollama | 8192 |
mxbai-embed-large |
Ollama | 512 |
text-embedding-3-small |
OpenAI | 8191 |
text-embedding-3-large |
OpenAI | 8191 |
text-embedding-ada-002 |
OpenAI | 8191 |
mistral-embed |
Mistral | 8192 |
voyage-code-2 |
Voyage | 16000 |
voyage-code-3 |
Voyage | 32000 |
gemma2 |
Ollama | 8192 |
snowflake-arctic-embed |
Ollama | 8192 |
Note: For code indexing, models with larger context (8192+ tokens) are recommended to avoid truncation of large functions/classes.
| Tool | Description |
|---|---|
add_folder |
Add a folder to be indexed |
remove_folder |
Remove a folder from indexing |
list_folders |
List all indexed folders |
clear_index |
Clear/rebuild index |
reindex_folder |
Reindex a folder (clears and rebuilds) |
pause_indexing |
Pause indexing for a folder |
resume_indexing |
Resume paused indexing |
reenrich_folder |
Re-enrich folder with LSP (for folders indexed without LSP) |
| Tool | Description |
|---|---|
search |
Perform semantic code search |
Search Parameters:
query(required): Natural language search queryfolderPath: Filter to specific folderfileTypes: Filter by extensions (e.g.,[".ts", ".js"])minScore: Minimum similarity (0-1)maxResults: Max results to return
| Tool | Description |
|---|---|
get_status |
Get indexing status |
get_errors |
Get error reports |
configure |
Update server configuration |
get_instructions |
Get AI assistant instructions (for CLAUDE.md, etc.) |
# From Claude CLI, you can use these tools directly:
# "search for authentication middleware"
# "add folder /path/to/project"
# "pause indexing for project-name"
# "reindex the api folder"JavaScript, TypeScript, TSX, JSX, Python, Rust, Go, C, C++, C#, Java, Ruby, PHP, Swift, Kotlin, Scala, Elixir, Erlang, Haskell, OCaml, Lua, Perl, R, Julia, Dart, Vue, Svelte, HTML, CSS, SCSS, SQL, GraphQL, Markdown, JSON, YAML, TOML, XML, Bash, PowerShell, Dockerfile, Terraform, Solidity, Zig, Nim, and more.
The server respects .gitignore files in indexed folders. You can also create a .cs-mcp-ignore file with additional patterns to exclude.
Always ignored directories:
node_modules,.git,dist,build,.next,.cache,coverage,.venv,vendor,target, etc.
All server data is stored in a platform-appropriate application data folder:
| Platform | Location |
|---|---|
| Windows | %LOCALAPPDATA%\code-search |
| macOS | ~/Library/Application Support/code-search |
| Linux | ~/.local/share/code-search (or $XDG_DATA_HOME/code-search) |
Folder contents:
code-search/
├── config.json # Server configuration
├── folders.json # Indexed folder registry
├── embedding-cache.db # SQLite embedding cache
├── lsp-cache.db # LSP enrichment cache
├── queries/ # Query logs (daily JSONL files)
│ └── queries-YYYY-MM-DD.jsonl
└── cache/ # Per-folder file hash cache
└── {folder-id}.json
Notes:
- Query logs are automatically cleaned up after 1 day
- Both stdio (Claude CLI) and SSE (Admin UI) servers share the same data
- Embedding cache persists across restarts to avoid redundant API calls
The server caches computed embeddings in SQLite to avoid redundant API calls. When re-indexing or updating files:
- Unchanged code chunks reuse cached embeddings
- Only new/modified code requires embedding API calls
- Cache is keyed by content hash and model ID
The cache auto-warms from existing Qdrant data on startup.
Enable Language Server Protocol integration to enrich code chunks with type information before embedding:
{
"lspEnabled": true,
"lspTimeout": 5000,
"lspMaxConcurrentRequests": 5,
"lspUseOmniSharp": false
}Benefits:
- Type signatures improve semantic matching
- Documentation/JSDoc included in embeddings
- Better results for type-related queries
Prerequisites:
- TypeScript/JavaScript:
npm install -g typescript-language-server typescript - C#:
dotnet tool install --global csharp-ls(or setlspUseOmniSharp: truefor OmniSharp)
Code chunks automatically include parent context from the AST:
- Class name for methods
- Module name for functions
- Namespace for nested types
- Parent function for nested functions
This helps queries like "authentication method in UserService" match more accurately.
The server automatically detects git worktrees and optimizes indexing:
How it works:
- When you add a worktree folder, the server detects it's a worktree
- The base repository is automatically discovered and indexed (if not already)
- Both repos are indexed, but the embedding cache deduplicates shared code
- Admin UI shows worktree relationships with "worktree" and "base repo" badges
Benefits:
- No duplicate embedding API calls for shared code (same content = cached embedding)
- Clear visualization of repo relationships in admin UI
- Automatic cleanup detection for deleted worktrees
Important: Keep your base repository checked out to the main/master branch for best results. The index reflects whatever is on disk, not a specific git branch.
Orphaned folder cleanup: If you delete a worktree from disk, the server marks it as "orphaned" on next startup. Use the Cleanup button in the admin UI to remove orphaned indexes.
When upgrading to a version with new enrichment features (LSP, hierarchical context), you need to reindex existing folders to take advantage of the improvements:
- Open Admin UI at
http://localhost:3100/admin - Go to the Folders tab
- Click Reindex on each folder
This clears the existing index and re-indexes all files with the new enrichment features.
See FUTURE-IMPROVEMENTS.md for planned enhancements including:
- LLM re-ranking for improved relevance
- Multi-level code summaries
- Graph-based relationship tracking
- Hybrid BM25 + vector search
Symptom: claude mcp list shows code-search: ... - ✗ Failed to connect
Solutions:
-
Verify the server can start manually:
node /path/to/code-search-mcp/dist/index.js
Should output:
[CodeSearch] Server running on stdio -
Check if Qdrant is running:
curl http://localhost:6333/collections
If not running, start it:
docker run -p 6333:6333 qdrant/qdrant -
Check if Ollama is running (if using Ollama embeddings):
curl http://localhost:11434/api/tags
If not running, start Ollama and ensure the model is pulled:
ollama pull nomic-embed-text -
Wrong config file location:
- Claude CLI reads
.mcp.jsonfrom the project root, NOT.claude/mcp.json - Use
claude mcp addcommand to ensure correct placement - Run
claude mcp listto verify registration
- Claude CLI reads
-
SSE vs stdio mode conflict:
- If running the Admin UI in SSE mode (
--sseflag), Claude CLI cannot connect - Stop the SSE server before using stdio mode with Claude CLI
- Or run two separate instances (different ports)
- If running the Admin UI in SSE mode (
-
Rebuild after code changes:
npm run build
-
Check for errors in server output:
- Run manually to see logs:
node dist/index.js - Look for embedding API errors (context length, rate limits)
- Run manually to see logs:
-
Embedding model context limits:
mxbai-embed-large: 512 tokens (small, may truncate code)nomic-embed-text: 8192 tokens (recommended for code)- Large code chunks are automatically truncated with a warning
-
Pause and resume indexing:
- Use Admin UI or MCP tools to pause/resume
- Check
get_errorsfor specific file failures
-
Check LSP server is installed:
# TypeScript/JavaScript typescript-language-server --version # C# csharp-ls --version
-
Enable LSP in config:
{ "lspEnabled": true } -
Check LSP status in Admin UI: Services tab shows LSP server status
- Reindex with LSP enabled for better type information
- Use descriptive queries: "function that validates user email" instead of "validate"
- Check folder is fully indexed: Use
get_statusor Admin UI - Try different embedding model: Some models work better for code
-
Use forward slashes in JSON config:
"args": ["C:/repos/code-search/dist/index.js"]
Not backslashes:
"C:\\repos\\..."(can cause escaping issues) -
Avoid spaces in paths or ensure proper quoting
# Find what's using port 3100
netstat -ano | findstr :3100 # Windows
lsof -i :3100 # macOS/Linux
# Use a different port
node dist/index.js --sse --port 3101-
Check Docker is running:
docker ps | grep qdrant -
Restart Qdrant container:
docker restart $(docker ps -q --filter ancestor=qdrant/qdrant) # Or start fresh: docker run -d -p 6333:6333 -v qdrant_storage:/qdrant/storage qdrant/qdrant
-
Check firewall/antivirus isn't blocking port 6333
-
Increase Node.js memory limit:
node --max-old-space-size=4096 dist/index.js
-
Index folders incrementally instead of all at once
-
Use
.cs-mcp-ignoreto exclude large generated files or vendor directories
- Use Ollama for local embeddings (no rate limits)
- Pause and resume indexing to spread out API calls
- Check provider dashboard for rate limit details
- The embedding cache prevents redundant calls on re-indexing
MIT License - see LICENSE file.
See THIRD-PARTY-LICENSES.md for dependency licenses.