Skip to main content
Glama

# repolens

Structure-aware code retrieval for AI coding agents. An MCP server that indexes a repository into declaration-level chunks and returns the smallest slice that answers a question 鈥?instead of making the agent read whole files.

ci node runtime dependencies

repolens answering a question in the terminal

Why

An agent asked "where is the retry backoff computed?" will grep, get 30 hits, and read four whole files 鈥?thousands of tokens to find 40 lines. That is the expensive part of agentic coding: not reasoning, but feeding the context window.

repolens indexes a repo once, then answers at the declaration level: chunk.js:311-334 路 findBlockEnd(), body included.

Related MCP server: sens

Benchmark

Three third-party repositories, three languages, questions written from each project's docs and public API 鈥?not from its source. 12 questions each, 36 total. Gold answers are the chunks that implement the behaviour.

repository

language

queries

top-1

top-5

MRR

tokens/answer

grep + read

saving

express

JavaScript (CJS)

12

91.7%

100%

0.958

835

2,952

71.7%

zod

TypeScript

12

66.7%

75.0%

0.694

804

4,249

81.1%

httpx

Python

12

66.7%

91.7%

0.792

1,071

41,950

97.4%

all

36

75.0%

88.9%

0.815

903

16,384

94.5%

The baseline is what an agent does today without an index: grep for the terms, then read every file that matched (capped at 5 files). Tokens are estimated at 3.6 chars/token for source.

Reproduce it:

git clone --depth 1 https://github.com/expressjs/express ../.corpus/express
git clone --depth 1 https://github.com/colinhacks/zod       ../.corpus/zod
git clone --depth 1 https://github.com/encode/httpx         ../.corpus/httpx
node eval/multirepo.js --reindex          # prints the table above, writes eval/results.json

The per-question rows 鈥?including the four misses 鈥?are in eval/results.json. The harness is checked in; the numbers are not screenshots.

Chunk coverage

Recall is bounded by what the chunker understands. Coverage = share of non-blank lines that live inside at least one chunk (files 鈮?15 lines):

repository

code lines

coverage

express

17,553

82.6%

zod

91,701

84.5%

httpx

14,376

86.9%

What is not covered is mostly unretrievable by construction: re-export files (index.js that only re-exports ./lib), config files, and script-style examples where the logic sits in anonymous callbacks. node tools/coverage.js <db> reports the worst files so chunker work is driven by data, not intuition.

How it works

repo 鈹€鈹€鈻?scan 鈹€鈹€鈻?chunk 鈹€鈹€鈻?index (SQLite + FTS5) 鈹€鈹€鈻?rerank 鈹€鈹€鈻?pack 鈹€鈹€鈻?MCP client
          鈹?       鈹?                 鈹?                 鈹?        鈹?       extensions  signature,     BM25 over          symbol identity  token
       + ignore    doc, body      identifier terms   + coverage +     budget
                                                      test/code prior
  1. Scan 鈥?walks the tree, skips node_modules/build output, caps file size.

  2. Chunk 鈥?one declaration per chunk: signature, doc comment, line range, body. Handles nested declarations, destructuring parameters, regex literals, CommonJS (exports.x = function), describe/it blocks, and Python's indentation rules.

  3. Index 鈥?SQLite FTS5, external-content table, identifier-split tokens in four columns (symbol / path / doc / body) with per-column BM25 weights.

  4. Rerank 鈥?BM25 only selects candidates; ranking uses symbol identity, how many of the question's concepts a chunk contains, parent names, path segments, and a definition-over-test prior.

  5. Pack 鈥?fills a token budget, degrading from full body to signature to omission.

Quick start

Requires Node.js 鈮?24 (or 22.5+ with --experimental-sqlite). No dependencies, no build step.

git clone https://github.com/wujiuyi55-sketch/repolens
cd repolens
node src/cli.js index .                 # build .repolens/index.db
node src/cli.js search "token budget"   # query from the terminal
node --test                             # 33 tests
node tools/coverage.js .repolens/index.db

Index any repository:

node src/cli.js index ~/code/express --db /tmp/express.db
node src/cli.js search "middleware stack" --db /tmp/express.db

Re-running index is incremental: unchanged files (mtime + size) are skipped.

Use it from an MCP client

{
  "mcpServers": {
    "repolens": {
      "command": "node",
      "args": ["/absolute/path/to/repolens/src/cli.js", "mcp", "--db", "/absolute/path/to/repo/.repolens/index.db"]
    }
  }
}

Tools

Tool

Question it answers

search_code

where does X live? (natural language or identifier)

get_symbol

show me X in full

find_references

who else touches X?

index_stats

is this repo indexed, and how well?

search_code takes budgetTokens (default 1200) so the caller decides how much context to spend.

Layout

src/
  scan.js      walk a repo, decide what is source
  chunk.js     file 鈫?declarations (JS/TS/TSX, Python)
  tokenize.js  identifier-aware tokenizer, shared by index and query
  store.js     SQLite schema, FTS5 external-content index
  indexer.js   build/refresh an index (used by CLI and eval)
  search.js    candidate retrieval + reranking
  pack.js      token-budget packing with graceful degradation
  tools.js     MCP tool definitions
  mcp.js       JSON-RPC over stdio
  cli.js       index | search | mcp | stats
eval/
  queries/*.jsonl   per-repository questions with gold answers
  lib.js            shared metrics (top-k, MRR, token cost)
  multirepo.js      the benchmark above
tools/coverage.js   "which files did the chunker fail to understand?"

Design notes

  • Zero runtime dependencies. node:sqlite (built in since Node 22.5) plus a hand-written JSON-RPC stdio transport. Nothing to audit, nothing to break on install.

  • Chunking is heuristic, not AST-based. A tree-sitter backend would be more precise; the chunker is a single module with a stable Chunk contract, so swapping it does not touch the index or query layer. See docs/DESIGN.md.

  • One tokenizer for both sides. Index and query share splitWords, so getUserById and "user by id" meet in the same vocabulary. This symmetry matters more than any scoring tweak.

  • BM25 for recall, rules for precision. Raw bm25 spans an order of magnitude, so it is normalised into a 0鈥?0 band and the explicit signals decide the order.

Limitations

  • JS/TS/JSX/Python only; JSX-heavy files may fall back to signature-only chunks.

  • No cross-file symbol resolution: find_references is lexical.

  • zod's score is lower than the others on purpose 鈥?its v4 code is largely generated one-line wrappers and type declarations, which is a known weak spot for a lexical chunker and the main argument for tree-sitter.

  • Index lives beside the repo; there is no shared daemon or file watcher.

  • node:sqlite is still marked experimental upstream 鈥?pin your Node version.

Roadmap

  • tree-sitter chunker behind the same Chunk interface

  • call/import graph, so find_references can be structural

  • file-watcher mode (repolens watch)

  • re-tune BM25F and reranker weights on a corpus 100脳 larger

License

MIT

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    A
    maintenance
    Provides MCP tools for Claude Code to query a project's codebase via a compact index, reducing token usage and context bloat by enabling symbol lookup, usage tracking, and dead-code detection.
    3 npm
    4
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    Provides a local-first code indexing and search engine for coding agents via MCP, enabling precise codebase queries, symbol lookup, and freshness-aware retrieval.
    -
  • A
    license
    Not graded
    quality
    A
    maintenance
    Provides coding agents with searchable codebase context through an MCP server, enabling hybrid BM25 and semantic search, symbol graph navigation, and dependency mapping over an incrementally maintained repository index.
    164 PyPI
    89
    Apache 2.0