Reference / Authoring
explore_data
Run one read-only SELECT against a connection and get a small, complete answer. The bound, why it refuses instead of shortening, parameters, errors and scope.
Runs one read-only SELECT against a connection and returns the whole small
answer: the columns, the row count, and the rows themselves.
This is the call for the questions that come before anything is designed. What values does this column hold. How many customers are there. What does a row of this table look like. How far back does the data go. Is this column ever null.
Behaviour hints for clients: read-only, not open-world.
When your AI should use it, and when it should not
The order matters, and only your AI can apply it, because only your AI can see its own tool list.
- If a semantic layer defines the metric, that definition wins. Dashies keeps a dashboard fresh, versioned and shareable; it does not decide what a metric means, and neither does anything you learn from this call.
- If the session can reach your warehouse directly (your own SQL client, or a server your warehouse vendor publishes), it should explore there. It is your system, it is not bounded by the size of an MCP result, and you can see what was run.
- If it has neither, this is the supported call. It is a real answer rather than a workaround, which is the whole reason it exists as its own tool.
It is not the check that a statement you are about to publish will keep
working. That is
validate_cube_sql, and no tool outside
Dashies can stand in for it.
Parameters
| Parameter | Type | Required | Notes |
|---|---|---|---|
sql | string | yes | A single read-only SELECT or WITH ... SELECT. Bound it yourself: a LIMIT, a narrow window, or a GROUP BY that returns counts rather than rows. |
connection | string | no | self (the default), a warehouse connection id, or the uploaded-file source id (which also needs upload). |
upload | string | no | Which uploaded file to read, as an upload id from create_file_upload. Required when connection is the workspace's uploaded-file source, and refused on any other connection - a warehouse has no uploads. There is no default and the newest upload is not assumed: a later upload would otherwise silently change what this call answers and what a published dashboard reads, so omitting it is refused with the newest ready upload named for you to pass back. |
The bound, and why it refuses instead of shortening
| Bound | Value |
|---|---|
| Rows in one answer | 200 |
| Bytes of rows in one answer | 12288 |
The two bind independently, whichever comes first, so a handful of rows carrying long text or JSON reaches the byte budget while a narrow answer reaches the row bound.
Past either bound you get nothing, not the first part
A shortened answer to "what values are in this column" reads exactly like a complete one, and a dashboard built on the short version filters on a value set that was never the whole set. So an oversized answer is refused, and the refusal says what to change.
The consequence is worth stating positively: every answer you do get is complete. There is no sample, no truncation flag, and nothing to page through.
That claim is load-bearing, so it is worth saying how it is kept. The whole result is read rather than the first page of it, and if the warehouse returns rows but will not report how many there are in total, the answer is refused rather than reported as though it were all of them. An answer that might be a fragment reads exactly like a complete one, which is the reason the refusal is better than a number.
There is no offset argument and no way to raise the bound, so this is not a way to read a table out a page at a time. If what you want is a table's contents, this is not the call.
Returns
Explored connection "self". 3 rows.
Columns:
- region: string
- orders: number
That is the whole answer, not a sample.
BEGIN_JSON
{"connection":"self","row_count":3,"columns":[{"name":"region","type":"string"},{"name":"orders","type":"number"}],"rows":[{"region":"EMEA","orders":1240},{"region":"AMER","orders":903},{"region":"APAC","orders":517}]}
END_JSON
The header lines and the JSON block are built from the same object, so they always agree: the columns listed above are the columns in the block.
row_count is the warehouse's own total for the statement, not a count of the rows in the
block. On every answer you actually receive the two are equal, because an answer whose row
count disagrees with its own total is refused in either direction: too few rows for the total,
or more rows than the total claims. They are stated as two facts rather than one because they
come from two places.
An answer with no rows is a result, not a failure, and the text says so:
Empty is a result, not a failure - the filter, the window or the table is empty. It usually means the filter matches nothing or the window is outside the
data, and retrying it unchanged will produce the same answer.
Errors
The shared connection rules, plus:
| Condition | Text |
|---|---|
| Missing SQL | sql is required |
| Over the row bound | That returned more than 200 rows, which is more than one exploration answer holds, so nothing is being returned rather than the first part of it: ... |
| Over the byte bound | That answer is too wide to return, so nothing is being returned rather than part of it. Name the columns you actually want instead of selecting them all, or take fewer rows. |
| An answer that could not be read to the end | The answer came back in parts and could not be read to the end, so it is not being returned - a partial answer reads exactly like a small one. Retry; ... |
| The warehouse would not say how many rows there are | The warehouse returned rows but did not report how many there are in total, so whether these are all of them cannot be established ... |
| More than one statement | That is more than one statement (a ';' separates statements). ... |
| Not a read-only SELECT | That is not a single read-only SELECT (or WITH ... SELECT). ... |
| A statement that writes | That statement tries to write, and this call runs in a read-only transaction. ... |
| A system-catalog read | That statement reads the database's own system catalog, which this connection does not expose. ... |
| Too slow | That took longer than one exploration answer is allowed to take, so it was stopped. ... |
For BigQuery, Snowflake, Redshift and Databricks an error about your own SQL (an unknown column, a syntax error) is surfaced as the engine wrote it.
Rate limited at 120 calls per user per minute, shared with
validate_cube_sql: the two run the
same read against the same executor, so they are one budget rather than two.
Scope
Connection-scoped, so there is no workspace argument. It accepts any
connection belonging to a workspace you are a current member of, plus the built-in
self and the shared sample. A
warehouse connection requires a paid plan; self does not. It reads only the
tables that connection exposes, inside a read-only transaction, exactly as
validate_cube_sql does.
Check it worked
The row count is the thing to read. If an exploratory count comes back at zero, that is a real finding about the filter or the window rather than a failed call, and it is worth resolving before it becomes a dashboard tile that reads zero forever.