MCP results and errors
Results and resources
Section titled “Results and resources”Successful tools return JSON text with the shape { ok: true, summary, data }. Failures return
{ ok: false, error } and set isError: true; the connection itself remains valid.
When a tool is refused for a service reason, the failure also carries code, that reason in
lowercase snake_case, and status, the HTTP status of the same refusal when it has one:
{ "ok": false, "error": "Could not open data-connection … Opening it needs permission to manage its access. The app was not changed.", "code": "public_data_access_refused", "status": 403}Branch on code, not on the wording of error, which can change. A failure with no service
reason, such as an upstream outage or a refused argument, has neither field.
A call whose arguments do not match the tool’s input schema is refused before the tool runs. It
comes back as an ordinary tool result with isError: true, not as a JSON-RPC error; the text
begins MCP error -32602: Input validation error: Invalid arguments for tool <name>: and then
states what was wrong. Where the schema offers alternative shapes — a selection list or a
name-keyed map, a measure reference or an inline expression — that statement names the branch
that best fits what you sent, addressed by the path you wrote and saying what the field accepts:
MCP error -32602: Input validation error: Invalid arguments for tool astrato_data_execute_query:measures.Reduced.nestedCalculation.aggregate: Invalid option: expected one of "SUM"|"AVG"|"MIN"|"MAX"at query.measuresRead the path rather than resending the same shape: a refusal that names a field is a refusal you can correct in one edit. A message naming a field you did not send means the schema matched a different shape than you intended — supply that shape’s required fields, or drop the keys that belong to it.
A layer whose model the query compiler rejects as a whole cannot answer any query, whatever the
query selects. The model is the published one, or the saved draft a call targets with draft: one broken entity, such as two measures whose names differ only in
capitals or a measure that reads a removed view, refuses them all. Editor validation errors that
the compiler accepts do not block queries. Both
astrato_data_prepare_query, including the first call with no selection, and
astrato_data_execute_query, dryRun included, then return ok: false with an error that says the
layer cannot be queried until it is repaired and names every validation error, as
astrato_semantic_layer_validate reports them. Answer from another layer, or repair and publish this
one; no part of it is queried in the meantime.
A failure that never reaches a tool answers at the transport instead, as a JSON-RPC error rather
than a tool result. 503 with a Retry-After header means a dependency this server needs is briefly unavailable, and
the same call is worth repeating. 500 means this deployment cannot serve the call at all until an
operator changes something, so repeating it will fail identically; the message names what needs
attention. Neither invalidates the connection.
Both astrato_data_execute_query and astrato_semantic_layer_execute_question accept an optional queryTimeout
in whole seconds, from 120 to 600 (default 120). This controls the engine’s timeout for each
attempt. A brief connection failure, an interrupted response, or a query timeout can trigger one
automatic retry of the same read. A second attempt may benefit from a result cached by your data
warehouse; it can also run the query again. A server-requested retry delay is never shortened: if
it cannot fit within the remaining wait, the call fails without another attempt.
Both tools also return best-effort sourceContext metadata with the rows:
"sourceContext": { "connection": { "connectionId": "…", "name": "BigQuery-acme-prod", "driver": "google_bigquery" }, "defaultLocation": { "kind": "project", "value": "acme-prod", "resolvedFrom": "connection" }, "additionalLocations": [{ "kind": "project", "value": "shared-crm" }], "scope": "query"}connectionis the data connection the published semantic layer runs against.defaultLocationis the configured default for tables without their own location: a BigQuery project, a Snowflake database or a Databricks catalog.kinduses the connection’s own term.- When the connection uses per-user settings, it is your own setting, and
resolvedFromisuser-settings. resolvedFrom: "unresolved"means the location could not be read, andvalueis absent. Say so rather than assuming the connection’s usual location.
- When the connection uses per-user settings, it is your own setting, and
additionalLocationslists distinct explicit locations from the runtime’s view states, without view names.scope: "query"means runtime-derived context; those states can include compatible tables that the query did not read. Withscope: "layer", runtime context was unavailable and the list covers all table sources in the semantic layer.- Tables defined by custom SQL can read locations named in their statements; those are not listed.
Known limitations: this metadata is not verified execution lineage or proof of whose data contributed to an answer. Connection and per-user settings are read independently of execution. If settings change while execution still uses a cached token, the reported default can name the new location while the query uses the earlier one. A default location also need not be used when all queried tables name explicit locations. Qualify source attribution with these limitations, and never guess an unresolved location. Source context does not change what the query reads.
Allow your MCP client enough time for preparation and both attempts: at least six minutes with
the default timeout, or 22 minutes with queryTimeout: 600. Query calls use the MCP event-stream
response while waiting. Keepalive events mean the connection is open; they do not report database
progress. Cancelling ends the wait and prevents another automatic attempt, but does not guarantee
that a query already submitted to your warehouse stops.
When a query reaches the engine and the engine refuses it, astrato_data_execute_query and
astrato_semantic_layer_execute_question report the engine’s own reason, so a rejected selection or filter
can be repaired rather than guessed at:
The semantic layer query failed upstream (422). The engine rejected it [BAD_FILTER]: <reason>The generated SQL is never included. A 5xx from the engine carries no reason a caller can act
on, so it keeps the generic advice to check the selection against astrato_data_prepare_query or narrow
the query.
measure is accepted as an alias of calculation, in both sourceKind and the key prefix, so a
stored measure addressed the natural way still resolves. A key or stored-question id that matches
nothing names the nearest real matches, ranked the same way astrato_semantic_layer_search_fields ranks, so a
near-miss can be corrected on the next call instead of prompting another catalogue read.
Searches rank by a 0..100 score that ignores punctuation, symbols, spacing and case, so for the
search Net Sales Δ the measures Net Sales Δ, Net Sales % Δ, Net Sales % Δ% and
Net Sales Δ% all score 100. From contract 6.52.0, among equal scores the item named exactly as
the search comes first, then one equal to it ignoring case, surrounding space and Unicode form,
then one whose label or synonym matches in the same two ways. Scores are unchanged. This order
applies to the search of astrato_data_prepare_query, to astrato_semantic_layer_search and
astrato_semantic_layer_search_fields, to the nearest matches above, and to the candidates of the
measure and dimension prepare tools and astrato_semantic_layer_plan_authoring_batch. A candidate
equal to its query text carries match: "exact" or match: "canonical", and a query whose best
candidate is its only exact match resolves to it; two exact matches stay ambiguous for you to
choose, whatever candidates rank between them. Search text is at most 200 characters in
astrato_data_prepare_query, astrato_semantic_layer_search,
astrato_semantic_layer_search_fields and authoring queries, counted as Unicode code points as
JSON Schema maxLength counts them. A search reads the layer once, as an
unsearched page does. A cursor issued for a search before contract 6.52.0 is refused once; call
again without it.
Some responses contain an nash-mcp://resources/{id} URI inside their JSON data. A URI field is not
itself an MCP resource_link content block: clients can pass it to later tools or read it through
MCP resources. Complete V3 snapshots requested with includeSnapshot: true, Apache Ossie exports
and progressive information-schema responses also include a resource_link block for clients that support one. See
Read the complete V3 document for the two-call raw
JSON-RPC workflow.
Query response budgets and continuation
Section titled “Query response budgets and continuation”Both astrato_data_execute_query and astrato_semantic_layer_execute_question accept optional
responseBudgetBytes for executed table results. Use an integer from 4,096 to 262,144; the default
is 49,152. It limits serialized UTF-8 bytes of result data, including the stored-question wrapper,
excluding MCP framing and the tool summary. This is an Astrato setting, not a token count or an
MCP protocol limit. It does not apply to dry-run SQL. MCP does not automatically advertise your
model’s remaining context, and Astrato does not infer it from your client’s name.
Follow nextOffset with the same query or stored question ID, parameters, limit and sorting.
Only delivered rows advance the offset; cells stay intact. Each request checks your current
permissions again and reads live data. Concurrent changes can affect later pages, so this is not
a snapshot export. An absent totalRecords means the total is unknown.
If one row cannot fit, retry the same offset with a larger byte budget within the allowed range, or select fewer columns or narrower values. Increasing the hint cannot override server caps. The HTTP query API uses an independent 1 MiB result cap; upstream query responses have a 16 MiB read cap. An oversized upstream result requires a smaller query.
Optional metadata yields to rows when a page is too large. Check the always-present
omittedMetadata flags: statement: true or sourceContext: true means that field was omitted
for size. A false flag does not mean the field was available. Do not infer missing SQL or sources.
You may retry with a larger budget, but optional metadata is not guaranteed to fit.
In-product Nash chooses a bounded allowance using its configured model window, reserved output, current context estimate and parallel tool count. This is an estimate, not exact token accounting; when no useful allowance remains, Nash declines the query instead of silently dropping its rows.
Query replay capture
Section titled “Query replay capture”Set debug: true on astrato_data_prepare_query, astrato_data_execute_query, or
astrato_semantic_layer_execute_question to capture a replay bundle for that call.
The response includes debugReplay and a native MCP resource link. Read
debugReplay.resourceUri with resources/read and save its JSON. Resources use
the existing caller and tenant scope. By default they expire after 30 minutes without a read;
each successful read refreshes that idle lifetime. A server can configure a different lifetime.
Capture is disabled by default.
The bundle contains the published semantic document and revisions, query catalog, runtime model, translated query contracts, resolved parameters, full compiler analysis, and available compiler version metadata. Discovery includes the current selection and assessed candidates. It preserves analysis omitted from the inline response. Existing compiled SQL is included when available. Authorized failures can also return a bundle; failures before published access do not.
Capture excludes request credentials and result rows. Model definitions and filter
or parameter values remain in the bundle. Capture is bounded to 16 MiB; size or
storage failures report debugReplayUnavailable without replacing a successful
query result. It does not perform additional SQL compilation or warehouse queries.
Warehouse contents and the compiler clock are not captured.
A saved bundle can be shared with Astrato support for query reproduction. Reproduction requires a compatible data snapshot and the recorded compiler version and database dialect.
Review a bundle before you share it. It includes filter and parameter values, which can contain business or personal data.