JMESPath Filtering on List Tools¶
Every *_list_* tool across every service accepts an optional query parameter that takes a JMESPath expression. The expression is applied client-side, after the API call returns — so it filters and projects the results without changing what the Zscaler API sees.
The use case¶
Tenants with thousands of locations, hundreds of policy rules, or large user populations return paginated results that an agent has to walk. JMESPath lets the operator (or the agent acting on their behalf) narrow the result set before it lands in the model’s context.
A concrete example. Without filtering, listing every ZIA location returns the full page:
zia_list_locations(page=1, page_size=100)
→ [{id: ..., name: "HQ", country: "USA", ...}, {id: ..., name: "Frankfurt", ...}, …]
With a JMESPath expression:
zia_list_locations(query="[?country=='USA'].{id: id, name: name}")
→ [{id: 123, name: "HQ"}, {id: 124, name: "Boston"}, …]
The agent gets exactly the fields it needs, scoped to exactly the rows that match.
Expression syntax¶
Standard JMESPath syntax applies. Field names use snake_case because the Zscaler SDK converts camelCase API responses to snake_case before the expression runs.
Common patterns:
Goal |
Expression |
Service example |
|---|---|---|
Filter rows |
|
|
Project fields |
|
|
Filter + project |
|
|
Count rows |
|
|
Contains substring |
|
|
First match |
|
|
Note
Booleans in JMESPath are backtick-quoted — write [?enabled==\`true\`] and [?enabled==\`false\`]. That’s a JMESPath syntax quirk, not a mistake.
Service-specific examples¶
Service |
Expression |
|---|---|
ZIA |
|
ZPA |
|
ZDX |
|
ZCC |
|
ZMS |
|
EASM |
|
For ZMS specifically, the GraphQL response is wrapped in a nodes[] + page_info envelope. The JMESPath expression starts inside that envelope, so use nodes[?...] instead of [?...].
How the agent should use this¶
The user is asking a business question. The JMESPath plumbing is internal optimization. Never narrate it.
Plain-language answers only. Translate tool output into the answer the admin actually wanted.
User: “How many ZIA DNS rules exist?”
Agent: “There are 19 ZIA DNS firewall rules in the tenant.”
❌ Do not say “The JMESPath ``length(@)`` returned 19.”
User: “List the names of my SSL inspection rules”
Agent lists the names directly.
❌ Do not mention “I projected ``[].name``.”*
The query parameter is a tool, not a presentation device.
Invalid expressions¶
A malformed JMESPath expression returns a structured error response instead of crashing:
zia_list_locations(query="not a valid expression")
→ [{"error": "Invalid JMESPath expression: ..."}]
The agent should treat this like any other tool error — surface a plain-language version to the user and stop, don’t retry.
Implementation¶
The shared helper lives at zscaler_mcp/common/jmespath_utils.py:
apply_jmespath(data, expression)— used by non-ZMS list tools (where the API result is a list of dicts).ZMS list tools use a dedicated wrapper in
zscaler_mcp/tools/zms/__init__.pythat preserves thenodes[]+page_infoenvelope so expressions can target either the envelope or the data.
When query is None, results pass through unchanged — full backward compatibility with callers that don’t use the parameter.
Return types¶
JMESPath expressions can produce scalars (length(@)), differently-shaped lists ([*].name), or filtered subsets of the original shape. Tools that accept query declare a permissive return type (Any) so the MCP / Pydantic output validator accepts whichever shape the expression produces.
If you write a tool that accepts query, never declare its return type as List[dict] / List[str] — that causes the validator to reject expressions like length(@) (which returns an int).
See also¶
Zscaler Integrations MCP Server Tools — the full list of tools that accept
query.JMESPath documentation — the upstream syntax reference.
Tool-Call Audit Logging — the audit log captures the
queryvalue (redacted if it contains sensitive substrings).