Josh Rotenberg
Access
foo.bar.baz # nested field
[0] [-1] [3:5] # index, last, slice
[*].name # list projection
*.values # object wildcard
[][] # flatten nested arrays
Multi-select
{n: name, a: age} # hash
[name, age] # list
Pipe
[*].score | sort(@) | [-1]
Literals & refs
`42` `"str"` `true` `[1,2]` # JSON literals
'raw string' # unquoted string
&field # expression reference
Filter
[?age > `30`]
[?active && score >= `90`]
[?!archived]
[?contains(name, 'test')]
Functions
length(@) sort(@) reverse(@)
sort_by(@, &age) max_by(@, &score)
join(', ', @) contains(@, 'x')
keys(@) values(@) merge(a, b)
Let expressions (JEP-18)
let $lat = `40.42`,
$lon = `-3.70` in
[*].{dist: geo_distance_km(
$lat, $lon, lat, lon)}
Current node
@ # the value being evaluated
curl -s https://api.github.com/users/octocat \
| jpx '{
login: upper(login),
joined: format_date(parse_date(created_at), `"%B %Y"`),
repos: public_repos
}'
{"login": "OCTOCAT", "joined": "January 2011", "repos": 8}
JMESPath is an RFC spec. jpx adds 400+ functions on top — dates, hashing, geo, fuzzy matching, and more.
sha256('secret') → "2bb80d5..."
geo_distance_km(40.42, -3.70, 37.77, -122.42) → 9318 Madrid → SF
jaro_winkler('Josh', 'Joshua') → 0.93
format_date(now(), '%B %d, %Y') → "February 10, 2026"
| jpx | CLI with REPL, streaming, table/CSV output |
| jpx-mcp | MCP server — 29 tools for AI assistants |
| jpx-core | From-scratch JMESPath engine in Rust |
| jpx-engine | Introspection, BM25 search, query store |
| jpx (PyPI) | Python bindings via PyO3 |
MCP server: 29 tools — AI agents can query JSON, discover functions, and build query libraries without the data ever entering the context window.
Without jpx: agent reads full JSON into context
60 KB → 21,469 tokens
With jpx MCP: file stays on disk, agent sends expression, gets result
92 + 263 = 355 tokens — 60x reduction
| Input | Without jpx | With jpx | Reduction |
| 85 earthquakes (60 KB) | 21K tokens | ~200 tokens | ~100x |
| 200 earthquakes (143 KB) | 51K tokens | ~200 tokens | ~250x |
| 100 Nobel laureates (367 KB) | 131K tokens | ~150 tokens | ~850x |
Result size is constant — the output depends on the query, not the input.
# Agent connects to 3 MCP servers — 100+ tools total
# Registers each server's tools with jpx at session start
register_tools({ server: "redisctl", tools: [...65] })
register_tools({ server: "github", tools: [...20] })
register_tools({ server: "deploy", tools: [...18] })
# Now search across all of them with natural language
query_tools("backup database")
→ redisctl:enterprise_database_backup score: 8.4
→ redisctl:enterprise_database_export score: 5.2
BM25 search —
finds "backup" from "snapshot", "export", "persist".
The agent doesn't scan tool lists. It asks jpx.
# Install
brew install joshrotenberg/brew/jpx
# or: cargo install jpx
# Discover functions
jpx --search "hash"
jpx --describe sha256
# Interactive REPL
jpx --repl
# Reusable query libraries
jpx -Q earthquakes.jpx:mag-stats -f data.json
github.com/joshrotenberg/jpx
joshrotenberg.github.io/jpx