Local-first semantic code search for AI coding agents, exposed via MCP.
Michel gives Claude Code, Codex, Cursor, Cline — or any MCP
aware agent — semantic search over your code. No more blind grep across a repo the agent
has never seen.
Everything runs locally: embeddings on CPU via ONNX, Qdrant as a native binary (no Docker), a launchd service to keep things warm across reboots. No telemetry, no cloud calls.
| MCP tool | When an agent should call it |
|---|---|
search_code(query) |
Semantic code search. Use before grep/glob/read. |
index_status() / reindex() |
Inspect or force a rebuild of the index. |
Files edited via an agent (Claude Code hook) or on disk (watchdog) are re-indexed automatically.
┌─── your repo ─────────────────────┐ ┌───── ~/.michel/ ────────┐
│ │ │ │
│ .py .ts .rs .md … │ │ bin/qdrant (native) │
│ │ │ │ qdrant/storage/ │
│ ▼ │ │ registry.db (sqlite) │
│ tree-sitter ─► chunks ───────► │────►│ │
│ │ │ │ Qdrant collections: │
│ ▼ │ │ code_<project> │
│ fastembed (ONNX, CPU) │ │ │
│ │ │ └─────────────────────────┘
│ ▼ │ ▲
│ watchdog ─► re-index on change │ │ MCP stdio
│ │ │
└───────────────────────────────────┘ ┌────┴─────┐
│ Claude │
│ Codex │
│ Cursor … │
└──────────┘
- Embeddings —
fastembedwithjinaai/jina-embeddings-v2-base-code. 768-dim, ONNX, fully local. No data leaves your machine. - Chunking —
tree-sittersplits source by symbol (function / class / block) so each hit is self-contained. Line-based fallback for unsupported languages. - Storage —
qdrantas a native binary (downloaded with a pinned SHA-256 checksum), one collection per project. - Watcher —
watchdogdebounces file events (500 ms) and re-indexes per file. - Services —
launchdkeepsqdrantand themichelwatcher daemon alive across reboots.
| Platform | Auto-install | MCP server | Indexer |
|---|---|---|---|
| macOS (Apple Silicon / Intel) | ✅ via michel daemon install |
✅ | ✅ |
| Linux | ✅ | ✅ | |
| Windows | ❌ not tested | ✅ (should work) | ✅ |
# From the repo root
pipx install -e . # or: uv tool install -e .
# Download Qdrant (SHA-256 verified), generate launchd plists
michel daemon install
# Load both services
michel daemon start
michel daemon status # should show both running + Qdrant HTTP OKcd /path/to/your/repo
michel initThis will:
- Register the project in
~/.michel/registry.db(SQLite, WAL mode). - Run a full index of every non-ignored file (respects
.gitignore+.michelignore). - Add
michelto~/.claude.jsonand~/.codex/config.tomlundermcpServers. A.michel-bakcopy is written the first time either file is modified. - Install a
PostToolUsehook in.claude/settings.json(Claude Code only) that re-indexes files after the agent edits them. - Inject a "Michel — Semantic Code Search" block into
CLAUDE.mdandAGENTS.md.
All writes to user dotfiles are atomic (tmp + os.replace) and refuse to overwrite an
existing file that cannot be parsed — Michel will not turn a transient config corruption
into permanent data loss.
michel init [PATH] [--name NAME] [--skip-index] [--skip-config-patch]
michel list
michel index [PATH] [--force]
michel index-file FILE [FILE …] [--project PATH]
michel rm <project-id> [--yes]
michel daemon install
michel daemon start | stop | status
michel daemon uninstall [--purge] [--yes]
michel mcp # stdio MCP server (usually launched by agents)
| I want to… | Command |
|---|---|
| Pause services (index preserved) | michel daemon stop |
| Resume | michel daemon start |
| Forget a single project | michel rm <project-id> |
| Fully uninstall, keep data | michel daemon uninstall |
| Fully uninstall, wipe data | michel daemon uninstall --purge |
uninstall always: unloads and deletes the launchd plists, removes the michel entry from
~/.claude.json and ~/.codex/config.toml, and strips michel-tagged hooks from every
registered project's .claude/settings.json. It deliberately does not touch CLAUDE.md /
AGENTS.md — you may have edited inside the block, and the block is inert without the hooks
and MCP server anyway.
~/.michel/config.toml (created on first michel daemon install):
qdrant_host = "127.0.0.1"
qdrant_port = 6333
embedding_model = "jinaai/jina-embeddings-v2-base-code"
embedding_dim = 768
max_chunk_tokens = 400
min_chunk_tokens = 40
debounce_ms = 500Types are validated on load — a string where an int is expected will loudly refuse to start rather than misbehave silently.
Michel reads .gitignore and .michelignore at the project root, plus a hardcoded list of
binary extensions (images, archives, compiled artefacts) and generated lockfiles
(package-lock.json, Cargo.lock, go.sum, …). Null-byte sniff catches anything else
that happens to be binary.
The compiled pathspec is cached per project root and invalidated by mtime, so the watcher
hot-path doesn't re-read .gitignore on every disk event.
~/.michel/
├── config.toml
├── registry.db # SQLite: projects + file hashes. WAL, migrations via PRAGMA user_version.
├── bin/qdrant # downloaded binary (SHA-256 verified)
├── qdrant/
│ ├── config.yaml
│ ├── storage/ # Qdrant data dir
│ └── snapshots/
└── logs/
├── qdrant.log qdrant.err.log
└── daemon.log daemon.err.log
~/Library/LaunchAgents/
├── com.michel.qdrant.plist
└── com.michel.daemon.plist
- No telemetry. Embeddings and chunks stay on your disk.
- No outbound calls except: (a) the initial Qdrant binary download from GitHub, verified
against a pinned SHA-256 in
src/michel/bootstrap/install.py; (b) fastembed downloading the embedding model on first run. - Atomic writes with backup for every user-owned config file Michel modifies.
- Parse-error abort: a corrupt
~/.claude.jsonhalts installation instead of overwriting.
See SECURITY.md for threat model and how to report issues.
michel daemon install currently only knows about launchd. On Linux:
pipx install -e .- Install Qdrant manually (binary or
cargo install qdrant); pointqdrant_host/qdrant_portin~/.michel/config.tomlat your instance. - Run
michel-daemonunder your init system of choice (sample systemd unit in CONTRIBUTING.md). - Add
michel-mcpto your agent's MCP config (Cursor, Cline, Codex, etc.) manually — Michel only auto-patches Claude Code and Codex user configs today.
PRs to extend bootstrap/install.py with a Linux code path are welcome.
uv venv --python 3.12
uv pip install -e ".[dev]"
pytest # unit tests (no Qdrant needed)
ruff checkSee CONTRIBUTING.md for patch guidelines, release flow, and the checksum bump procedure when upgrading Qdrant.
MIT. Because this thing lives in your home directory and touches your dotfiles, please actually read the "NO WARRANTY" bits.