--- name: jumpcloud-directory-search description: Answer aggregate, counting, and cross-resource questions about a JumpCloud organization in a single call using search_api_execute — how many users per group, devices by OS, accounts created in a date range, anything needing a group-by or a filter no list endpoint offers. Use whenever a JumpCloud question contains how many, count, per, by, average, oldest, newest, without, or missing, or when answering it would otherwise mean listing a resource and looping over the results. license: MIT compatibility: Requires the JumpCloud MCP server (https://mcp.jumpcloud.com/v1) connected with an admin account or API key. metadata: author: jumpcloud version: "1.1.0" --- # Searching a JumpCloud directory `search_api_execute` takes a natural-language question, compiles it into a structured query against the organization's own resources, runs it, and returns the result. One call does what would otherwise take a list plus N lookups. It is the highest-leverage tool on the server. Reach for it first on any question that is not a plain listing or a lookup by known ID. ## When to use it **Use it for:** - Counts and aggregations — "how many users per group", "devices by OS version", "average number of apps per user" - Filters no list endpoint exposes — "users created after 2026-01-01 who have never logged in", "devices with an agent older than the current release" - Negative and cross-resource questions — "users without MFA", "groups with no members", "applications nobody has accessed" - Broad exploration — "find anything related to contractors" **Do not use it for:** - A plain full listing → `users_list`, `devices_list` - A lookup by known ID → `user_get`, `device_get` - Product or documentation questions — "how do I configure SAML", "does JumpCloud support SCIM". It searches the customer's data, not the docs, and will return nothing useful. The `*_list` tools are also capped — `users_list` returns at most 20 records per call. Any question about the whole population is a search question, not a pagination exercise. ## Asking well Write the question the way you would ask a colleague who can see the database. Be explicit about the grouping and the filter; vagueness produces a vague query. | Instead of | Ask | |---|---| | "user info" | "How many users are in each state?" | | "check MFA" | "Which users have no MFA factors enrolled?" | | "old devices" | "List devices whose agent last checked in more than 30 days ago." | | "group sizes" | "How many users are in each user group, highest first?" | Put the time window in the question when one applies — "in the last 90 days", "since 2026-01-01" — rather than filtering afterwards. Ask one question per call; a compound question produces a compromise query that answers neither half well. ## Reading the result The response is an envelope, not a bare row set: ```json { "type": "dsl", "rationale": "why this query shape was chosen", "explanation": "what the query retrieves, in plain language", "query_result": { "results": [ { "itemnum": 1, "fields": [ { "field": "user.user_state", "value": "ACTIVATED" }, { "field": "user_count", "value": 116 } ]} ], "metadata": { "schema": [ { "field": "user.user_state", "type": "string" } ] } } } ``` Read it in this order: 1. **`explanation`** — check that the query it actually ran matches the question you meant to ask. This is the guard against silently answering the wrong question. If it drifted, re-ask more explicitly rather than reporting the number. 2. **`query_result.results`** — each row is a `fields` array of `{field, value}` pairs, not a flat object. Field names are namespaced (`user.user_state`), and `metadata.schema` gives their types. 3. **`type`** — `"dsl"` means a structured query ran. A `type` of `"none"`, or an `error` flag with a message in `explanation`, means the question could not be compiled. That is not an empty result set — do not report it as "zero". An empty `results` array genuinely means zero matches. A `"none"` type means the question was not understood. Say which one you got. ## Reporting The caller usually sees the raw rows already. Add what the rows do not say: > 123 users: 116 activated, 6 suspended, 1 staged. The staged account has been sitting > unactivated since March — worth chasing or removing. Not: "ACTIVATED: 116, STAGED: 1, SUSPENDED: 6." Lead with the number that answers the question, then the outlier. If a result is surprising, say so and suggest the follow-up query rather than speculating about the cause. ## Chaining `search_api_execute` narrows; the typed tools act. A normal sequence is: search to find the population, then `user_get` or `device_get` on the specific records that matter, then a write tool if the user asks for one. Do not invert this. Listing every user and filtering client-side to answer "how many are suspended" wastes calls and truncates at the list cap, so it is also wrong.