narrow-mcp
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:
Runs deterministic narrowing first (grep-style search, structural parsing per file type, sampling) -- free, fast, zero LLM cost.
If that alone resolves the query with confidence (e.g. an exact CSV "null column" query), returns it directly. No LLM call at all.
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.
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.
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
Install:
pip install narrow-mcpor run it without installing anything, always on the latest version:
uvx narrow-mcpSet 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-...Register it with Claude Code:
claude mcp add narrow-mcp -- uvx narrow-mcp(Verify current
claude mcp addflag syntax withclaude mcp add --helpfirst — CLI flags change across releases.)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 |
|
|
|
|
|
|
|
|
|
(none) |
|
|
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-modelsThen 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(default3.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
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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