Skip to content

MCP budget: get_query_store_top's default stays under 32 KB (#4198) - #4273

Merged
erikdarlingdata merged 8 commits into
devfrom
fix/4198-qs-top-default
Sep 25, 2026
Merged

erikdarlingdata merged 8 commits into
devfrom
fix/4198-qs-top-default

Conversation

@erikdarlingdata

Copy link
Copy Markdown
Owner

Part of #4198.

Why

get_query_store_top (Darling and Lite) had no MCP response-size budget. query_text was truncated only at 2,000 characters, with no opt-in for the full statement and no flag saying a row was cut. At the default top=20, a busy production store measured 48 KB, over the shared 32 KB budget (McpResponseBudget.DefaultBytes).

What changes

  • Default preview cut to 400 characters. top (row count) stays 20. This row is narrow enough (17 fields) that only the wide field needed cutting. get_plan_corrections and get_query_store_regressions have wider rows that required a row-count cut too.
  • New full_text opt-in (default false) on both products, plus a per-row query_text_truncated flag. This mirrors get_plan_corrections's existing shape and the full_text name get_store_query_stats already uses.
  • Darling's MCP signature forwards to an internal previewLength overload (no async on the public wrapper, matching the shape #3897's trend tools use for TrendBudget.Chart). The web viewer's /api/read row calls that overload directly with previewLength: 2000 and full_text: QueryBool(c, "full_text", false). That is the exact number query_text was already capped at before this PR, so the viewer's page is unchanged. This differs from get_plan_corrections/get_deadlock_detail's web rows, which default to full_text/full_graph: true. Those fields had no cap before MCP read tools have no default response-size budget: at default arguments 12 tools return >50 KB and 3 return >100 KB for one server, more than an agent client's per-result cap #4198, so keeping the viewer unchanged meant keeping full text. This field already had a cap, so keeping the viewer unchanged means keeping the same 2,000-character limit. A no-rig source-pin test (QueryStoreTopWebDefaultTests, mirroring #4254's DeadlockDetailWebDefaultTests) checks the row's exact arguments.
  • Census and budget bookkeeping: FieldPreviewCutKeys in McpPayloadContractCensusTests now lists this tool's two files alongside DarlingMcpPlanCorrectionTools.cs/McpPlanCorrectionTools.cs under the same query_text_truncated key. tools/list budget ceilings raised by the measured growth: Darling +136 bytes, Lite +125 bytes (84 bytes of new-parameter description plus its JSON schema wrapper). DarlingCustomViewsTests' wire-key pin for this tool now includes full_text.

#4224 roster note

This tool is on PR #4224's (not yet merged) McpReadToolBudgetLiveTests ExemptOffenders roster. Per the coordinator's ruling, that wait is lifted since #4224 is stalled. McpReadToolBudgetLiveTests is not on dev yet, so nothing in this PR touches it. Whichever of #4224 or this PR merges second must remove get_query_store_top from that roster. It is now fixed and belongs under budget, not on the exempt list.

Touched only this tool's code

Lane TK is changing get_collection_health in the same DarlingMcpDataTools.cs file. This PR's diff in that file is scoped to get_query_store_top only: a new private const, the signature split, and the projection.

Test plan

Measured on the reused rig-tg rig (port 55982). darlingtest/probe dropped and recreated before use, per the lane brief.

  • New live test QueryStoreTopBudgetLiveTests (Darling): seeds 25 rows across 5 query-text lengths (120-2,600 chars). Default call measured 17,394 bytes (was 48 KB pre-fix). Well under the 32 KB budget, with margin for real-world field-width variance. full_text=true returns the whole statement. query_text_truncated is correct on both sides of the 400-char line.
  • New DuckDB test QueryStoreTopBudgetTests (Lite): same shape, no rig needed.
  • Darling.Tests full suite: 13,887 total, 0 failed (1 unrelated failure below, fixed and re-verified). 47 skipped, 1 not-run, both pre-existing categories unrelated to this change. ~11 min.
  • Lite.Tests full suite: 5,345 total, 0 failed, ~4 min.
  • Targeted re-runs green: McpToolsListBudgetTests (both products), McpPayloadContractCensusTests, QueryStoreTopWebDefaultTests, QueryStoreTopWindowTests (re-anchored to the new internal method), DarlingCustomViewsTests, DeadlockDetailWebDefaultTests.
  • Both projects build with 0 Warning(s), 0 Error(s).

One failure found and fixed: the first full run caught DarlingCustomViewsTests.Catalog_ParamsNameTheActualWireQueryKeys_NotCSharpParamNames failing. The Custom Views catalog reflects get_query_store_top's MCP parameters, and the test's hardcoded expected-keys list was stale. Fixed by adding full_text to that list. Re-ran the class alone (71/71 green) and then the full suite again.

One failure in the full run judged not mine: EventWindowedReadsAreBoundedLivePostgresTests.TheFlooredReads_PlanAtMostThreeChunksForA24HourWindow_AgainstDevPostgres. It is an EXPLAIN-plan chunk-count assertion in a file I never touched, about #4229/#4235 (event-windowed reads). It reproduces alone on this rig too.

The job_history hypertable accumulates many TimescaleDB chunks over a long-lived rig's full-suite runs. The EXPLAIN plan is sensitive to that count. This is not a regression from this diff. dev's last Build run is green. Flagging for the coordinator's census rather than re-running against a from-scratch rig, which did not fit this lane's remaining budget.

Open items for the coordinator

  • The A budget pin over every MCP read tool (part of #4198) #4224 roster removal above, whichever PR merges second.
  • The EventWindowedReadsAreBoundedLivePostgresTests failure: worth a look on a fresh rig if it recurs elsewhere tonight, since several lanes reused long-lived rigs.
  • That Lane TK's get_collection_health change in DarlingMcpDataTools.cs merges cleanly against this diff.

CHANGELOG entry

SECTION: Fixed
ENTRY:

Generated with Claude Code

https://claude.ai/code/session_01FVjn4PBJN71NQXdFo6ZxNQ

erikdarlingdata and others added 7 commits September 25, 2026 04:48
query_text was truncated only at 2,000 characters with no opt-in. At the
default top=20 that measured 48 KB on a busy production store's single
server, over McpResponseBudget.DefaultBytes (32 KB). Cuts the default
preview to 400 characters, adds a full_text opt-in and a per-row
query_text_truncated flag, on both Darling and Lite. Darling's MCP
signature forwards to an internal overload taking an explicit preview
length, so the web viewer can keep the old 2000-char cap unchanged
(wired in a follow-up commit).

New live test (Darling, rig) and DuckDB test (Lite) seed 25 rows across
five query-text lengths and assert the default call stays under budget
while full_text still returns the whole statement.

Part of #4198.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
Web viewer /api/read keeps the exact old 2000-char query_text cap
(through the new previewLength overload) instead of inheriting the
MCP tool's new 400-char default or switching to full text, pinned by
a no-rig source test. Census FieldPreviewCutKeys grows to cover this
tool's query_text_truncated key. tools/list budget gets the new
full_text parameter on both products, ceilings raised by the measured
growth (Darling +136 bytes, Lite +125).

Part of #4198.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
Catalog_ParamsNameTheActualWireQueryKeys_NotCSharpParamNames asserts
the exact wire-key list per tool; add full_text now that the tool has
one.

Part of #4198.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FVjn4PBJN71NQXdFo6ZxNQ
GitHub did not fire pull_request events for this draft-opened PR.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FVjn4PBJN71NQXdFo6ZxNQ
Darling ceiling: 172,367 (dev) + 136 (TJ) = 172,503.
Lite ceiling: 90,418 (dev) + 125 (TJ) = 90,543.
Census: merge TJ's query_text_truncated (adds get_query_store_top files/description)
with dev's top_query_text_truncated (from heatmap PR).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FVjn4PBJN71NQXdFo6ZxNQ
Resolves the #4198 train's conflicts after #4272 landed:
- McpToolsListBudgetTests (Darling and Lite): keep every change-log line;
  TotalCeilingBytes re-measured on the merged tree (Darling 174,373,
  Lite 92,041).
- McpPayloadContractCensusTests: query_text_truncated keeps dev's files
  and adds DarlingMcpDataTools.cs for get_query_store_top.
- Lite McpQueryTools: dev's get_query_store_regressions preview constant
  and this branch's get_query_store_top one shared a name in one class;
  the regressions one is now RegressionsQueryTextPreviewLength.
- QueryStoreRegressionsWebDefaultTests: its "no full_text false" check is
  scoped to the regressions dispatch entry, since get_query_store_top's
  web entry keeps full_text off by default on purpose.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FVjn4PBJN71NQXdFo6ZxNQ
@erikdarlingdata
erikdarlingdata enabled auto-merge (squash) September 25, 2026 13:26
@erikdarlingdata
erikdarlingdata merged commit c3780ff into dev Sep 25, 2026
15 of 16 checks passed
@erikdarlingdata
erikdarlingdata deleted the fix/4198-qs-top-default branch September 25, 2026 13:34
erikdarlingdata added a commit that referenced this pull request Sep 25, 2026
…sclosure

McpToolsListBudgetTests: keep every change-log line. #4231 adds no head
bytes, so TotalCeilingBytes is dev's measured total after #4272 and #4273
(174,373), re-measured on the merged tree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FVjn4PBJN71NQXdFo6ZxNQ
erikdarlingdata added a commit that referenced this pull request Sep 25, 2026
…as merged

After merging dev, the per-tool fixes for get_blocking (#4267),
get_collection_log (#4265), get_query_store_regressions (#4264),
get_collection_health (#4268), describe_custom_view_catalog (#4272) and
get_query_store_top (#4273) are all in, and get_fleet_overview already fit
(CI's stale-exemption message). Every row goes; CI's live run decides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FVjn4PBJN71NQXdFo6ZxNQ
erikdarlingdata added a commit that referenced this pull request Sep 25, 2026
…t-read

McpToolsListBudgetTests: keep every change-log line. TotalCeilingBytes
re-measured on the merged tree: dev after #4272 and #4273 (174,373) plus
get_store_host's 616 bytes = 174,989.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FVjn4PBJN71NQXdFo6ZxNQ
erikdarlingdata added a commit that referenced this pull request Sep 25, 2026
* Add the #4198 MCP read-tool budget pin (part of #4198)

McpReadToolBudgetLiveTests reflects over every [McpServerTool] method
on every [McpServerToolType] class in the Darling service assembly,
excludes write tools/analyze_*/compare_*/audit_config, binds each
tool's DI services and server_name generically, and asserts the reply
stays under McpResponseBudget.DefaultBytes unless the tool is named
in an explicit, byte-stamped exemption roster. A fix lands by
deleting its row.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3

* MCP budget pin: empty ExemptOffenders now every #4198 per-tool lane has merged

After merging dev, the per-tool fixes for get_blocking (#4267),
get_collection_log (#4265), get_query_store_regressions (#4264),
get_collection_health (#4268), describe_custom_view_catalog (#4272) and
get_query_store_top (#4273) are all in, and get_fleet_overview already fit
(CI's stale-exemption message). Every row goes; CI's live run decides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FVjn4PBJN71NQXdFo6ZxNQ

* MCP budget pin: get_collection_health is still over on this fixture

CI on 08e6e81 measured get_collection_health at 35,145 B, over the 32 KB
default. #4268 compacts only healthy collectors with nothing to report, and
this fixture's collectors are not healthy, so nothing compacts. The row goes
back, under #4198, which stays open for it. Every other exempt tool fits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FVjn4PBJN71NQXdFo6ZxNQ

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant