The MCP half of a defect just fixed on the web half in #2477.
get_blocking_trend serializes { server, hours_back, trend } unconditionally. On a server that had no blocking, trend is [] — the same bytes a server with nothing collected yet produces. There is no {status, message} envelope, no count, nothing.
That was visible on the web dashboard because renderPanel had real data in hand and fell through to the chart's "Not enough data points to chart yet" — a warming-up message on a server that simply never blocked. #2477 fixes the browser at the renderer.
It is worse for an agent than it was for a person. A human sees an empty chart in a page whose other panels are populated and infers the server is fine. An MCP client has only the JSON. An LLM asked "is this server blocking?" can reasonably answer "there is no data" when the true answer is "no, and that is good news" — and on a monitoring tool that is close to the worst available failure, because it converts a clean bill of health into an apparent gap in coverage.
Scope of the pattern
Scanned every [McpServerTool] in Darling/PerformanceMonitor.Darling.Service/Mcp and Lite/Mcp for a top-level collection with no early-return on empty.
Darling — no distinction between "none happened" and "none collected":
| Tool |
Empty shape |
Web read? |
get_blocking_trend |
trend: [] |
yes |
get_deadlock_trend |
trend: [] |
yes |
get_memory_clerks |
clerks: [] |
yes |
get_wait_types |
wait_types: [] |
yes |
get_mute_rules |
bare list |
yes |
compare_analysis |
bare comparison |
yes |
Neighbours that already do this correctly, which is what makes the six read as an omission rather than a decision: get_wait_stats, get_memory_stats, get_file_io_trend, get_memory_trend, get_query_duration_trend and ~50 others all return McpHelpers.Status("unavailable", "…"); the three *_changes tools go further and return a purpose-built NoChanges(...) carrying hints: { server, snapshot_count } — which is precisely the "queried successfully, zero rows, here is the denominator" shape the trend tools want; and list_servers guards its empty registry with prose. So the vocabulary exists and these did not adopt it.
Lite — the same six, plus three where the two SKUs disagree:
get_file_io_trend, get_memory_trend and get_query_duration_trend return a bare empty array on Lite while their Darling twins return a status envelope. Same tool name, same client, two different answers depending on which SKU it is pointed at. That is a parity break independent of this issue's main point and is arguably the more urgent half.
What the fix probably looks like
Not simply "add an envelope" — for a periodic sample, an absence is only interpretable with its denominator, which is the reasoning behind the PostgreSQL pg_blocking read carrying its own capture counts from collection_log: the edge table cannot tell "no blocking" from "not collected", both being an absence of rows, but the capture count can. get_blocking_trend has exactly that property.
So the shape worth converging on is the one NoChanges(...) already uses — a status plus hints naming what was examined — rather than a bare "unavailable", which would replace one ambiguity with a friendlier-sounding one. An agent needs to be able to tell "42 collection cycles ran in this window and none of them saw blocking" from "no collection cycles ran".
Both SKUs, and the shape has to match or the parity gap above just moves.
Related
The MCP half of a defect just fixed on the web half in #2477.
get_blocking_trendserializes{ server, hours_back, trend }unconditionally. On a server that had no blocking,trendis[]— the same bytes a server with nothing collected yet produces. There is no{status, message}envelope, no count, nothing.That was visible on the web dashboard because
renderPanelhad real data in hand and fell through to the chart's "Not enough data points to chart yet" — a warming-up message on a server that simply never blocked. #2477 fixes the browser at the renderer.It is worse for an agent than it was for a person. A human sees an empty chart in a page whose other panels are populated and infers the server is fine. An MCP client has only the JSON. An LLM asked "is this server blocking?" can reasonably answer "there is no data" when the true answer is "no, and that is good news" — and on a monitoring tool that is close to the worst available failure, because it converts a clean bill of health into an apparent gap in coverage.
Scope of the pattern
Scanned every
[McpServerTool]inDarling/PerformanceMonitor.Darling.Service/McpandLite/Mcpfor a top-level collection with no early-return on empty.Darling — no distinction between "none happened" and "none collected":
get_blocking_trendtrend: []get_deadlock_trendtrend: []get_memory_clerksclerks: []get_wait_typeswait_types: []get_mute_rulescompare_analysisNeighbours that already do this correctly, which is what makes the six read as an omission rather than a decision:
get_wait_stats,get_memory_stats,get_file_io_trend,get_memory_trend,get_query_duration_trendand ~50 others all returnMcpHelpers.Status("unavailable", "…"); the three*_changestools go further and return a purpose-builtNoChanges(...)carryinghints: { server, snapshot_count }— which is precisely the "queried successfully, zero rows, here is the denominator" shape the trend tools want; andlist_serversguards its empty registry with prose. So the vocabulary exists and these did not adopt it.Lite — the same six, plus three where the two SKUs disagree:
get_file_io_trend,get_memory_trendandget_query_duration_trendreturn a bare empty array on Lite while their Darling twins return a status envelope. Same tool name, same client, two different answers depending on which SKU it is pointed at. That is a parity break independent of this issue's main point and is arguably the more urgent half.What the fix probably looks like
Not simply "add an envelope" — for a periodic sample, an absence is only interpretable with its denominator, which is the reasoning behind the PostgreSQL
pg_blockingread carrying its own capture counts fromcollection_log: the edge table cannot tell "no blocking" from "not collected", both being an absence of rows, but the capture count can.get_blocking_trendhas exactly that property.So the shape worth converging on is the one
NoChanges(...)already uses — a status plushintsnaming what was examined — rather than a bare"unavailable", which would replace one ambiguity with a friendlier-sounding one. An agent needs to be able to tell "42 collection cycles ran in this window and none of them saw blocking" from "no collection cycles ran".Both SKUs, and the shape has to match or the parity gap above just moves.
Related
vizLinerenders a descriptor's own sentence at exactly zero rows).