context-sniper-mcp
A tiny local MCP server that indexes a repository into line-range chunks andserves compact "evidence packets" (file + lines + score + snippet) instead ofdumping whole files into the model's context. Meant to be shared byClaude Code and Codex to cut token usage when exploring or debugging a repo.
No database — the index is a single JSON file written to<repo>/.context-index/chunks.json.
Tools
- index_repo
{ root }— scansroot(skippingnode_modules,.git,dist,build,.next,coverage,.venv,target), chunks supportedfiles (ts tsx js jsx py java go rs md json yml yaml toml) into ~80-linesliding windows (max 120 lines/chunk), and writesroot/.context-index/chunks.json. Dependency lockfiles(package-lock.json,pnpm-lock.yaml,npm-shrinkwrap.json), minifiedbundles (*.min.js,*.bundle.js), and files larger than 512 KB areskipped so the index stays focused on real source. - search_code
{ root, query, topK? }— loads the chunk index and scoresit againstquerywith a BM25-style ranker. Returns up totopK(default5) hits, each withFILE,LINES,SCORE, and a snippet capped at 4000characters. If no index exists yet, it tells you to runindex_repofirst. - read_snippet
{ root, path, startLine, endLine }— reads an explicitline range from one file insideroot. Capped at 300 lines per call (longerranges are truncated with a note).pathis resolved and checked againstroot; anything that would escaperootis refused. - run_test_filtered
{ root, command }— runs one of a fixed allowlist ofcommands (npm_test→npm test,pnpm_test→pnpm test,pytest→pytest -q) viaspawnwithshell: false— no arbitrary shell execution.Captures stdout/stderr, keeps only lines matchingerror|failed|failure|assert|expected|received|tracebackor a test-filepath, tail-capped at 120 lines. If nothing matches, falls back to the last80 raw output lines. Always reports the resolved command and exit code.
There is intentionally no run_shell or equivalent — only the four toolsabove are exposed.
Installation
For installation and client configuration, see INSTALL.md.
CLI usage
The same binary also works as a plain shell command — pass a subcommand and itruns once and exits, instead of starting the MCP stdio server:
context-sniper-mcp index <root>
context-sniper-mcp search <root> <query...> [--top-k N]
context-sniper-mcp read <root> <path> <startLine> <endLine>
context-sniper-mcp test <root> <npm_test|pnpm_test|pytest> [--timeout ms]
context-sniper-mcp help
context-sniper-mcp --version
Each subcommand maps 1:1 to the tool of the same purpose above and prints thesame human-readable output. test exits with the underlying test command'sown exit code (or 124 on timeout), so it's usable in scripts, e.g.context-sniper-mcp test . npm_test || echo "tests failed". Running thebinary with no arguments still starts the MCP stdio server.
Recommended usage
- Call index_repo once per repo (and again after large changes) beforedoing anything else.
- Before fixing a bug, prefer search_code over opening files — searchfor the symptom, error message, or function name first.
- Don't read a whole file up front. Let the evidence packet from
search_codetell you where to look. - If a test fails, use run_test_filtered to get the trimmedfailure output instead of piping raw test-runner logs into context.
- If a returned snippet cuts off before the context you need, useread_snippet with a widened
startLine/endLinerange around it(still capped at 300 lines per call) rather than reading the entire file.
Project layout
context-sniper-mcp/
├── package.json
├── tsconfig.json
├── src/
│ ├── index.ts # MCP server wiring + tool registration; dispatches to cli.ts
│ ├── cli.ts # shell subcommands (index/search/read/test) for direct CLI use
│ ├── repo-index.ts # scanning, chunking, safe path resolution, index I/O
│ ├── search.ts # BM25-style scoring + evidence packet formatting
│ ├── snippets.ts # bounded, path-safe line-range reads
│ └── output-gate.ts # allowlisted test runner + output filtering
├── build/ # compiled output (npm run build)
├── INSTALL.md
├── HUMAN.md
└── README.md
License
MIT