Skip to main content
Glama

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 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:

    {
      "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

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/manik-prakash/narrow-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server