Skip to content

feat(search-events): support time series via events-stats - #1305

Merged
betegon merged 3 commits into
mainfrom
feat/search-events-timeseries
Sep 14, 2026
Merged

betegon merged 3 commits into
mainfrom
feat/search-events-timeseries

Conversation

@betegon

@betegon betegon commented Sep 11, 2026

Copy link
Copy Markdown
Member

Problem

search_events couldn't answer "over time" questions. Depending on phrasing the embedded agent either:

  • failed outright — "how many errors per hour" burned the step budget and returned AI_NoOutputGeneratedError / "could not construct a valid query" (the MCP-SERVER-F1Z failure mode), or
  • gave a wrong answer — "trend by day" / "errors over time" degraded to a single total, with the prompt literally telling the model time series were "not currently supported".

What this does

Adds first-class time series via the events-stats endpoint. "X per hour / per day / trend / over time" now returns a bucketed series.

The agent decides the granularity — it's never a required input:

  • User names a granularity ("per hour") → interval: "1h".
  • User just asks "over time" → interval: null, so Sentry picks a sensible bucket for the range (mirrors get_interval_from_range; it rejects intervals that would make too many buckets).

Before / after (verified live with the real model, gpt-5.6-luna)

Query Before (main) After
errors per hour last 24h AI_NoOutputGeneratedError — fails ✅ series, interval 1h
error trend by day last 30d ⚠️ "not supported" → single total ✅ series, interval 1d
errors over time last 7d ⚠️ "not supported" → single total ✅ series, interval auto
how many errors today (control) count() count() (unchanged)

Related Sentry issues

  • MCP-SERVER-F1ZAI_NoOutputGeneratedError (~60k users). "Over time" queries were one source of these no-output failures (see the before/after above). Already resolved by the environment-grounding fix (fix(search-events): ground the agent in the org's real environments #1300); this removes the time-series subset of the underlying cause so those requests return data instead of dead-ending.

Changes

  • api-client: getEventsTimeSeries (→ /events-stats/, omits interval when null) + EventsStatsResponseSchema.
  • agent: timeSeries { yAxis, interval } on the output schema; prompt now instructs the model to use it (replacing the old "not supported → return an error" guidance).
  • handler: routes timeSeries to getEventsTimeSeries + a new formatter; skips the sort-in-fields check for it.
  • formatter: formatTimeSeriesResults — bucketed table with total and peak.
  • spec: Time Series section in docs/specs/search-events.md.

Test plan

  • pnpm --filter @sentry/mcp-core tsc — clean; biome lint — clean; full mcp-core suite — 1419 passed.
  • New unit test: agent returns timeSeries → handler calls events-stats → formatted buckets/total/peak.
  • Verified end-to-end locally with the real embedded agent (OpenRouter gpt-5.6-luna) against mocked Sentry — the four queries above behaved exactly as the table shows.

🤖 Generated with Claude Code

"X over time / per hour / per day / trend" now returns a bucketed series instead of failing. The embedded agent decides the granularity: it sets timeSeries.interval when the user names one ("per hour" -> 1h), otherwise leaves it null so Sentry picks a sensible bucket for the range. Never a required input.

- api-client: getEventsTimeSeries (events-stats) + response schema
- agent: timeSeries {yAxis, interval} output + prompt guidance (previously returned a 'not supported' error)
- handler: time series branch -> events-stats + formatter
- formatter: bucketed table with total/peak

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread packages/mcp-core/src/tools/support/search-events/formatters.ts Outdated
…non-additive aggregates

Cleanup to follow existing search_events patterns, plus a correctness fix
from PR review:

- Match the sibling result formatters: title the timeseries output
  `# Search Results for "<query>"` (the previously-unused `inputQuery`)
  and move the yAxis into a `## <yAxis> over time` section heading.
- Reuse the existing `formatExecutedTimeRange` helper instead of a
  bespoke time-range branch.
- Move the `EventsStatsResponseSchema` import back into alphabetical order.
- Document why the timeseries branch deliberately skips `validateEventsSearch`
  (it validates the /events/ discover shape, not /events-stats/).
- Fix: `Total` summed buckets for every yAxis, which is wrong for
  non-additive aggregates (count_unique, avg, percentiles, rates). Only
  render Total for additive aggregates (count(), sum(...)); Peak stays.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread packages/mcp-core/src/tools/catalog/search-events.ts
@betegon
betegon marked this pull request as ready for review September 12, 2026 06:19
Warden (mcp-audit) flagged that the client-visible search_events description
still said "TWO query types" after the events-stats timeseries path landed,
so tools/list never told clients that "errors per hour" / "over time" works.

- Add the TIME SERIES mode (and an example) to the tool description.
- Trim offsetting verbosity to stay under the 2048-char budget: tighten the
  intro line, merge the two org/project hints, drop the redundant
  level:error example.
- Regenerate toolDefinitions.json and skillDefinitions.json.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@betegon
betegon merged commit 55a62a2 into main Sep 14, 2026
20 checks passed
@betegon
betegon deleted the feat/search-events-timeseries branch September 14, 2026 14:07
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