Region: If more than one
squaredup-*server is connected, ask the user which one they mean before running any tools, then use that same server for every SquaredUp tool call in the task.
Querying a SquaredUp data stream
The data_stream_query tool runs a configured data stream against one or more data sources, scoped to a set of entities, optionally parameterised by stream-specific inputs, over a timeframe, in the context of a workspace. Output is rows of structured data.
This mirrors the five steps a user takes in the tile editor (which always happen inside a workspace - MCP makes that context explicit as a required workspaceId):
- Pick a data source.
- Pick a data stream.
- Pick scope (objects), if the stream requires it.
- Fill in the stream's inputs (template), if it has any.
- Optionally wrap the request in a SQL envelope to aggregate, filter, or sort.
The workspace context (required)
workspaceId is a required argument. The query runs under that workspace's access control, exactly as it would for a tile in that workspace: you must have access to the workspace, and the data sources you query must be linked to it. Get the id from workspace_list; if you're re-assembling a query from an existing tile, use the dashboard's workspaceId (returned by dashboard_list and dashboard_get). To check which data sources a workspace is linked to, read links.plugins from workspace_get.
The five pieces
Which stream -
dataStreamId(e.g.datastream-upITeLZ9YzWNZbrx6iHU, or a built-in slug likedatastream-health/datastream-sql) ordataStreamName(canonical name). The two are mutually exclusive. Discover stream IDs by:- Reading existing tiles via
tile_get(the most reliable source - see thedashboardsskill). - Finding stream-defining nodes via
graph_query. - Asking the user if they referenced a specific report or metric by name.
- Reading existing tiles via
Which data source(s) -
pluginConfigId(single) oractivePluginConfigIds(array, for streams that aggregate across sources). Get fromdata_source_list. Without this the stream has nothing to run against.Scope - which entities to query for. Shape:
{ "resolvedIds": ["node-4tfDTNWMNFf9Hdygh53vwWB4pLY5iK6WYB-4cp3PAvZbuEoJr0V3iX7"], "resolvedTypes": ["host", "container"], "resolvedSourceTypes": ["AWS::EC2::Instance"] }Each data stream definition declares what it can be scoped to via its
matchesfield - for example a Pull Requests stream that matches repo vertices meansresolvedTypesmust include the repo type orresolvedIdsmust point at concrete repo nodes. Picking the wrong entity types returns nothing.Use
scope_resolveto convert workspace IDs / scope IDs / type filters into concreteresolvedIds, then pass the result through. For inventory-style streams ("list all EC2 instances"), an empty scope or a type-only scope often works.Stream inputs (template) - many streams expose user-fillable inputs (state pickers, severity filters, query strings). In the tile editor these come from the stream's
templatedefinition; on the wire they live indataSourceConfig. Example: a Pull Requests-style stream that takes astatechoice (open, draft, merged) is invoked with:{ "dataSourceConfig": { "state": "open" } }A built-in concrete example: the
datastream-configurableGremlinstream takes agremlintext input, so the call shape is{ "dataStreamId": "datastream-configurableGremlin", "dataSourceConfig": { "gremlin": "g.V().limit(10)" } }.To find what inputs a stream accepts, the most reliable path is to read an existing tile that uses the same stream via
tile_getand copy itsconfig.dataSourceConfig. Inputs are stream-specific - there is no universal list.Timeframe - a string like
"last1hour","last24hours","last7days", or an object{ from, to }. Required by most streams. Omit only for inventory-style streams that don't have a time dimension.
Standard workflow
workspace_list- pick the workspace the question targets (this becomes the requiredworkspaceId).data_source_list- pick the data source(s). Check they're linked to the workspace (workspace_get→links.plugins) if unsure.- Identify the stream (often by reading an existing tile via
tile_get, which also reveals the required scope shape and anydataSourceConfiginputs). scope_resolve- get the entity node IDs the user cares about, matching the stream's required entity types.data_stream_query- passworkspaceId+ stream id/name + source(s) + scope +dataSourceConfig(if the stream takes inputs) + timeframe.
If you don't know which stream to use, find a tile on an existing dashboard that does something similar and copy its dataStream.id, scope, dataSourceConfig, and source IDs - and take workspaceId from that dashboard (dashboard_list / dashboard_get return it). This is faster and more reliable than guessing.
Shortcut: re-running an existing tile's query
If the user wants the data a tile is already showing - don't assemble the query manually. Use:
tile_query(dashboardId, tileId, timeframe?)- runs a single tile's stream as configured, returning its rows directly. Optionally override the timeframe.dashboard_query(dashboardId, timeframe?)- runs all data-stream tiles on a dashboard in parallel and returns all rows grouped by tile.
Both dashboardId and tileId are returned by tile_list, kpi_list, and dashboard_list. Use these tools when the tile already exists and you don't need to modify the query - data_stream_query is for custom or ad-hoc queries.
Aggregation, filtering, sorting - prefer SQL
options carries sort, filter, and group specs that mirror the dashboard tile UI (column-level sort/filter, single grouping key). They work for the simple cases the tile editor exposes, but they don't compose: you can't join, you can't aggregate-then-filter, and the spec shape is opaque to copy from a tile.
For anything beyond a single column sort or a flat group-by, invoke the built-in datastream-sql meta-stream and pass the inner stream as a table inside dataSourceConfig.tables. This is the path the client UI's "advanced mode" uses, and the only reliable one for joins, multi-step aggregations, top-N, or cross-stream queries:
{
"workspaceId": "space-yHLAPmsNyGkUPe8ZdDxE",
"dataStreamId": "datastream-sql",
"dataSourceConfig": {
"sql": "SELECT state, COUNT(*) AS n FROM prs GROUP BY state ORDER BY n DESC",
"tables": [
{
"tableName": "prs",
"config": {
"dataStream": {
"id": "datastream-upITeLZ9YzWNZbrx6iHU",
"pluginConfigId": "config-i5ZApyjOBIHBz2J4kG1z"
},
"scope": { "resolvedIds": ["node-4tfDTNWMNFf9Hdygh53vwWB4pLY5iK6WYB-4cp3PAvZbuEoJr0V3iX7"] },
"dataSourceConfig": { "state": "open" },
"timeframe": "last7days"
}
}
]
}
}
The inner config of each entry in tables is a partial tile config (inner stream id, plugin config, scope, inputs, timeframe). The outer SQL runs over the resulting rows, with each tableName available as a SQL table. Use multiple tables entries to join across streams.
Use this whenever the user wants a summary, a top-N, a join, or any composed operation.
Result size
The MCP data_stream_query tool runs in multi-page mode internally and returns the full result set in one response - there is no paging knob exposed to the caller. That makes large queries dangerous: streams over noisy entities can return tens of thousands of rows straight into the LLM context.
Before invoking, decide whether the user actually wants raw rows or a rollup. If a rollup is fine, wrap the inner stream in the datastream-sql envelope above and aggregate server-side. If raw rows are needed, narrow the scope or shorten the timeframe rather than relying on a default limit - most streams don't have one.
Common mistakes
- Omitting
workspaceId- it's required; the call is rejected before anything runs. - Passing a
workspaceIdthe user can't access, or one the queried data sources aren't linked to - denied or empty. Pick the workspace the tile/dashboard actually lives in, or checkworkspace_get→links.plugins. - Trying to override access control via
options.accessControlType- rejected; queries always run under the workspace's access control. - Calling
data_stream_querywith nopluginConfigId/activePluginConfigIds- returns an error. - Passing a scope with no
resolvedIds, noresolvedTypes, and noresolvedSourceTypes- typically returns nothing. - Passing scope with the wrong entity types for the stream's
matches- returns nothing. Check an existing tile. - Forgetting to pass required
dataSourceConfiginputs (e.g. omittingstateon a Pull Requests stream, orgremlinondatastream-configurableGremlin) - returns an error or empty result. - Confusing
dataStreamId(datastream-…) with the data sourcepluginConfigId(config-…). - Passing both
dataStreamIdanddataStreamName- they are mutually exclusive. - Forgetting
timeframeon a time-series stream - error or empty result. - Reaching for
options.sort/options.filter/options.groupfor anything non-trivial - the spec shape mirrors the tile UI and doesn't compose. Use thedatastream-sqlenvelope for joins, aggregation, top-N, or anything multi-step. - Expecting
options.pagingContextto work - the MCP tool runs the stream end-to-end internally; there is no LLM-driven pagination. Narrow scope/timeframe instead.