{"id":46274504,"url":"https://github.com/us/qex","last_synced_at":"2026-03-04T04:04:20.835Z","repository":{"id":341921816,"uuid":"1172039773","full_name":"us/qex","owner":"us","description":"Lightweight MCP server for semantic code search — BM25 + optional dense vectors + tree-sitter chunking","archived":false,"fork":false,"pushed_at":"2026-03-04T00:23:30.000Z","size":182,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-04T02:12:00.415Z","etag":null,"topics":["bm25","claude","code-search","mcp","onnx","rust","semantic-search","tree-sitter","vector-search"],"latest_commit_sha":null,"homepage":"https://crates.io/crates/qex-mcp","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/us.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-03T22:07:59.000Z","updated_at":"2026-03-04T00:23:33.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/us/qex","commit_stats":null,"previous_names":["us/qex"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/us/qex","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/us%2Fqex","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/us%2Fqex/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/us%2Fqex/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/us%2Fqex/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/us","download_url":"https://codeload.github.com/us/qex/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/us%2Fqex/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30071671,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-04T03:25:38.285Z","status":"ssl_error","status_checked_at":"2026-03-04T03:25:05.086Z","response_time":59,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bm25","claude","code-search","mcp","onnx","rust","semantic-search","tree-sitter","vector-search"],"created_at":"2026-03-04T04:04:15.767Z","updated_at":"2026-03-04T04:04:20.826Z","avatar_url":"https://github.com/us.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003eQEX\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cstrong\u003eLightweight MCP server for semantic code search\u003c/strong\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  BM25 + optional dense vectors + tree-sitter chunking\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://crates.io/crates/qex-mcp\"\u003e\u003cimg src=\"https://img.shields.io/crates/v/qex-mcp.svg\" alt=\"crates.io\"\u003e\u003c/a\u003e\n  \u003ca href=\"LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/badge/License-AGPL--3.0-blue.svg\" alt=\"License: AGPL-3.0\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://www.rust-lang.org/\"\u003e\u003cimg src=\"https://img.shields.io/badge/Rust-2021_edition-orange.svg\" alt=\"Rust\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cstrong\u003eEnglish\u003c/strong\u003e | \u003ca href=\"README.zh-CN.md\"\u003e中文\u003c/a\u003e\n\u003c/p\u003e\n\n---\n\nQEX is a high-performance MCP server for semantic code search built in Rust. It combines BM25 full-text search with optional dense vector embeddings for hybrid retrieval — delivering Cursor-quality search from a single ~19 MB binary. Tree-sitter parsing understands code structure (functions, classes, methods), Merkle DAG change detection enables incremental indexing, and everything runs locally with zero cloud dependencies.\n\n## What's New\n\n- **Pluggable Embedding Backends** — Trait-based abstraction over ONNX Runtime (local) and OpenAI API embedding providers with env var configuration\n- **Hybrid Search** — BM25 + dense vector search with Reciprocal Rank Fusion for 48% better accuracy than dense-only retrieval\n- **10 Language Support** — Python, JavaScript, TypeScript, Rust, Go, Java, C, C++, C#, Markdown via tree-sitter\n- **Incremental Indexing** — Merkle DAG change detection, only re-indexes what changed\n- **Optional Dense Vectors** — snowflake-arctic-embed-s (33 MB, 384-dim, INT8 quantized) via ONNX Runtime, or OpenAI text-embedding-3-small via API\n- **MCP Native** — plugs directly into Claude Code as a tool server via stdio\n\n## Why QEX?\n\nClaude Code uses grep + glob for code search — effective but token-hungry and lacks semantic understanding. Cursor uses vector embeddings with cloud indexing (~3.5 GB stack). **QEX** is the middle ground:\n\n- **BM25 + Dense Hybrid**: 48% better accuracy than dense-only retrieval ([Superlinked 2025](https://superlinked.com/vectorhub/articles/optimizing-rag-with-hybrid-search-reranking))\n- **Tree-sitter Chunking**: Understands code structure — functions, classes, methods — not just lines\n- **Incremental Indexing**: Merkle DAG change detection, only re-indexes what changed\n- **Zero Cloud Dependencies**: Everything runs locally via ONNX Runtime\n- **MCP Native**: Plugs directly into Claude Code as a tool server\n\n## Quick Start\n\n**Install from crates.io:**\n\n```bash\ncargo install qex-mcp\n\n# Add to Claude Code\nclaude mcp add qex --scope user -- ~/.cargo/bin/qex\n```\n\n**Build from source:**\n\n```bash\n# Build (BM25-only, ~19 MB)\ncargo build --release\n\n# Or with dense vector search (~36 MB)\ncargo build --release --features dense\n\n# Or with OpenAI embedding support\ncargo build --release --features openai\n\n# Or with all embedding backends\ncargo build --release --features \"dense,openai\"\n\n# Install\ncp target/release/qex ~/.local/bin/\n\n# Add to Claude Code\nclaude mcp add qex --scope user -- ~/.local/bin/qex\n```\n\nThat's it. Claude will now have access to `search_code` and `index_codebase` tools.\n\n### Enable Dense Search (Optional)\n\nDense search adds semantic understanding — finding \"authentication middleware\" even when the code says `verify_token`. Two embedding backends are available:\n\n#### Option A: Local ONNX Model (Recommended)\n\nRequires the `dense` feature flag. Zero cloud dependencies.\n\n```bash\n# Download the embedding model (~33 MB)\n./scripts/download-model.sh\n\n# Or via MCP tool (after adding to Claude)\n# Claude: \"download the embedding model\"\n```\n\n**Model**: [snowflake-arctic-embed-s](https://huggingface.co/Snowflake/snowflake-arctic-embed-s) — 384-dim, INT8 quantized, 512 token max.\n\nWhen the model is present, search automatically switches to hybrid mode. No configuration needed.\n\n#### Option B: OpenAI API Embeddings\n\nRequires the `openai` feature flag and an API key. Supports any OpenAI-compatible API.\n\n```bash\n# Build with OpenAI support (can combine with dense)\ncargo build --release --features \"dense,openai\"\n\n# Configure\nexport QEX_EMBEDDING_PROVIDER=openai\nexport QEX_OPENAI_API_KEY=sk-...  # or OPENAI_API_KEY\n```\n\nSee [Configuration](#embedding-backends) for all options.\n\n## Architecture\n\n```\nClaude Code ──(stdio/JSON-RPC)──▶ qex\n                                      │\n                      ┌───────────────┼───────────────┐\n                      ▼               ▼               ▼\n                 tree-sitter      tantivy        ort + usearch\n                  Chunking         BM25         Dense Vectors\n                 (11 langs)       (\u003c1ms)         (optional)\n                      │               │               │\n                      └───────┬───────┘               │\n                              ▼                       │\n                      Ranking Engine ◄────────────────┘\n                    (RRF + multi-factor)\n                              │\n                              ▼\n                      Ranked Results\n```\n\n### How Search Works\n\n1. **Query Analysis** — Tokenization, stop-word removal, intent detection\n2. **BM25 Search** — Full-text search via tantivy with field boosts (name, content, tags, path)\n3. **Dense Search** _(optional)_ — Embed query via pluggable backend (ONNX or OpenAI) → HNSW cosine similarity → top-k vectors\n4. **Reciprocal Rank Fusion** — Merge BM25 and dense results: `score = Σ 1/(k + rank)`\n5. **Multi-factor Ranking** — Re-rank by chunk type, name match, path relevance, tags, docstring presence\n6. **Test Penalty** — Down-rank test files (0.7×) to prioritize implementation code\n\n### How Indexing Works\n\n1. **File Walking** — Respects `.gitignore`, filters by extension\n2. **Tree-sitter Parsing** — Language-aware AST traversal, extracts functions/classes/methods\n3. **Chunk Enrichment** — Tags (async, auth, database...), complexity score, docstrings, decorators\n4. **BM25 Indexing** — 14-field tantivy schema with per-field boosts\n5. **Dense Indexing** _(optional)_ — Batch embedding via `Embedder` trait (ONNX or OpenAI) → HNSW index\n6. **Merkle Snapshot** — SHA-256 DAG for incremental change detection\n7. **Dimension Guard** — `dense_meta.json` tracks provider/model/dimensions; mismatches trigger full re-index\n\n## MCP Tools\n\n### `index_codebase`\nIndex a project for semantic search.\n\n| Parameter | Type | Required | Description |\n|-----------|------|----------|-------------|\n| `path` | string | yes | Absolute path to project directory |\n| `force` | boolean | no | Force full re-index (default: false) |\n| `extensions` | string[] | no | Only index specific extensions, e.g. `[\"py\", \"rs\"]` |\n\nReturns file count, chunk count, detected languages, and timing.\n\n### `search_code`\nSearch the indexed codebase with natural language or keywords.\n\n| Parameter | Type | Required | Description |\n|-----------|------|----------|-------------|\n| `path` | string | yes | Absolute path to project directory |\n| `query` | string | yes | Search query (natural language or keywords) |\n| `limit` | integer | no | Max results (default: 10) |\n| `extension_filter` | string | no | Filter by extension, e.g. `\"py\"` |\n\nAuto-indexes if needed. Returns ranked results with code snippets, file paths, line numbers, and relevance scores.\n\n### `get_indexing_status`\nCheck if a project is indexed and get stats.\n\n| Parameter | Type | Required | Description |\n|-----------|------|----------|-------------|\n| `path` | string | yes | Absolute path to project directory |\n\nReturns index status, file/chunk counts, languages, and whether dense search is available.\n\n### `clear_index`\nDelete all index data for a project.\n\n| Parameter | Type | Required | Description |\n|-----------|------|----------|-------------|\n| `path` | string | yes | Absolute path to project directory |\n\n### `download_model`\nDownload the embedding model for dense search. Requires the `dense` feature.\n\n| Parameter | Type | Required | Description |\n|-----------|------|----------|-------------|\n| `force` | boolean | no | Re-download even if exists (default: false) |\n\n## Supported Languages\n\n| Language | Extensions | Chunk Types |\n|----------|------------|-------------|\n| Python | `.py`, `.pyi` | function, method, class, module-level, imports |\n| JavaScript | `.js` | function, method, class, module-level |\n| TypeScript | `.ts`, `.tsx` | function, method, class, interface, module-level |\n| Rust | `.rs` | function, method, struct, enum, trait, impl, macro |\n| Go | `.go` | function, method, struct, interface |\n| Java | `.java` | method, class, interface, enum |\n| C | `.c`, `.h` | function, struct |\n| C++ | `.cpp`, `.cc`, `.cxx`, `.hpp` | function, method, class, struct, namespace |\n| C# | `.cs` | method, class, struct, interface, enum, namespace |\n| Markdown | `.md` | section, document |\n\n## Crates\n\n| Crate | Description | |\n|-------|-------------|-|\n| [`qex-core`](crates/qex-core) | Core library: chunking, search, indexing, Merkle DAG | [![crates.io](https://img.shields.io/crates/v/qex-core.svg)](https://crates.io/crates/qex-core) |\n| [`qex-mcp`](crates/qex-mcp) | MCP server binary (stdio transport via rmcp) | [![crates.io](https://img.shields.io/crates/v/qex-mcp.svg)](https://crates.io/crates/qex-mcp) |\n\n## Project Structure\n\n```\nqex/\n├── Cargo.toml                        # Workspace root\n├── scripts/\n│   └── download-model.sh             # Model download script\n├── crates/\n│   ├── qex-core/            # Core library\n│   │   └── src/\n│   │       ├── lib.rs\n│   │       ├── chunk/                # Tree-sitter chunking engine\n│   │       │   ├── tree_sitter.rs    # AST traversal\n│   │       │   ├── multi_language.rs # Language dispatcher\n│   │       │   └── languages/        # 11 language implementations\n│   │       ├── search/               # Search engines\n│   │       │   ├── bm25.rs           # Tantivy BM25 index\n│   │       │   ├── dense.rs          # HNSW vector index (feature: dense)\n│   │       │   ├── embedding.rs      # Embedder trait + ONNX backend (feature: dense|openai)\n│   │       │   ├── openai_embedder.rs # OpenAI API backend (feature: openai)\n│   │       │   ├── hybrid.rs         # Reciprocal Rank Fusion (feature: dense)\n│   │       │   ├── ranking.rs        # Multi-factor re-ranking\n│   │       │   └── query.rs          # Query analysis\n│   │       ├── index/                # Incremental indexer\n│   │       │   ├── mod.rs            # Main indexing logic\n│   │       │   └── storage.rs        # Project storage layout\n│   │       ├── merkle/               # Change detection\n│   │       │   ├── mod.rs            # Merkle DAG\n│   │       │   ├── change_detector.rs\n│   │       │   └── snapshot.rs\n│   │       └── ignore.rs             # Gitignore-aware file walking\n│   │\n│   └── qex-mcp/            # MCP server binary\n│       └── src/\n│           ├── main.rs               # Entry point, stdio transport\n│           ├── server.rs             # Tool handlers\n│           ├── tools.rs              # Parameter schemas\n│           └── config.rs             # CLI args\n│\n└── tests/fixtures/                   # Test source files\n```\n\n## Storage\n\nAll data is stored locally under `~/.qex/`:\n\n```\n~/.qex/\n├── projects/\n│   └── {name}_{hash}/         # Per-project index\n│       ├── tantivy/           # BM25 index\n│       ├── dense/             # Vector index (optional)\n│       │   ├── dense.usearch  # HNSW index file\n│       │   ├── dense_mapping.json  # Chunk ID ↔ vector key mapping\n│       │   └── dense_meta.json     # Provider/model/dimensions guard\n│       ├── snapshot.json      # Merkle DAG\n│       └── stats.json         # Index stats\n│\n└── models/\n    └── arctic-embed-s/        # Embedding model (optional)\n        ├── model.onnx         # 33 MB, INT8 quantized\n        └── tokenizer.json\n```\n\n## Embedding Backends\n\nQEX uses a pluggable `Embedder` trait to support multiple embedding providers. The backend is selected via the `QEX_EMBEDDING_PROVIDER` environment variable.\n\n### ONNX Runtime (default)\n\nLocal inference with zero cloud dependencies. Requires the `dense` feature flag.\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `QEX_EMBEDDING_PROVIDER` | `onnx` | Set to `onnx` (or omit) |\n| `QEX_ONNX_MODEL_DIR` | `~/.qex/models/arctic-embed-s` | Override model directory |\n\n### OpenAI API\n\nCloud-based embeddings via the OpenAI API (or any compatible API like Ollama, LiteLLM, Azure). Requires the `openai` feature flag.\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `QEX_EMBEDDING_PROVIDER` | — | Set to `openai` |\n| `QEX_OPENAI_API_KEY` | — | API key (also reads `OPENAI_API_KEY`) |\n| `QEX_OPENAI_MODEL` | `text-embedding-3-small` | Model name |\n| `QEX_OPENAI_BASE_URL` | `https://api.openai.com/v1` | API base URL |\n| `QEX_OPENAI_DIMENSIONS` | auto | Override dimensions for unknown models |\n\n**Security features:**\n- SSRF protection: only HTTPS or `http://localhost` URLs allowed for base URL\n- API key sanitization: keys are never leaked in error messages\n- Typed retry: exponential backoff (1s, 2s, 4s) on 429/5xx/timeout/connection errors\n\n**Compatible APIs:** Any OpenAI-compatible embeddings endpoint works. Set `QEX_OPENAI_BASE_URL` to your provider's URL:\n\n```bash\n# Ollama\nexport QEX_OPENAI_BASE_URL=http://localhost:11434/v1\n\n# Azure OpenAI\nexport QEX_OPENAI_BASE_URL=https://your-resource.openai.azure.com/openai/deployments/your-model\n```\n\n### Dimension Mismatch Guard\n\nWhen switching embedding providers or models, QEX detects the mismatch via `dense_meta.json` and automatically triggers a full re-index. This prevents silent search quality degradation from mismatched vector spaces.\n\n## Build \u0026 Test\n\n```bash\n# Run tests (BM25-only)\ncargo test                              # 41 tests\n\n# Run tests (with dense search)\ncargo test --features dense             # 48 tests\n\n# Run tests (with OpenAI embedder)\ncargo test --features openai            # 50 tests\n\n# Run tests (all features)\ncargo test --features \"dense,openai\"    # 55 tests\n\n# Build for release\ncargo build --release                   # ~19 MB binary\ncargo build --release --features dense  # ~36 MB binary\ncargo build --release --features \"dense,openai\"  # All backends\n```\n\n## Key Dependencies\n\n| Crate | Version | Purpose |\n|-------|---------|---------|\n| tantivy | 0.22 | BM25 full-text search |\n| tree-sitter | 0.24 | Code parsing (11 languages) |\n| rmcp | 0.17 | MCP server framework (stdio) |\n| rusqlite | 0.32 | SQLite metadata (bundled) |\n| ignore | 0.4 | Gitignore-compatible file walking |\n| rayon | 1.10 | Parallel chunking |\n| ort | 2.0.0-rc.11 | ONNX Runtime _(optional, `dense`)_ |\n| usearch | 2.24 | HNSW vector index _(optional, `dense`)_ |\n| tokenizers | 0.22 | HuggingFace tokenizer _(optional, `dense`)_ |\n| ureq | 3 | Sync HTTP client _(optional, `openai`)_ |\n\n## Performance\n\nBenchmarked on an Apple Silicon Mac:\n\n| Metric | Value |\n|--------|-------|\n| Full index (400 chunks) | ~20s with dense, ~2s BM25-only |\n| Incremental index (no changes) | \u003c100ms |\n| BM25 search | \u003c5ms |\n| Hybrid search | ~50ms (includes embedding) |\n| Binary size | 19 MB (BM25) / 36 MB (dense) |\n| Model size | 33 MB (INT8 quantized) |\n\n## License\n\n[AGPL-3.0](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fus%2Fqex","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fus%2Fqex","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fus%2Fqex/lists"}