jpx

JMESPath + 400 functions.
A jq alternative you can actually remember.

Josh Rotenberg

JMESPath syntax — the whole language

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

Query language for JSON
that reads like what you want

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.

32 categories. 400+ functions.

string array object math datetime hash encoding regex geo fuzzy network semver validation expression text phonetic url color duration ids

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"

The jpx ecosystem

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.

Why give this to an agent?

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

It scales. It's correct.

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.

Deterministic: the query engine processes the data, not the LLM.
No hallucinated values. No miscounted arrays. Same result every time.

Tool discovery across servers

# 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.

Get started

# 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