ContextPull · an MCP server and library for document retrieval

Pull, don’t push.

Classic RAG pushes the top-k chunks at the model before it has thought about the question. ContextPull works the way Claude Code works on a codebase: a small index that is always in context, and tools that return exact sections on demand. The model decides what to read, reads it verbatim, and cites it.

The model never receives content it did not ask for.
PyPI npm ci license
Claude Code
claude mcp add contextpull -- uvx --from "contextpull[mcp]" contextpull serve ./docs
Any MCP host, Node or Go
npx -y contextpull serve ./docs/.contextpull/store.sqlite
./contextpull-server serve store.sqlite --http 0.0.0.0:8765   # Go, one static binary
Python library
pip install contextpull
from contextpull import Store, Ops
ops = Ops(Store.open(".contextpull/store.sqlite"))
ops.search("refund window", in_=["policy-2025.md"])
how it works

Index in context. Pointers on request. Text only when asked.

Ingest once into one SQLite file

Markdown, text, Word, Excel, PowerPoint and PDF become heading-aware sections with stable ids like policy-2025.md#3. Tables and code are never split mid-block. Unchanged files are skipped on re-ingest. Zero dependencies.

The index rides along

A token-budgeted table of contents, one line per document, is delivered in the MCP server’s instructions so the model knows what exists before it does anything. Also a tool and a resource.

The model pulls

search returns ids and snippets, never bodies. read returns one section verbatim. grep finds exact codes and flags. neighbours recovers a table header or the next clause. The answer cites ids.

five tools, one definition

Small on purpose.

The same schemas power the Python server, the Node server, the direct-API recipe and the benchmark adapters.

index

Table of contents. Flat with summaries, compact past ~110 documents, hierarchical past ~300; index(prefix) drills down.

search

FTS5 lexical search that keeps identifiers whole; three passes, phrase then all words then any. Optional hybrid with embeddings.

read

One section verbatim with heading path and neighbour ids. Table parts always begin with their header row.

grep

Substring or regex over sections. TX-45 finds TX-4501; embeddings never will.

neighbours

Adjacent sections in document order, plus the table header when an id is part of a split table.

measured, including the part that hurts

The pull pattern is only as good as the model’s willingness to read.

Every claim here is a row in a ragbisect table next to bm25, dense and hybrid, on the same self-built eval set. uv documentation, 603 sections, 221 questions, recall@5.

configrecall@5mrr@5ms / querymodel tokens / querytool calls / query
pull · Claude Code (10-question sample)1.0000.88326,56586,6564.8
hybrid push · dense + bm25, RRF0.9640.8216900
bm25 push0.9230.805500
dense push · text-embedding-3-small0.8510.738366≈150
pull · gpt-5.4-mini, reasoning off0.6150.58516,02310,8912.7

A strong agent searched, read the gold section and cited it on every sampled question, at about $0.30 each. A small model with reasoning off reads the right section when it reads (NDCG 0.96 given a hit) but misses the gold section on 38% of questions. Its own searches surfaced the gold section on 88% of a sample, so the gap is mostly snippets answered from rather than read, and a stricter prompt did not change it. Push retrieval costs milliseconds and no model tokens; pull costs tens of thousands of tokens a question. Both facts stay in the table. An earlier figure of 0.045 for the small model was a benchmark-harness bug, retracted in the testing notes. Full write-up, mistakes included, in the testing notes.

works with what you have

One store file, three ways in.

Open MCP hosts

Claude Code, Claude Desktop, Cursor and others attach the server over stdio; a shared read-only server speaks streamable HTTP. The Python server needs the mcp extra; the Node server needs nothing but Node.

Your own client

Import the Python library, or use tools.json with the Anthropic or OpenAI API directly. MIT licensed, no network calls in the core, so it embeds in air-gapped products. The embedding guide covers ingest, access control and versioning.

Other languages

The SQLite store is the contract. A conformance suite defines correctness; the TypeScript reader and the Go single-binary server both pass it and return byte-identical results to Python over MCP.

Honest measurement

ragbisect, the companion harness, builds an eval set from your corpus in five question shapes and scores each retrieval stage on its own. ContextPull is just another row.