Cairn Index
FreeMaintainedLocal hybrid index (FTS5 + vector embeddings + AST graph) over a single sqlite file. Curate, ingest, retrieve.
About
Local hybrid index (FTS5 + vector embeddings + AST graph) over a single sqlite file. Curate, ingest, retrieve.
README
A local, code-aware retrieval engine for grounding LLMs and coding agents — one SQLite file, no model required. cairn indexes codebases, docs, and web pages into hybrid search (lexical + vector) and a real AST knowledge graph — functions, types, and the calls/depends_on/imports edges between them — traversable inside a repo and across repos you link. On top of that it packs cursor-shaped context for completion and serves an MCP endpoint agents query directly.
Not embeddings-only RAG:
- A knowledge graph, not just chunks. tree-sitter extracts entities + call/reference edges;
graph/path/asktraverse them. Ask "what reaches this function," not only "what reads like it." - Cross-source linking.
cairn link sdk programresolves names from one indexed repo into another — a call in your SDK paths straight to its on-chain/program definition. Multi-repo systems become one graph. - Context packing for completion.
packassembles a token-budgeted neighborhood — callee signatures, touched types, callers — for a cursor. Deterministic, model-free, drift-flagged. The same context an editor plugin would build, exposed as a tool. - Inference-optional, tiered. Runs with zero models (FTS + graph). Add an embed endpoint for hybrid search; add a chat endpoint for doc-extract. You opt in, layer by layer.
- Local and yours. No telemetry, no bundled model; inference is HTTP to an endpoint you control. MCP-native, so any agent harness binds by URL.
pnpm add cairn-index # npm install cairn-index — the bins are cairn and cairn-mcp
cairn is the pipeline; inference is yours — and optional. [v3] cairn bundles no model; [v8] it needs no model at all. Three tiers, each opt-in to the next:
| tier | needs | adds | enable |
|---|---|---|---|
| 0 (default) | nothing | FTS5 chunk search + AST graph (calls/imports/depends, cross-source) + path |
make up — zero setup |
| 1 | embed endpoint | vector arm of search (RRF hybrid) + semantic graph(query) |
make up-embed / set CAIRN_EMBED_URL |
| 2 | chat endpoint | markdown doc-extract → concept/tag/doc-edges |
make up-extract / set CAIRN_CHAT_URL |
Endpoints are any OpenAI-compatible server (llama.cpp's llama-server, Ollama, vLLM, LM Studio, OpenAI, Azure). make up-embed/up-extract run host-native llama.cpp models so they get Metal on Mac / GPU on Linux (Docker can't pass a GPU into a container); cairn reaches them over host.docker.internal. See setup / inference.
Tier 0 is distinct from CAIRN_OFFLINE — that flag blocks non-localhost egress but still assumes a (localhost) model; tier 0 means no model at all.
Embedding dim is pinned at 1024 (what's tested) and guarded on every call. Switching tier 0 → 1 (or changing embed models) on an existing index: run cairn reindex to (re-)embed — plain refresh is hash-gated and skips unchanged files. Upgrading from v2: inference moved out of process — there's no embedded model or ~/.cairn/models anymore.
quick start
Fastest path — make up (tier 0, just Docker; no models):
cp .env.example .env # set CAIRN_INGEST_ROOT to the code you want indexed
make up # cairn-mcp (:8093) — FTS + AST graph, no models
# make up-embed # …tier 1: also start the embed server (:8091) for hybrid search
# make up-extract # …tier 2: also start the chat server + enable doc-extract
# point your MCP harness at http://localhost:8093/mcp (make down to stop)
Library — createCairn is the composition root; pass inference to opt into a tier (omit for tier 0):
import { createCairn } from 'cairn-index'
const cairn = createCairn() // tier 0; or createCairn({ inference: { embed: { url, model } } }) for tier 1
await cairn.start() // optional: stale-job recovery + eager embed health probe
await cairn.ingest.add({ kind: 'code', path: './src', label: 'my-project' })
const hits = await cairn.retrieve.search('how does the chunker handle overlap', { k: 5 })
cairn.close()
CLI:
cairn add ./src --label my-project
cairn search "how does the chunker handle overlap" -k 5
cairn graph "fee invariant"
cairn refresh all
what cairn is for
Local-first retrieval grounding for an LLM. You curate what's indexed (no automatic crawling), cairn add brings it in, and either you or a model running over MCP can query the result. Five query surfaces:
- Hybrid chunk search (
search) — FTS5 + vector embeddings fused via RRF. Default mode; returns ranked text chunks. In tier 0 (no embed) it degrades cleanly to FTS-only ranking (vec_rankis null on every hit). - Knowledge graph (
graph) — entities (functions, structs, concepts) and edges (calls,depends_on,mitigates,references,verifies) extracted from code (tree-sitter, AST-based) and markdown (LLM, hash-gated).graph(query)uses semantic entity search in tier 1+, falling back to lexical entity match (entities_fts) in tier 0. - Composed retrieval (
ask) — hybrid search + per-hit entity context in one call. - Shortest path (
path) — BFS between two entities via batched layer fetch (one SQL per BFS layer). - Tag-filtered retrieval (
tags,--tag) — concept entities carry free-form LLM-emitted tags; filtersearch/ask/graphby tag.
Cross-source linking (cairn link sdk program) lets you resolve names across two related sources — SDK calling its on-chain program is the canonical case. Soft-delete + FK cascades keep the graph clean across refreshes and removals.
Beyond query-shaped retrieval, cairn exposes a cursor-shaped surface for code completion:
- Context packing (
complete.contextPack, CLIcairn pack, MCPpack) — given an enclosing symbol, assemble a token-budgeted pack of its graph neighborhood (callees' signatures, the types it touches, its callers) for a completion model's context window. Deterministic, no model call on the hot path (pure graph walk + indexed seeks); drift made visible via a per-snippetstaleflag. The MCP tool lets a thinking-loop agent pull the same cursor-context — no separate completion model needed. See completion.
architecture
- data layer —
better-sqlite3+sqlite-vec. Flat tables (sources,files,chunks,entities,edges,source_links) plus FTS5 + vec0 virtual tables. FK cascades fromsourcesthrough entities into edges; triggers keepchunks_vecandentities_vecin sync. - ingestion — gitignore-aware walker → boring chunkers (code: 60-line / 10-overlap; text: ~2000-char / paragraph-snapping) → (tier 1+) batch embed via the configured endpoint (1024-dim, guarded) → atomic insert. Tier 0 skips embedding and writes no vectors;
cairn reindexbackfills them once an embed endpoint is configured. - graph — tree-sitter (Rust, TypeScript / TSX, Python) extracts entities and per-fn AST call/ref maps. Edge layer derives
calls/depends_onparse edges intra-file, cross-file, and cross-source (when sources are explicitly linked viacairn link). Optional LLM pass over markdown emitsmitigates/references/verifiesdoc edges (capped confidence 0.7, hash-gated) plus free-form tags on concept entities. Fails open: if the chat endpoint is unreachable, doc-extract is skipped and chunk indexing continues. Soft-delete on entities; clean rebuild on every refresh. - retrieval — FTS5 + (tier 1+) vec each return top-50; reciprocal rank fusion with
k=60; hydrate via narrow indexed seeks. No reranker, no tuning. In tier 0 the fusion degenerates to FTS rank (vec_rank: null) andgraph(query)falls back to lexical entity match overentities_fts. Empty-query short-circuit. - inference —
Embed/Chatproviders wrap anEmbedRuntime/ChatRuntime; the runtimes are thin OpenAI-compatible HTTP clients (/v1/embeddings,/v1/chat/completionswithresponse_formatjson_schema). Swapping the backend is config, not code. See inference. - serving —
cairn-mcpis a passive Streamable-HTTP MCP server (CAIRN_MCP_TRANSPORT=http, default:8093) — any harness binds by URL; stdio is still available for spawn-based hosts. See serving. - interface —
createCairn(opts)builds the graph at one composition root and injects it into theCairnclass (side-effect-free ctor; per-concern providersDb/Embed?/Chat?/Ingest/Retrieve/Complete);await cairn.start()runs recovery + an eager health probe. CLI covers ingestion, graph queries, and admin (init/add/list/search/ask/graph/path/pack/tags/refresh/reindex/link/unlink/links/remove/gc). MCP exposes the same surface (searchlistaddlinkunlinklinksgraphaskpathtagsrefreshremovereindexgcjobjobspack) for agent-driven workflows; onlyinitis CLI-only.
configuration & safety
Cairn is a curated index — you trust what you put in, and you control the surface around ingestion via env vars.
| Env var | Default | Purpose |
|---|---|---|
CAIRN_EMBED_URL / CAIRN_EMBED_MODEL |
unset → tier 0 | Set to an OpenAI-compatible embedding endpoint (must return 1024-dim) to enable tier 1 (hybrid search). Unset = lexical + graph only. |
CAIRN_CHAT_URL / CAIRN_CHAT_MODEL |
unset | Set to enable tier 2 markdown doc-extract (structured outputs). Best-effort; fails open. Requires an embed endpoint too. |
CAIRN_MCP_TRANSPORT / CAIRN_MCP_PORT |
http / 8093 |
MCP transport + port for cairn-mcp (stdio for spawn-based hosts). |
CAIRN_MCP_TOKEN |
unset | Bearer token required on every MCP request when set (use to expose beyond localhost). |
CAIRN_OFFLINE |
unset | When 1/true, blocks fetchWeb (cairn add <url>) and refuses any non-localhost inference URL — one coherent "no non-localhost egress" switch. |
CAIRN_INGEST_ROOT |
./ |
Compose only: host dir bind-mounted read-only at /projects. Address sources over MCP by their container path ($CAIRN_INGEST_ROOT/foo → /projects/foo), not the host path — see setup.md. |
CAIRN_ALLOWED_ROOTS |
unset | Comma-separated absolute paths; cairn add rejects local paths outside them. In Docker, the read-only mount is the hard boundary on top. |
CAIRN_MAX_INGEST_FILES / CAIRN_MAX_INGEST_BYTES |
10000 / 500 MB |
Abort directory ingestion before chunking if it exceeds the cap. CLI --force bypasses; MCP does not. |
All inference is HTTP to an endpoint you control — no telemetry, no bundled model. Keys (CAIRN_EMBED_KEY / CAIRN_CHAT_KEY) are sent only to the endpoints you configure.
stack
| dep | purpose |
|---|---|
better-sqlite3 |
sync sqlite driver |
sqlite-vec |
vector search extension |
tree-sitter + tree-sitter-rust / tree-sitter-typescript / tree-sitter-python |
AST entity + call extraction |
@modelcontextprotocol/sdk |
MCP server (streamable-http + stdio) |
linkedom / unpdf |
HTML / PDF → text |
zod |
tool input + LLM output schemas |
Inference is not a dependency — it's an external OpenAI-compatible endpoint. The compose stack runs qwen3-embedding-0.6b (1024-dim) for embeddings and qwen3-0.6b for doc-extract, but any compatible models work (embeddings must be 1024-dim).
docs
- setup — install, the compose stack, bring-your-own-endpoints, harness wiring, troubleshooting.
- inference — the OpenAI-compatible embed/chat contract, dim guard, config.
- serving — passive Streamable-HTTP MCP server, transports, auth seam.
- design — data model, retrieval pipeline, decisions, what's out of scope.
- completion — cursor-shaped context packing for code completion.
- graph — knowledge-graph layer (entities, edges, soft-delete, AST extraction).
- tags — concept-tag design + LLM emission + storage shape.
- next — completed roadmap with retrospectives plus remaining open items.
- enterprise — pitch / reference for an enterprise-shaped variant on Postgres + Azure OpenAI.
Install Cairn Index in Claude Desktop, Claude Code & Cursor
unyly install cairn-indexInstalls into Claude Desktop, Claude Code, Cursor & VS Code — handles npx, uvx and build-from-source repos for you.
First time? Get the CLI: curl -fsSL https://unyly.org/install | sh
Or configure manually
Run in your terminal:
claude mcp add cairn-index -- npx -y cairn-indexStep-by-step: how to install Cairn Index
FAQ
Is Cairn Index MCP free?
Yes, Cairn Index MCP is free — one-click install via Unyly at no cost.
Does Cairn Index need an API key?
No, Cairn Index runs without API keys or environment variables.
Is Cairn Index hosted or self-hosted?
Self-hosted: the server runs locally on your machine via the install command above.
How do I install Cairn Index in Claude Desktop, Claude Code or Cursor?
Open Cairn Index on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.
Changes
Versions and requested access over time.
- New version published
- New version published
Related MCPs
wenb1n-dev/SmartDB_MCP
A universal database MCP server supporting simultaneous connections to multiple databases. It provides tools for database operations, health analysis, SQL optim
by wenb1n-devPostgres Server
This server enables interaction with PostgreSQL databases through the Model Context Protocol, optimized for the AWS Bedrock AgentCore Runtime. It provides tools
by madhurprashPostgres
Query your database in natural language
by AnthropicPostgreSQL
Read-only database access with schema inspection.
by modelcontextprotocolRedis
Interact with Redis key-value stores.
by modelcontextprotocolSQLite
Database interaction and business intelligence capabilities.
by modelcontextprotocolmxcp
Open-source framework for building enterprise-grade MCP servers using just YAML, SQL, and Python, with built-in auth, monitoring, ETL and policy enforcement.
by raw-labstadas-github/a2asearch-mcp
MCP server to search 4,800+ MCP servers, AI agents, CLI tools and agent skills. Install: npx -y a2asearch-mcp. Ask Claude: "Find MCP servers for database access
by tadas-githubjulien040/anyquery
Query more than 40 apps with one binary using SQL. It can also connect to your PostgreSQL, MySQL, or SQLite compatible database. Local-first and private by desi
by julien040drakonkat/wizzy-mcp-tmdb
A MCP server for The Movie Database API that enables AI assistants to search and retrieve movie, TV show, and person information.
by drakonkatCompare Cairn Index with
Not sure what to pick?
Find your stack in 60 seconds
Author?
Embed badge for your README
Browse similar
All data MCPs
