leann-dotnet 2.5.6

dotnet tool install --global leann-dotnet --version 2.5.6
                    
This package contains a .NET tool you can call from the shell/command line.
dotnet new tool-manifest
                    
if you are setting up this repo
dotnet tool install --local leann-dotnet --version 2.5.6
                    
This package contains a .NET tool you can call from the shell/command line.
#tool dotnet:?package=leann-dotnet&version=2.5.6
                    
nuke :add-package leann-dotnet --version 2.5.6
                    

LEANN .NET

CI License: MIT

A native .NET 10 MCP server for semantic code search. Chunks source repositories, computes embeddings via ONNX (GPU-accelerated), and serves results over the Model Context Protocol.

Ported from LEANN by Yichuan Wang — an innovative Python-based vector database and MCP server for personal AI. The original project pioneered graph-based selective recomputation for ultra-compact vector indexes (97% less storage than traditional solutions) and supports indexing everything from codebases to emails to browser history — all locally with zero cloud costs. This .NET port focuses on the semantic code search and MCP server pipeline, rebuilt natively for Windows/macOS with GPU acceleration via ONNX Runtime.

Why a .NET port?

The original LEANN requires Python, WSL (on Windows), PyTorch, and several other dependencies — a significant setup burden, especially on corporate machines with limited install permissions. This port is a single self-contained executable with zero external dependencies. No Python, no WSL, no pip, no virtual environments. Just download and run.

It works natively on Windows (DirectML GPU acceleration) and macOS (CoreML on Apple Silicon), with automatic CPU fallback on any platform.

How It Works

Source Code → Chunk → Embed (GPU) → Index → MCP Search
  1. Chunk — splits source files into overlapping passages (code-aware: respects functions, classes, blocks)
  2. Embed — computes 768-dim vectors using jinaai/jina-embeddings-v2-base-code (default; code-aware, 30 programming languages, 8192 max sequence length) via ONNX Runtime (DirectML on Windows, CoreML on macOS). The legacy facebook/contriever model is still selectable with --model facebook/contriever or LEANN_MODEL=facebook/contriever.
  3. Index — stores embeddings in a flat vector index with L2-normalized cosine similarity
  4. Search — any MCP client (VS Code Copilot, Claude Desktop, etc.) queries the index via semantic search

Quick Start

Prerequisites

  • .NET 10 SDK
  • GPU recommended but not required (falls back to CPU)
  • macOS: Apple Silicon (M1/M2/M3/M4) required — Intel Macs are not supported
dotnet tool install -g leann-dotnet
leann-dotnet --setup                                  # downloads the default jina-embeddings-v2-base-code model (~282 MB zip → ~306 MB ONNX)
# or explicitly:
leann-dotnet --setup --model jinaai/jina-embeddings-v2-base-code
leann-dotnet --setup --model facebook/contriever     # legacy 418 MB English-prose model

The model is extracted to ~/.leann/models/<sanitized-id>/ and is idempotent (re-running --setup is a no-op once the SHA256 marker is present; pass --force to re-download).

Option B: Build from source

# Git LFS required — the repo includes the 418 MB ONNX model
git lfs install
git clone https://github.com/d-german/leann-dotnet.git
cd leann-dotnet
dotnet publish src/LeannMcp -r win-x64 --self-contained -c Release -o publish/win-x64

Forgot git lfs install? Run git lfs pull to download model.onnx.

Note: leann-dotnet is both the NuGet package id and the installed command name. After dotnet tool install -g leann-dotnet the leann-dotnet executable is on your PATH and is what every example below invokes.

Index Your Code

cd <data-root>

# Step 1: Chunk source code into passages
leann-dotnet --build-passages --docs /path/to/my-repo --index-name my-repo

# Step 2: Compute embeddings (GPU-accelerated)
leann-dotnet --build-indexes --index my-repo

# Or do both in one command:
leann-dotnet --rebuild --docs /path/to/my-repo --index-name my-repo

# Restrict to specific file types (whitelist; comma- or space-separated):
leann-dotnet --rebuild --docs /path/to/my-repo --index-name my-repo \
  --file-types .cs,.csproj,.sln,.props,.targets

This creates <data-root>/.leann/indexes/my-repo/ with passages + embeddings.

Tip: Use --file-types to dramatically shrink the index on large polyglot repos. Restricting a 2.4M-passage C#/JS/CSS mono-repo to .cs,.csproj,.sln typically cuts the embeddings file from ~7 GB down to ~1–2 GB and removes noise (minified JS, vendored libraries, test fixtures) from search results.

4. Connect an MCP Client

Workspace auto-detection (new!): Register once globally — LEANN auto-resolves its data directory to whatever workspace your MCP client is currently in, via the MCP roots capability. No cwd, no per-project LEANN_DATA_ROOT. See docs/workspace-roots-design.md.

Add to your MCP client config (e.g., .vscode/mcp.json):

{
  "servers": {
    "leann": {
      "type": "stdio",
      "command": "leann-dotnet",
      "args": []
    }
  }
}

That's it — switching VS Code workspaces hot-swaps indexes without a restart.

Resolution priority (MCP-server mode, evaluated per tool call):

  1. LEANN_DATA_ROOT env var — explicit override
  2. MCP client-advertised roots (e.g., VS Code workspace folder)
  3. Directory.GetCurrentDirectory() — fallback

Override (only if your client doesn't advertise roots and isn't launched from the workspace folder):

"env": { "LEANN_DATA_ROOT": "${workspaceFolder}" }

LEANN_MODEL is only needed to change the default model used when building indexes or to pick which model the server warms up at startup. At query time, the server automatically loads each index's own embedding model from its manifest — you can mix models freely in one workspace.

Index compatibility: every index records the embedding model + dimensions used to build it. The server reads the manifest at load time and uses the matching model to embed queries. Cross-model querying that previously errored out (IndexCompatibility: refusing index ...) now Just Works as of v2.4.0.

Mixing models in one workspace

Code repositories generally retrieve better with jinaai/jina-embeddings-v2-base-code; PDF manuals and prose retrieve better with facebook/contriever. You can build both kinds of index side-by-side and query them all from one MCP session:

# Build a code index with the default Jina model
leann-dotnet --rebuild --docs C:\repos\my-app --index-name my-app

# Build a PDF index with Contriever (text-domain model)
leann-dotnet --rebuild --docs C:\docs\manuals --index-name manuals `
  --model facebook/contriever --file-types .pdf

Both indexes are now queryable from a single MCP server — leann_search index_name="my-app" uses Jina, leann_search index_name="manuals" uses Contriever, no restart and no LEANN_MODEL change needed.

From your MCP client, use these tools:

  • leann_search — semantic search across all indexed repos
  • leann_list — list available indexes
  • leann_warmup — pre-load embedding model (faster first search)

Data Layout

<data-root>/
└── .leann/
    └── indexes/
        ├── my-repo/
        │   ├── documents.leann.meta.json
        │   ├── documents.leann.passages.jsonl
        │   ├── documents.ids.txt
        │   ├── documents.embeddings.bin
        │   └── documents.embeddings.meta.json
        └── another-repo/
            └── ...

~/.leann/
└── models/
    ├── jinaai-jina-embeddings-v2-base-code/   # default code-aware model
    │   ├── model.onnx
    │   ├── tokenizer.json
    │   ├── vocab.json
    │   ├── merges.txt
    │   └── .sha256.ok                         # idempotency marker
    └── facebook-contriever/                   # legacy English-prose model (optional)
        ├── model.onnx
        └── vocab.txt

CLI Reference

Modes

Mode Command Description
MCP Server leann-dotnet Default. Starts MCP server on stdio
Chunk leann-dotnet --build-passages Split source files into passages
Embed leann-dotnet --build-indexes Compute embeddings for passages
Full Pipeline leann-dotnet --rebuild Chunk + embed in one step
Watch leann-dotnet --watch Auto-sync git repos and rebuild on changes
Setup leann-dotnet --setup [--model ID] [--force] Download ONNX model (~282 MB jina, ~418 MB contriever; one-time per model)

Passage Builder Flags

Flag Description Default
--docs <path> [...] Source directories to chunk (required)
--index-name NAME Index name cwd directory name
--chunk-size N Text chunk size in chars 256
--chunk-overlap N Text chunk overlap 128
--code-chunk-size N Code chunk size in chars 512
--code-chunk-overlap N Code chunk overlap 64
--include-hidden Include hidden files/dirs false
--file-types EXT [EXT...] Whitelist of extensions (e.g. .cs .csproj or .cs,.csproj). When set, overrides the built-in extension defaults (built-in defaults)
--exclude-paths PAT [PAT...] Gitignore-style globs to skip (e.g. "**/Tests/**" "**/Mocks/**"). Supports **, *, ?. Combined with any .gitignore files found in the tree (none)
--no-ast Disable AST-aware code chunking (Roslyn for C#, brace-balanced for TS/JS/Java/C-family) and fall back to the legacy line-based sliding-window chunker false (AST enabled)
--force Overwrite existing passages false

Index Builder Flags

Flag Description Default
--force Rebuild even if embeddings exist false
--index NAME Build only this index all
--exclude NAME [...] Skip specified indexes
--batch-size N Passages per GPU batch 32
--max-tokens N Max token sequence length 512

Watch Mode Flags

Flag Description Default
--interval N Check interval in seconds 300
--repos-config PATH Path to repos.json config .leann/repos.json
--force Force a full rebuild on the first sweep, then resume normal change-detection. Useful for one-shot reindex of every repo without changing the daemon model false
Per-repo settings in repos.json

Each entry supports optional per-repo filters and chunking overrides:

{
  "intervalSeconds": 300,
  "repos": [
    {
      "folder": "C:\\OnBase.NET",
      "gitUrl": "git@github.com:org/OnBase.NET.git",
      "branch": "main",
      "indexName": "onbase-dotnet",
      "enabled": true,

      // Optional — same semantics as the CLI flags
      "fileTypes":        [".cs", ".props", ".targets", ".json"],
      "excludePaths": [
        "**/Tests/**", "**/*.Tests/**", "**/*Tests.cs",
        "**/Mocks/**", "**/third-party-assemblies/**",
        "**/project.assets.json", "**/*.deps.json"
      ],
      "codeChunkSize":    1024,   // overrides default 512
      "codeChunkOverlap": 128,    // overrides default 64
      "useAst":           true    // AST-aware chunking (default true)
    },
    {
      "folder": "C:\\angular-app",
      "gitUrl": "git@github.com:org/angular-app.git",
      "branch": "main",
      "indexName": "angular-app",
      "enabled": true,
      "fileTypes": [".ts", ".html", ".scss"],
      "excludePaths": ["**/node_modules/**", "**/dist/**", "**/*.spec.ts"]
    }
  ]
}

All per-repo fields are optional — existing repos.json files keep working unchanged.

AST-Aware Code Chunking

Starting in 1.0.15, code files are chunked by language structure rather than by raw character windows. This dramatically improves search relevance by ensuring each passage is a complete logical unit (a method, a property, a function) instead of a half-method that happens to start mid-line.

Language(s) Strategy Notes
C# (.cs) Roslyn AST (Microsoft.CodeAnalysis.CSharp) One chunk per method, constructor, property, indexer, operator, event, field, enum, delegate. Nested types and file-scoped namespaces supported. Each chunk is prefixed with a // {namespace}.{type}.{member} context comment so embeddings carry symbolic context.
TypeScript, JavaScript, Java, C/C++, Go, Rust, Kotlin, Scala, Swift, PHP Brace-balanced walker A small state machine that tracks strings, line/block comments, and template-literal interpolation (${...}) to split on top-level {...} blocks without being confused by braces inside strings. No native dependencies.
PDF (.pdf) PdfPig text extraction + page markers Each page's text is extracted via UglyToad.PdfPig and joined with \n\n--- Page N ---\n\n separators. The prose chunker then splits on those paragraph breaks, so chunks naturally fall on page boundaries and search results stay citeable to a specific page. Passages emit source_type=pdf metadata. See PDF Support for limitations.
Markdown, JSON, YAML, plain text, etc. Sliding-window character chunker (legacy) Unchanged from previous releases.

After chunking, every passage (regardless of strategy) goes through a quality filter that drops:

  • Trimmed length < 20 chars (e.g. lone } lines)
  • Punctuation ratio > 70% (e.g. } } } } } or ;;; ; ;;;)
  • A run of ≥ 200 base64 characters ([A-Za-z0-9+/]{200,}) — strips PDF/image blobs accidentally embedded in source
  • An underscore run > 10 (e.g. __________)

The filter is the reason that, after upgrading, you'll typically see fewer passages in the index than before (1-3% drop is normal) — the eliminated chunks were noise that previously dominated unrelated queries.

Opting out

If a Roslyn parse error or an exotic file format causes problems on your repo, fall back to the legacy chunker:

# Per-build (CLI flag)
leann-dotnet --build-passages --docs C:\repo --no-ast

# Per-repo (repos.json)
{
  "folder": "C:\\repo",
  "useAst": false
}

The CLI also prints which mode is active in its startup banner:

LEANN Passage Builder
  ...
  Code chunk: 512 (overlap 64)
  AST chunk:  enabled (Roslyn for C#, brace-balanced for C-family)
  File types: ...

PDF Support

Starting in 2.2.0, .pdf files are first-class citizens of the index alongside source code and Markdown.

How it works

  • Files are extracted page-by-page via UglyToad.PdfPig (pure managed .NET, MIT licensed, zero native deps).
  • Pages are joined with \n\n--- Page N ---\n\n markers so the prose chunker splits on paragraph breaks, keeping each chunk citeable to a specific page.
  • Every PDF passage carries source_type: "pdf" in its metadata, so MCP search consumers can filter or visually distinguish PDF results from code/Markdown.

What works

  • Text-based PDFs (technical docs, runbooks, design specs, API references, exported Word/HTML output).
  • Multi-column layouts (best-effort — see PdfPig docs for the underlying letter-positioning heuristics).
  • Mixed indexes with PDFs, source code, and Markdown in the same --docs directory.

What does NOT work (yet)

  • Scanned / image-only PDFs — these contain no embedded text. PdfPig will return zero text for affected pages and the file will be indexed as an empty document. There is no built-in OCR. If you need OCR, pre-extract with a tool like Tesseract or pdftotext (Poppler) and index the resulting .txt/.md instead.
  • Encrypted / password-protected PDFs — these are skipped with a warn-level log message; the build continues with the remaining files.
  • Corrupt PDFs — same skip-with-warning behavior. One bad file in a 5,000-file repo will not abort the run.
# Mixed code + PDF index
leann-dotnet --rebuild --docs C:\projects\my-app C:\docs\architecture --index-name my-app

Environment Variables

Variable Description Default
LEANN_DATA_ROOT Directory containing .leann/indexes/ Current working directory
LEANN_MODEL Default model id for setup, passage building, rebuilds, and watch-mode rebuilds when no --model flag is given. Not a query-time override — query routing is automatic per index. jinaai/jina-embeddings-v2-base-code
LEANN_MODEL_DIR Override the model directory location (rarely needed; computed from the user profile + sanitized model id by default) ~/.leann/models/<sanitized-id>
LEANN_FORCE_CPU Set to 1 or true to disable GPU acceleration (GPU enabled)

GPU Acceleration

Platform Provider Automatic
Windows DirectML (any GPU) ✅ Yes
macOS CoreML (Apple Silicon GPU + Neural Engine) ✅ Yes
Linux CPU only

GPU is used for both indexing and search query embedding. For MCP server use (search only), you can set LEANN_FORCE_CPU=1 to free your GPU — single query embedding is fast on CPU.

Examples

# Index a single repo
leann-dotnet --rebuild --docs ~/projects/my-app --index-name my-app

# Index multiple directories into one index
leann-dotnet --build-passages --docs ~/proj/frontend ~/proj/backend --index-name my-app

# Index only specific file types (e.g. C# source only)
leann-dotnet --rebuild --docs ./my-repo --index-name my-repo \
  --file-types .cs,.csproj,.sln,.props,.targets

# Skip test projects, mocks, fixtures (gitignore-style globs)
leann-dotnet --rebuild --docs ./my-repo --index-name my-repo \
  --file-types .cs,.csproj,.sln,.props,.targets \
  --exclude-paths "**/Tests/**" "**/*.Tests/**" "**/Mocks/**" "**/*Test.cs" "**/*Tests.cs"

# Rebuild all embeddings with smaller batches (low VRAM GPU)
leann-dotnet --build-indexes --force --batch-size 8

# Rebuild everything except one large index
leann-dotnet --build-indexes --exclude large-mono-repo

# Use shorter token sequences for faster indexing (slight quality trade-off)
leann-dotnet --build-indexes --force --max-tokens 256

# Auto-watch repos for changes
leann-dotnet --watch --interval 120

# One-shot full reindex of every watched repo, then keep watching incrementally
leann-dotnet --watch --force

Tuning Guide

Chunk Size vs. Batch Size vs. Max Tokens

These three settings control different parts of the pipeline:

Setting What It Controls CPU/GPU When to Change
--chunk-size Characters per text passage CPU (chunking) Adjust search granularity
--code-chunk-size Characters per code passage CPU (chunking) Adjust code search granularity
--batch-size Passages processed per GPU call GPU (embedding) Match to your VRAM
--max-tokens Token sequence length per passage GPU (embedding) Trade speed vs. context

Chunk Size (search quality)

Chunk size controls how much context each passage contains. This is independent of GPU power.

  • Smaller chunks (128-256 chars) → more precise search hits, less context per result
  • Larger chunks (512-1024 chars) → more context per result, but may dilute relevance
  • Code chunks default larger (512) because functions/methods need more context than prose

Note: Passages longer than --max-tokens (default 512 tokens ≈ ~2000 chars) are truncated during embedding. Making chunks larger than ~2000 chars wastes disk without improving search.

Batch Size (GPU utilization)

Batch size determines how many passages are embedded simultaneously. This is where GPU VRAM matters.

GPU VRAM Recommended --batch-size
4 GB 32 (default)
8 GB 64
12+ GB 128
# RTX 3500 Ada (12 GB) — crank up the batch size
leann-dotnet --rebuild --docs ./my-repo --index-name my-repo --batch-size 128

# Low-VRAM GPU or integrated graphics
leann-dotnet --rebuild --docs ./my-repo --index-name my-repo --batch-size 8

Max Tokens (speed vs. context)

The contriever model processes up to 512 tokens per passage. Lowering this speeds up embedding (attention is O(n²)) at the cost of truncating longer passages.

# Faster indexing, slight quality trade-off on long passages
leann-dotnet --build-indexes --max-tokens 256

# Full context (default)
leann-dotnet --build-indexes --max-tokens 512

Performance

Embedding throughput on NVIDIA RTX A1000 (4GB VRAM):

Passage Type Avg Tokens Throughput
Short text/docs ~100 150-175 passages/s
Long code (C#) ~300 8-20 passages/s

Optimizations included:

  • Length-sorted batching (groups similar-length passages to minimize padding waste)
  • Bulk file writes (single I/O call for embedding output, critical for network drives)
  • Configurable --max-tokens to reduce O(n²) attention cost on long passages

Platform Support

Platform GPU Provider Accelerator
Windows x64 DirectML NVIDIA, AMD, Intel Arc
macOS ARM64 CoreML Apple Silicon GPU + Neural Engine
Linux x64 CPU (GPU EPs can be added)

GPU support is automatic with graceful fallback — if the GPU provider isn't available, it logs a warning and uses CPU.

Building & Publishing

# Build
dotnet build

# Test
dotnet test

# Publish self-contained binary
dotnet publish src/LeannMcp -r win-x64 --self-contained -c Release -o publish/win-x64

# macOS
dotnet publish src/LeannMcp -r osx-arm64 --self-contained -c Release -o publish/osx-arm64

# Install as dotnet tool (framework-dependent)
dotnet pack src/LeannMcp -c Release
dotnet tool install --global --add-source src/LeannMcp/bin/Release leann-dotnet

Troubleshooting

Problem Solution
"No ONNX model found" Run leann-dotnet --setup (downloads the active model). The model directory is ~/.leann/models/<sanitized-model-id>/.
"Pre-computed embeddings not found" Run leann-dotnet --build-indexes
IndexCompatibility: refusing index ... built with <other-model> (Pre-v2.4.0 only.) As of v2.4.0 the server loads the manifest's model automatically — upgrade if you still see this message. The remaining cause is a dimension mismatch, which means the index is corrupt; rebuild with leann-dotnet --rebuild ....
"DirectML not available" Falls back to CPU automatically. Update GPU drivers.
Slow first search Call leann_warmup to pre-load the model
Out of GPU memory (4 GB VRAM) Use --batch-size 8 and/or --max-tokens 256. Set LEANN_FORCE_CPU=1 if it still OOMs — single-query search is fast on CPU.
Network drive writes slow Already fixed — uses bulk writes. Update to latest build.

Migrating from 1.0.x (contriever-only) to 1.0.16+ (jina default)

  1. dotnet tool update -g leann-dotnet
  2. leann-dotnet --setup (downloads jina; existing contriever model is not deleted).
  3. Rebuild your indexes: leann-dotnet --rebuild --docs <repo> --index-name <name> — required because index files record the model that built them and the new compatibility guard refuses cross-model loads.
  4. Optional: keep using contriever by setting LEANN_MODEL=facebook/contriever (env var) or passing --model facebook/contriever to --setup/--rebuild.

License

MIT — see LICENSE

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

Version Downloads Last Updated
2.5.6 116 5/8/2026
2.5.5 116 4/26/2026
2.1.0 100 4/25/2026
2.0.0 99 4/25/2026