Skip to main content
Glama
README.md
# narrow-mcp

<!-- mcp-name: io.github.manik-prakash/narrow-mcp -->

An MCP server that narrows large, low-density non-source files (logs, test
output, CSV, JSON, HTML) down to the verbatim spans relevant to a stated
intent -- verified against the original file, never a summary.

## Why

Coding agents burn context reading large low-density files. A 40k-token log
might hold a few hundred tokens of signal. This tool:

1. Runs deterministic narrowing first (grep-style search, structural
   parsing per file type, sampling) -- free, fast, zero LLM cost.
2. If that alone resolves the query with confidence (e.g. an exact CSV
   "null column" query), returns it directly. No LLM call at all.
3. Otherwise passes only the narrowed candidates (never the raw file) to a
   cheap, fast selector model that returns line ranges and a one-line
   reason -- never prose.
4. Re-reads the chosen line ranges from the **original file on disk** and
   returns that verbatim text. The selector's own words are never trusted
   or returned -- only its line-number coordinates, which get verified.
5. If the selector fails, times out, or returns something invalid, falls
   back to the deterministic candidate set rather than failing outright.

Non-goals: source code retrieval (use LSP/tree-sitter/ast-grep), prose
summarization, local/self-hosted models.

## Quick start

1. **Install:**
   ```
   pip install narrow-mcp
   ```
   or run it without installing anything, always on the latest version:
   ```
   uvx narrow-mcp
   ```

2. **Set one API key.** The provider is auto-detected from whichever you
   set — see [Configuration](#configuration) for the full picker, including
   a completely free option via OpenRouter:
   ```
   export ANTHROPIC_API_KEY=sk-ant-...
   ```

3. **Register it with Claude Code:**
   ```
   claude mcp add narrow-mcp -- uvx narrow-mcp
   ```
   (Verify current `claude mcp add` flag syntax with `claude mcp add --help`
   first — CLI flags change across releases.)

4. **That's it.** You don't call the tool directly — once registered, your
   coding agent sees one tool, `narrow_file(path, intent)`, and decides on
   its own when a large file is worth narrowing instead of reading in full.
   For example, given a CSV and the intent `"which rows have null
   customer_id"`, the agent gets back exactly the matching rows, verified
   against the file on disk, along with how much that saved:
   ```json
   {
     "status": "deterministic",
     "file_type": "csv",
     "spans": [
       {"start_line": 4, "end_line": 4, "text": "3,Carol White,,carol@example.com", "reason": "structured_null", "source": "deterministic"},
       {"start_line": 6, "end_line": 6, "text": "5,Eve Black,,eve@example.com", "reason": "structured_null", "source": "deterministic"},
       {"start_line": 9, "end_line": 9, "text": "8,Heidi Young,,heidi@example.com", "reason": "structured_null", "source": "deterministic"}
     ],
     "metrics": {"original_size_tokens_est": 94, "returned_size_tokens_est": 23, "savings_pct": 75.5, "latency_ms": 1}
   }
   ```
   `status: "deterministic"` means this resolved from the exact CSV query
   alone — no LLM call at all, the most common outcome for well-posed
   queries. `status: "selected"` means the cheap selector model chose the
   spans; `status: "deterministic_fallback"` means the selector was tried
   and failed, so the tool fell back to its deterministic candidates rather
   than returning nothing; `status: "refused"` means the path was a source
   file (use LSP/tree-sitter/ast-grep for those instead).

## Status

v1, single file per call. Four file types: log/build-output, CSV, JSON
(single document or JSONL), HTML.

## Development

```
pip install -e ".[dev]"
pytest
python eval/run_eval.py          # mocked selector, free
python eval/run_eval.py --live   # real selector call, needs an API key (see Configuration)
```

## Configuration

**You only need to set one API key.** The provider is auto-detected from
whichever key is present -- no separate provider/model config required:

| If you set...       | Provider used | Default model                                  |
|----------------------|----------------|-------------------------------------------------|
| `ANTHROPIC_API_KEY`   | `anthropic`    | `claude-haiku-4-5`                              |
| `OPENAI_API_KEY`      | `openai`       | `gpt-5-nano`                                    |
| `OPENROUTER_API_KEY`  | `openrouter`   | `openrouter/free` (see below)                   |
| *(none)*              | `anthropic`    | `claude-haiku-4-5` (calls just always fall back to the deterministic path) |

If more than one key is set, priority is Anthropic > OpenAI > OpenRouter.
Override anything explicitly with the env vars below.

### Using OpenRouter's free models

OpenRouter still requires its own API key even for $0-cost models -- set
`OPENROUTER_API_KEY` and you're done, no other config needed. It defaults to
**`openrouter/free`**, a meta-router that auto-picks among whichever
tool-calling-capable models are currently free, so it never goes stale the
way hardcoding one specific `:free` model name would.

To see the current free-model roster live (it rotates) and pick a specific
one instead of the meta-router:

```
narrow-mcp-list-free-models
```

Then set `NARROW_MCP_SELECTOR_MODEL=<id>` to whichever one you want.

OpenRouter's free tier is rate-limited (20 req/min; 50 req/day, or 1000/day
once the account has $10+ lifetime spend) -- fine for interactive use, worth
knowing about for batch runs.

### All environment variables

- `NARROW_MCP_SELECTOR_PROVIDER` -- `anthropic` | `openai` | `openrouter`.
  Overrides auto-detection.
- `NARROW_MCP_SELECTOR_MODEL` -- overrides the provider's default model.
- `NARROW_MCP_SELECTOR_API_KEY_ENV` -- overrides which env var holds the key.
- `NARROW_MCP_SELECTOR_TIMEOUT_S` (default `3.0`)
- `NARROW_MCP_MAX_CANDIDATE_CHARS`, `NARROW_MCP_MAX_CANDIDATES`,
  `NARROW_MCP_CONTEXT_LINES`, `NARROW_MCP_RIPGREP_PATH`,
  `NARROW_MCP_MAX_JSON_BYTES`

TDQS

A4.7/5.0

Scored across 1 tool

Disambiguation5/5

With only one tool, there is no possibility of an agent selecting the wrong operation from this server. The tool's description also clearly delineates supported file types and explicitly excludes source files.

Naming Consistency4/5

The single name 'narrow_file' follows a clear verb_noun structure and is easy to predict. However, with only one tool there is no larger naming pattern to assess for consistency.

Tool Count3/5

A single tool is at the low end of what feels like a complete server, even though the server is intentionally narrow. The count is not a severe mismatch, but agents have no fallback or related operations available.

Completeness5/5

For the server's stated narrow purpose, the tool covers the entire workflow: it accepts a qualifying file and intent, returns verified verbatim relevant spans, and handles boundary cases by refusing source files. No obvious missing operation remains within this deliberately small scope.

Maintenance

ActivityMaintained
ResponsivenessNo issues