About cube-run-query
cube-run-query is a skill for querying Cube data through available MCP tools or the Cube CLI. It guides users to discover model members, run queries, and interpret results with security context and query details.
Key features
- 01Query through Cube MCP tools or CLI
- 02Discover queryable model members
- 03Run filtered and time-based queries
- 04Page through results over 100 rows
- 05Verify changes on development branches
Use cases
- 01Answering metric questions from Cube data
- 02Checking results after a model change
- 03Running Cube queries in scripts or CI jobs
MCP support
When Cube MCP tools are available, the skill uses `cube:searchDataModel`, `cube:runQuery`, and `cube:chat`. It also describes the CLI path when MCP tools are unavailable.
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ cube-js/ cube-agent-skills --skill cube-run-queryAfter installing, type/cube-run-queryin Claude Code to confirm it loaded.
Details
From the project READMERun a query against Cube
Choose the path
Cube MCP tools are written cube:<tool> below: the cube server this plugin
bundles. Through the Cube connector, the same tools carry the connector's name.
If the Cube MCP tools are available, use them. They come from this
plugin's cube server or the Cube connector — look for cube:searchDataModel,
cube:runQuery and cube:chat. They need no install or login step, and every call runs
as the signed-in user, so row-level security applies exactly as in the UI.
- A specific number:
cube:searchDataModelto find the view and the exact member names, thencube:runQuerywith Cube SQL —SELECT status, MEASURE(count) FROM orders_view GROUP BY 1. Never guess a member name. - An open-ended question ("why did signups drop in March"):
cube:chat, which runs Cube's own agent over the governed model and existing reports. Give the user thecubeChatUrlit returns so they can continue in Cube. - Another deployment:
cube:listDeployments, then pass itsdeploymentId. - A dev branch: pass the same
branchNametocube:searchDataModelandcube:runQuery, so the members you find are the ones the query sees. - More than 100 rows: page
cube:runQuerywithoffsetuntilhasMoreis false.
Use the CLI path below when the MCP tools are not connected, or in a script or CI job. Interpreting results applies to both paths.
CLI path
Querying steps outside the CLI. cube api is bound to Cube's console API
and cannot reach a deployment's query endpoint, so querying is two CLI calls
to get a URL and a token, then a direct request to the deployment.
Preflight
command -v cube >/dev/null || echo "Cube CLI not installed: curl -fsSL https://raw.githubusercontent.com/cube-js/cube/master/install-cli.sh | sh"
cube whoami || echo "Not authenticated. Interactive: cube login. Headless: set CUBE_API_URL + CUBE_API_KEY."
cube context list
jq and curl are also required here, unlike every other skill in this
plugin. Check for them and say so if they are missing.
Get the endpoint and a token
DEPLOYMENT=<id>
URL=$(cube deployments get "$DEPLOYMENT" --json | jq -r .deploymentUrl)
TOKEN=$(cube deployments token "$DEPLOYMENT")
cube deployments token prints the Cube API token on its own; add --json
if you want the wrapping object. The token carries the calling user's
security context, so row-level security applies exactly as it would in the
UI — a query here returns what that user is allowed to see, not everything.
Know what you can query first
Do not guess member names. Ask the compiled model:
cube meta --selectors '[{"type":"cube","deploymentId":'"$DEPLOYMENT"',"environment":"production"}]'
Members are Cube.member or View.member. A dimension that exists on a cube
but is not exposed in a view is not queryable through that view — cube meta
is what tells you which is which.
Run it
curl -s "$URL/cubejs-api/v1/load" \
-H "Authorization: $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"query":{
"measures":["Orders.count","Orders.totalRevenue"],
"dimensions":["Orders.status"],
"timeDimensions":[{"dimension":"Orders.createdAt","granularity":"month","dateRange":"Last 6 months"}],
"limit":100
}}' | jq
Query shape, in the order you usually need it:
measures— the numbers. Always fully qualified.dimensions— what to break down by.timeDimensions—dimensionplusgranularity(day/week/month/…) anddateRange, which accepts natural ranges like"Last 30 days"or an explicit["2026-01-01","2026-06-30"].filters—[{"member":"Orders.status","operator":"equals","values":["paid"]}].order,limit.
The response carries data plus annotation, which holds the human titles
and formats for each member. Use annotation when presenting results — it is
where a measure's display name and unit live.
Interpreting results
- Report the number, then how it was derived. Which measure, which range, which filters. A number without its query is not checkable.
- An empty
dataarray is a result, not an error. Say the query succeeded and returned no rows; do not retry with a different query and present that instead without saying you changed it. - Never fill in a value you did not receive. If the query failed, report the error.
Verifying a model change
The common case after cube-build-model: query the new measure on the dev
branch before merging. Over MCP, pass the dev branchName to
cube:searchDataModel and cube:runQuery. With the CLI, $URL/cubejs-api/v1/load
always answers from production; a dev branch has its own endpoint under the
same deployment URL, used with the same token:
cube deployments build-status "$DEPLOYMENT" --branch <dev-branch> --wait
curl -s "$URL/dev-mode/<dev-branch>/cubejs-api/v1/load" -H "Authorization: $TOKEN" ...
Point cube meta at the branch the same way, with "environment":"<dev-branch>".
When something fails
| Symptom | Cause |
|---|---|
deploymentUrl is null | The deployment has never finished a build; check cube deployments build-status |
| 401 from the load endpoint | Token expired — they are short-lived, just mint another |
Member not found | Wrong name or not exposed in that view — re-check cube meta, do not guess a variant |
{"error":"Continue wait"} | The query is still running — send the same request again until data comes back. Tell the user if it keeps going past a minute or two |