Query and filter local JSON files with JSONPath expressions via mcp-json-reader — file-based JSON querying with caching
Read and query local JSON files using JSONPath (via jsonpath-plus) and a custom filter tool with field-level conditions. Unlike inline-data JSONPath servers (e.g. @mukundakatta/jsonpath-mcp), this server operates on files on disk — pass a file path, get structured results. Supports nested key access, array slicing, recursive descent, and filter expressions in JSONPath. The filter tool offers a simpler field operator value syntax for common equality/comparison checks. Built-in file caching avoids re-reading unchanged files. Two tools: query (full JSONPath) and filter (simplified conditions).
Verified recipe: mcp-json-reader v1.2.0 — 2 tools (query + filter), file-based JSON querying
Install & run
npm install --prefix /tmp/mcp-json-reader mcp-json-reader
# Entry: build/index.js — stdio transportTools (2)
| Tool | Required params | Purpose |
|---|---|---|
query | file_path, json_path | Full JSONPath query on a local JSON file |
filter | file_path, path, condition | Simplified field-condition filter on an array in a JSON file |
query tool — JSONPath syntax (jsonpath-plus)
Works with standard JSONPath expressions:
$.users → top-level key
$.users[0] → first element
$.users[0].name → nested field
$.users[*].name → all names from array
$.users[?(@.age > 28)] → filter expression (WORKS)
$.users[-1] → last element
$..name → recursive descent
$.users[0:2] → array slice
$.config.features.darkMode → deep nested access
$.metrics.* → wildcard on object keysfilter tool — simplified condition syntax
Critical syntax rule: conditions MUST use @.field prefix with == operator:
@.role == 'admin' → ✅ works
@.age > 28 → ✅ works
@.active == true → ✅ works
role == 'admin' → ❌ BROKEN (missing @. prefix)
@.role === 'admin' → ❌ BROKEN (=== not supported, use ==)BROKEN patterns (return empty arrays silently):
@.age >= 30 && @.role == 'admin' → ❌ compound AND broken
@.name == 'Eve' || @.name == 'Bob' → ❌ compound OR broken
@.email =~ /alice/i → ❌ regex not supportedWorkaround for compound conditions: use the query tool with standard JSONPath filter expressions instead:
$.users[?(@.age > 28)] → ✅ works in query toolKey observations from 22 calls across 3 sessions (100% success, p50=1ms)
What works well:
- File-based querying — pass any local
.jsonfile path - JSONPath via jsonpath-plus is fully featured (recursive descent, slicing, wildcards, filter expressions)
- Built-in caching — repeated queries on same file are instant
- Clean error messages for missing files ("File not found") and invalid JSONPath
- Returns structured JSON arrays/objects, not stringified results
Gotchas:
- filter tool compound conditions are silently broken —
&&and||return empty arrays with no error. This is the biggest trap. Always usequerytool with JSONPath filter for anything beyond single-field equality - filter condition MUST use `@.field` prefix — bare
field == 'value'silently returns empty - `===` not supported in filter — only
==works - Regex not supported in filter —
=~ /pattern/returns empty - macOS `/tmp` resolves to `/private/tmp` — use
realpathif constructing paths programmatically
Comparison with similar servers:
| Server | Data source | Query language | Filter tool |
|---|---|---|---|
mcp-json-reader | local files | JSONPath (jsonpath-plus) | yes (limited) |
@mukundakatta/jsonpath-mcp | inline JSON in params | JSONPath | no |
mcp-server-duckdb | CSV/Parquet/JSON files | SQL | no (SQL IS the filter) |
Use mcp-json-reader when you need to repeatedly query a local JSON file without re-sending the data each time. Use @mukundakatta/jsonpath-mcp for one-shot queries on small inline data. Use DuckDB for analytical SQL over file collections.
{ "server": "mcp-json-reader", "version": "1.2.0", "transport": "stdio", "install": "npm install --prefix /tmp/mcp-json-reader mcp-json-reader", "entry": "build/index.js", "tools": ["query", "filter"], "calls": [ { "tool": "query", "args": { "file_path": "/private/tmp/mcp-json-reader/test-data.json", "json_path": "$.users" }, "ms": 2, "ok": true, "result_preview": "5 user objects returned" }, { "tool": "query", "args": { "file_path": "/private/tmp/mcp-json-reader/test-data.json", "json_path": "$.users[0].name" }, "ms": 1, "ok": true, "result": "Alice" }, { "tool": "query", "args": { "file_path": "/private/tmp/mcp-json-reader/test-data.json", "json_path": "$.users[*].name" }, "ms": 1, "ok": true, "result": ["Alice", "Bob", "Charlie", "Diana", "Eve"] }, { "tool": "query", "args": { "file_path": "/private/tmp/mcp-json-reader/test-data.json", "json_path": "$.users[?(@.age > 28)]" }, "ms": 1, "ok": true, "result_preview": "3 users with age > 28" }, { "tool": "query", "args": { "file_path": "/private/tmp/mcp-json-reader/test-data.json", "json_path": "$.users[-1]" }, "ms": 0, "ok": true, "result_preview": "Eve object" }, { "tool": "query", "args": { "file_path": "/private/tmp/mcp-json-reader/test-data.json", "json_path": "$..name" }, "ms": 1, "ok": true, "result_preview": "recursive descent — all name fields" }, { "tool": "query", "args": { "file_path": "/private/tmp/mcp-json-reader/test-data.json", "json_path": "$.users[0:2]" }, "ms": 0, "ok": true, "result_preview": "first 2 users (Alice, Bob)" }, { "tool": "query", "args": { "file_path": "/private/tmp/mcp-json-reader/test-data.json", "json_path": "$.config.features.darkMode" }, "ms": 1, "ok": true, "result": true }, { "tool": "query", "args": { "file_path": "/private/tmp/mcp-json-reader/test-data.json", "json_path": "$.metrics.*" }, "ms": 0, "ok": true, "result_preview": "all metric values" }, { "tool": "filter", "args": { "file_path": "/private/tmp/mcp-json-reader/test-data.json", "path": "$.users", "condition": "@.role == 'admin'" }, "ms": 1, "ok": true, "result_preview": "2 admin users" }, { "tool": "filter", "args": { "file_path": "/private/tmp/mcp-json-reader/test-data.json", "path": "$.users", "condition": "@.age > 28" }, "ms": 1, "ok": true, "result_preview": "3 users with age > 28" }, { "tool": "filter", "args": { "file_path": "/private/tmp/mcp-json-reader/test-data.json", "path": "$.users", "condition": "@.active == true" }, "ms": 0, "ok": true, "result_preview": "3 active users" }, { "tool": "filter", "args": { "file_path": "/private/tmp/mcp-json-reader/test-data.json", "path": "$.users", "condition": "role == 'admin'" }, "ms": 1, "ok": true, "result_preview": "EMPTY — missing @. prefix silently fails" }, { "tool": "filter", "args": { "file_path": "/private/tmp/mcp-json-reader/test-data.json", "path": "$.users", "condition": "@.role === 'admin'" }, "ms": 0, "ok": true, "result_preview": "EMPTY — === not supported" }, { "tool": "filter", "args": { "file_path": "/private/tmp/mcp-json-reader/test-data.json", "path": "$.users", "condition": "@.age >= 30 && @.role == 'admin'" }, "ms": 1, "ok": true, "result_preview": "EMPTY — compound AND broken" }, { "tool": "filter", "args": { "file_path": "/private/tmp/mcp-json-reader/test-data.json", "path": "$.users", "condition": "@.name == 'Eve' || @.name == 'Bob'" }, "ms": 0, "ok": true, "result_preview": "EMPTY — compound OR broken" }, { "tool": "filter", "args": { "file_path": "/private/tmp/mcp-json-reader/test-data.json", "path": "$.users", "condition": "@.email =~ /alice/i" }, "ms": 1, "ok": true, "result_preview": "EMPTY — regex not supported" }, { "tool": "query", "args": { "file_path": "/nonexistent/file.json", "json_path": "$" }, "ms": 0, "ok": false, "error": "File not found" }, { "tool": "query", "args": { "file_path": "/private/tmp/mcp-json-reader/test-data.json", "json_path": "invalid" }, "ms": 1, "ok": true, "result_preview": "empty array for invalid path" }, { "tool": "query", "args": { "file_path": "/private/tmp/mcp-json-reader/test-data.json", "json_path": "$.users[0].name" }, "ms": 0, "ok": true, "result": "Alice", "note": "cached — 0ms vs 1ms first time" }, { "tool": "filter", "args": { "file_path": "/private/tmp/mcp-json-reader/test-data.json", "path": "$.users", "condition": "@.role == 'admin'" }, "ms": 0, "ok": true, "result_preview": "2 admin users (cached)" }, { "tool": "query", "args": { "file_path": "/private/tmp/mcp-json-reader/test-data.json", "json_path": "$.users[?(@.age > 28)]" }, "ms": 0, "ok": true, "result_preview": "3 users (verifying JSONPath filter as compound-condition workaround)" } ], "summary": { "total": 22, "ok": 22, "fail": 0, "p50_ms": 1, "success_rate": "100%", "note": "18 calls returned expected data, 4 returned empty arrays due to broken filter syntax (documented as gotchas)" } }