fix(mcp): steer agents toward builder query tools instead of raw SQL (HDX-4892) - #2840
fix(mcp): steer agents toward builder query tools instead of raw SQL (HDX-4892)#2840brandon-pereira wants to merge 9 commits into
Conversation
…ql-tool-use-investigation
Probe each MCP server (initialize + tools/list) before spawning any agents and abort the batch if a server is unreachable or serves zero tools. Previously a dead API server produced an entire suite of silent zero-tool-call runs that graded as real (bad) scores, masking the failure. Add --no-preflight to bypass.
🦋 Changeset detectedLatest commit: 6b82767 The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub. 2 Skipped Deployments
|
Greptile SummaryThe PR adds agent-facing guidance that prioritizes structured MCP query builders over raw SQL and introduces an eval-harness MCP reachability preflight.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| packages/api/src/mcp/mcpServer.ts | Adds server-level tool-selection instructions to the MCP initialize response. |
| packages/api/src/mcp/tools/query/builderCatalog.ts | Centralizes builder-tool descriptions and the criteria for falling back to raw SQL. |
| packages/hdx-eval/src/harness/preflight.ts | Implements HTTP MCP initialization and tool-list reachability checks while passing through stdio definitions. |
| packages/hdx-eval/src/cli.ts | Runs the preflight before seed detection and reseeding, fully addressing the prior ordering issue. |
| packages/hdx-eval/src/tests/preflight.test.ts | Covers successful JSON/SSE responses, zero-tool and protocol failures, connectivity errors, and stdio passthrough. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
Run[Start eval run] --> Resolve[Resolve config and anchor time]
Resolve --> Preflight[Probe HTTP MCPs]
Preflight -->|Failure or zero tools| Abort[Abort batch]
Preflight -->|All healthy| SeedCheck[Check or reseed scenario data]
SeedCheck --> Agents[Spawn evaluation agents]
Agents --> Grade[Grade and report results]
Reviews (6): Last reviewed commit: "Merge branch 'main' into brandon/brandon..." | Re-trigger Greptile
E2E Test Results✅ All tests passed • 277 passed • 1 skipped • 1117s
Tests ran across 4 shards in parallel. |
🔵 Tier 2 — Low RiskSmall, isolated change with no API route or data model modifications. Why this tier:
Additional context: 2 file(s) in private internal-tooling packages, excluded from the line count Review process: AI review + quick human skim (target: 5–15 min). Reviewer validates AI assessment and checks for domain-specific concerns. Stats
|
Move the preflight probe ahead of the re-seed block so an unavailable MCP server fails fast instead of after truncating and repopulating scenario tables (--reseed/--live). The preflight is independent of seeding, so running it first avoids the avoidable destructive work. Addresses Greptile review feedback on #2840.
Deep ReviewScope: Intent: Reduce agent over-reliance on ✅ No critical issues found. The happy paths (server instructions surfaced on 🟡 P2 -- recommended
🔵 P3 nitpicks (5)
Reviewers (9): correctness, testing, maintainability, project-standards, kieran-typescript, reliability, adversarial, agent-native, learnings-researcher. Testing gaps:
|
The 'prefer builder tools over raw SQL' list was hand-duplicated across SERVER_INSTRUCTIONS, the clickstack_sql description, and each builder tool's description, and had already drifted (sql.ts omitted the trace tools; event_patterns/event_deltas lacked the reciprocal nudge). Define the builder-tool catalog once in builderCatalog.ts and compose the server instructions, the sql.ts alternatives list, and every builder tool's 'prefer me over SQL' nudge from it. Adds event_patterns and event_deltas nudges that were missing, and a drift-guard test that fails if the catalog and the registered query/trace tools diverge. Addresses Greptile review feedback on #2840.
Replace the catalog module's types/functions/lookup (and the drift-guard test) with three plain constants: BUILDER_TOOLS_LIST, SQL_FALLBACK_CRITERIA, and a single generic PREFER_BUILDER_OVER_SQL_NUDGE reused by every builder tool. Same anti-drift guarantee, far less machinery.
Drop redundant coverage (plain-JSON body, non-2xx, JSON-RPC error, and the preflightMcps ordering wrapper) and use one compact fetch mock helper. Keeps the four behaviors that matter: happy path w/ tool count, zero-tools failure, connection-refused actionable message, and stdio passthrough.
Summary
We've noticed agents increasingly reaching for raw SQL instead of the structured builder query tools — drift that crept in as the tool set grew without a clear, up-front tool-selection policy. Over-using
clickstack_sqlproduces static result tiles instead of the builder tiles users can click into and pivot from, and it's more error-prone.This PR steers agents back toward the builder tools (
clickstack_table/clickstack_timeseries/clickstack_search) and reserves raw SQL for cases the builders genuinely can't express. It also includes an eval-harness fix (MCP reachability preflight) that was needed to validate the steering change with trustworthy A/B evals — see the scoping note below.Why we needed this (HDX-4892)
Production MCP telemetry confirmed the drift. Over the last 30 days of investigation traffic:
clickstack_sqlwas ~73% of all data-querying calls — ~6.5× the next query tool.The root cause was steering, not capability:
instructions— there was no tool-selection policy the agent saw by default.clickstack_sqldescription and was hedged ("ADVANCED: only use this when…"). None of the builder tools asserted primacy over SQL.query_guide/create_dashboardprompts, which a free-form investigation agent never fetches.What changed
MCP steering (
packages/api/src/mcp/)sql.ts— reframedclickstack_sqlas a last-resort tool with an explicit decision rule: single-source aggregations, top-N, time-series, and row browses must use builder tools; SQL is only for JOINs / sub-queries / CTEs / unregistered tables.table.ts,timeseries.ts,search.ts— added a reciprocal "prefer me overclickstack_sql" nudge so the steering isn't one-directional.mcpServer.ts— added a server-levelinstructionstool-selection policy, surfaced on the MCPinitializehandshake so agents see it by default instead of only via an opt-in prompt.Eval harness preflight (
packages/hdx-eval/)harness/preflight.ts: probes every MCP server (initialize+tools/list) before spawning any agents, and theruncommand aborts the batch if a server is unreachable or serves zero tools. Added--no-preflightto bypass.__tests__/preflight.test.ts(SSE + plain-JSON bodies, zero-tools, HTTP error, connection-refused, JSON-RPC error, stdio passthrough).Why the preflight fix was necessary
The first full A/B run came back with all-flat, near-zero scores. On inspection, 0 of 59 runs made a single MCP tool call — the dev API servers had died mid-suite, so Claude Code's MCP client got a dead endpoint, every agent burned its turns on
ToolSearchtrying to discover tools that were never served, and the runs graded as real (but terrible) scores. A dead server silently masquerading as "the model did badly" is a nasty failure mode; the preflight now fails loudly and immediately with an actionable message ("is the API server on this slot running?").How it affects the evals
Re-ran the full
hdx-evalsuite (8 scenarios) branch vsmain, dual-slot (main = slot 98, branch = slot 99), identical seeded data on both, judge =claude-opus-4-7. Combined score (Δ = branch − main). The two scenarios with a "major" runs=3 delta were re-run at runs=5 to rule out variance:Takeaways:
dashboard-build: +37% (judge mean 85% vs 25%), confirmed at runs=5. This is the scenario most sensitive to builder-vs-SQL tile choice — agents now build proper builder tiles instead of dumping raw-SQL tiles.segmented-regressionlooked like a −9% regression at runs=3 but flipped to +4% at runs=5 — it was variance, not a real regression. The two remaining small negatives (−4%, −5%) are within runs=3 noise.Testing
packages/api: MCP query unit tests pass;tsc --noEmitclean;yarn lint:fixclean.packages/hdx-eval:ci:lint(eslint + tsc) clean; new preflight unit suite passes (8 tests).OK (29 tools); dead port → loud abort). Full 8-scenario A/B + runs=5 confirmations executed against live stacks.