Uh oh!
There was an error while loading. Please reload this page.
feat: Add dashboard table onClick to MCP schemas and prompts - #2273
Conversation
The latest updates on your projects. Learn more about Vercel for GitHub.
|
🦋 Changeset detectedLatest commit: e89800d 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 |
🟡 Tier 3 — StandardIntroduces new logic, modifies core functionality, or touches areas with non-trivial risk. Why this tier:
Review process: Full human review — logic, architecture, edge cases. Stats
|
PR ReviewSolid PR — the MCP path now properly mirrors REST v2 validation for onClick link-outs and dashboard-level filters, the schemas are richly documented, and there are ~700 lines of round-trip tests covering both the happy and rejection paths. Feature-flag cleanup is mechanical and complete. Must-fix
Nits (non-blocking)
|
Deep Review✅ No critical issues found. 🟡 P2 -- recommended
🔵 P3 nitpicks (8)
Reviewers (4): ce-correctness-reviewer, ce-testing-reviewer, ce-api-contract-reviewer, ce-maintainability-reviewer. Testing gaps:
|
E2E Test Results✅ All tests passed • 178 passed • 3 skipped • 1213s
Tests ran across 4 shards in parallel. |
brandon-pereira
commented
May 14, 2026
Nice find. The primary issue appears that the existing MCP dashboard schemas were missing filters entirely, so the target dashboard could not be created with filters. I have fixed that, and also added updated prompts/examples to keep the agent from generating filters in links to dashboards without the corresponding filter |
brandon-pereira
left a comment
There was a problem hiding this comment.
LGTM - this is awesome 🥳
PR Review
Otherwise:
|
Uh oh!
There was an error while loading. Please reload this page.
Drew's #2273 shipped `mcpDashboardFilterSchema` + `mcpFiltersParam` with explicit id-management semantics in the description (telling the LLM exactly how to handle filter ids on create vs update). The pre-rebase version of this branch defined `mcpFiltersParam` as `z.array(externalDashboardFilterSchemaWithId.partial({ id: true }))` instead, which loses Drew's id description. Aligning back to main's design keeps the richer guidance the LLM benefits from and avoids overriding a schema another PR shipped upstream four days earlier. The wider `(ExternalDashboardFilter | ExternalDashboardFilterWithId)[]` input type on saveDashboard.ts still ships, so REST/MCP parity is unaffected. `externalDashboardFilterSchemaWithId` is no longer used in schemas.ts; the import is dropped.
Drew's #2273 shipped `mcpDashboardFilterSchema` + `mcpFiltersParam` with explicit id-management semantics in the description (telling the LLM exactly how to handle filter ids on create vs update). The pre-rebase version of this branch defined `mcpFiltersParam` as `z.array(externalDashboardFilterSchemaWithId.partial({ id: true }))` instead, which loses Drew's id description. Aligning back to main's design keeps the richer guidance the LLM benefits from and avoids overriding a schema another PR shipped upstream four days earlier. The wider `(ExternalDashboardFilter | ExternalDashboardFilterWithId)[]` input type on saveDashboard.ts still ships, so REST/MCP parity is unaffected. `externalDashboardFilterSchemaWithId` is no longer used in schemas.ts; the import is dropped.
Drew's #2273 shipped `mcpDashboardFilterSchema` + `mcpFiltersParam` with explicit id-management semantics in the description (telling the LLM exactly how to handle filter ids on create vs update). The pre-rebase version of this branch defined `mcpFiltersParam` as `z.array(externalDashboardFilterSchemaWithId.partial({ id: true }))` instead, which loses Drew's id description. Aligning back to main's design keeps the richer guidance the LLM benefits from and avoids overriding a schema another PR shipped upstream four days earlier. The wider `(ExternalDashboardFilter | ExternalDashboardFilterWithId)[]` input type on saveDashboard.ts still ships, so REST/MCP parity is unaffected. `externalDashboardFilterSchemaWithId` is no longer used in schemas.ts; the import is dropped.
…e tool (#2264) ## Summary Drew shipped the schemas in #2273 (dashboard-level filters, per-tile onClick, drill-down navigation). This PR ships the prompts and examples that teach an MCP-using agent how to use those primitives correctly. Without it, an LLM building a HyperDX dashboard via MCP has the API but doesn't know when or how to call it. ## What lands **`create_dashboard` design checklist.** A ten-rule scannable preamble at the top of the prompt covers RED columns with aliases, per-series `numberFormat` for durations, `groupByColumnsOnLeft` for inventory tables, dashboard-level filters instead of per-tile `where` literals, one-metric-per-tile for metric sources, and containers and tabs for grouping. Replaces the wall-of-JSON canonical example in the old prompt so the model picks the right pattern in one read. **Four verified `dashboard_examples` patterns.** `service_inventory`, `service_detail`, `log_analytics`, `backend_dependencies`. Each carries a "When to use" header and a "Why this shape" note. Each was built and rendered on a live dev stack with realistic synthetic data (90K traces across 8 services, 17K logs with severity distribution) before landing. That verification surfaced two design issues now captured as explicit rules: chart-level `numberFormat` on a table mixing counts and durations formats every value as a duration (per-series `numberFormat` is the fix), and metric tiles take exactly one select item because the renderer destructures `select[0]` and ignores the rest (multi-metric authoring needs one tile per metric). **`query_guide` additions.** New sections for `DASHBOARD FILTERS` (canonical `{ type, name, expression, sourceId, where?, whereLanguage? }` shape), `NUMBER FORMAT` (per-series vs chart-level distinction), and `PER-TILE TYPE CONSTRAINTS` (the metric one-select rule). **`hyperdx_save_dashboard` filters parameter.** Widened the tool's input handling to accept both `ExternalDashboardFilter` and `ExternalDashboardFilterWithId` shapes via the same body schemas the v2 REST handler already uses (`createDashboardBodySchema`, `updateDashboardBodySchema`). MCP and REST surfaces stay in lockstep; the existing `convertExternalFiltersToInternal` helper handles the conversion without a translation layer. **`service_inventory` row-click wiring.** Concrete worked example of how to compose Drew's onClick primitive: the Services table in the `service_inventory` example carries an `onClick` that drills into the partner Service Detail dashboard. Uses `mode: "template"` with the constant template "Service Detail" so the destination resolves by name at click time and there's no order-of-save dependency between the two dashboards. **Post-Claude-run prompt refinements.** Three follow-up commits capture bugs the v1 prompts surfaced when Claude actually authored dashboards against them. The groupBy alias gap (LLM kept emitting `SELECT toStartOfMinute(t) AS time` without aliasing the groupBy column). A broader lucene-gotcha note. Inlined concrete examples for the patterns that were ambiguous on first pass. **Voice pass.** Em-dashes dropped from `buildSourceSummary` and from the dashboard authoring prompt content. Snapshot test guards regressions. ## Co-authorship and rebase The branch composes Drew's MCP `onClick` schemas and prompts commit (`78e72652`) on top of his already-merged #2273. Drew authored that commit; the `service_inventory` row-click wiring on top is the concrete demonstration of the foundation he built. Drew's commit is preserved in the chain to keep attribution intact. Drew's `chore: Remove IS_DASHBOARD_LINKING_ENABLED toggle` dropped during rebase as already-upstream via #2273. ## Schema design note `mcpDashboardFilterSchema` and `mcpFiltersParam` use the inline-schema design from main (the version Drew's #2273 shipped). An earlier version of this branch defined `mcpFiltersParam` as `z.array(externalDashboardFilterSchemaWithId.partial({ id: true }))` to reuse the canonical REST schema, but that lost Drew's id-management description (concrete LLM guidance on filter-id handling for create vs update flows). After rebase, the refactor commit aligned back to main's design to preserve those semantics. The wider input type on `saveDashboard.ts` still ships, so REST/MCP type parity is unaffected. ## Test plan - [x] `yarn workspace @hyperdx/api tsc --noEmit` clean - [x] `yarn workspace @hyperdx/api lint` clean (prettier + eslint + tsc + openapi lint) - [x] `prose-lint.py` clean on every changed file - [x] Built every example dashboard on a live dev stack and confirmed every tile renders with realistic synthetic data - [x] Snapshot tests guard: design checklist present, four patterns listed, each example carries "When to use", zero em-dashes in any prompt string - [x] Filter round-trip via MCP: save with two filters, get back, update with one renamed filter, fetch, assert shape - [x] Filter rejection: save with bogus `sourceId` returns 4xx - [x] Backward compat: save without `filters` returns `filters: []` - [ ] Green CI across lint, unit, integration, knip, e2e shards 1-4, ClickHouse Bundle Build, Vercel (handled on push) Co-authored-by: Drew Davis <6097246+pulpdrew@users.noreply.github.com>

Summary
This PR updates the MCP schemas and prompts to include dashboard table custom on-click behaviors.
I also remove the IS_DASHBOARD_LINKING_ENABLED toggle, as this is the last change that is part of the dashboard linking MVP.
Screenshots or video
Given the prompt
Claude is able to one-shot a catalog which correctly links to per-service dashboards when clicking the RED metrics table rows:
How to test on Vercel preview
This can be tested locally with the MCP server.
References