Skip to content

feat(mcp): preview_alert_rule dry-runs a rule before it is saved - #1207

Merged
Makisuo merged 2 commits into
fix/alert-check-skip-reasonsfrom
feat/mcp-preview-alert-rule
Oct 2, 2026
Merged

Makisuo merged 2 commits into
fix/alert-check-skip-reasonsfrom
feat/mcp-preview-alert-rule

Conversation

@Makisuo

@Makisuo Makisuo commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

Stack 3/5. An agent writing a raw-SQL rule could only learn what it does by saving it and reading checks later, which is how a rule that matches nothing ships looking quiet.

New read-only MCP tool preview_alert_rule:

  • Takes a saved rule (rule_id, optionally with overrides) or a create_alert_rule definition, plus a window.
  • Replays it through AlertsService.previewRule. Per window: value, sample count, verdict, and for skips the reason. It also reports when the rule would have fired.
  • Warns when every window had no data or fell below the minimum sample count, plus the PR 1 samples warning.

Preview points carry skipReason (skip_reason on v2). create/update/get_alert_rule and list_alert_checks link to the tool. Registered in the output catalog, web tool metadata, MCP instructions and docs.

Test: alert-tools (draft and saved-rule cases), registry contract, previewRule suite.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Summary by CodeRabbit

  • New Features
    • Added a read-only alert rule preview that replays saved or draft rules over a selected time range, showing window results, firing intervals, and warnings.
    • Preview results now explain why a window was skipped, including when no data was available.
    • Alert management guidance now recommends previewing rules before saving or adjusting them.
  • Documentation
    • Added the preview tool to the alerting tools reference.

@coderabbitai

coderabbitai Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 9a392335-bd50-4ae3-9aee-3d25f7b15791

📥 Commits

Reviewing files that changed from the base of the PR and between 6a90ec1 and 4259130.

📒 Files selected for processing (20)
  • apps/ai/src/mcp/lib/alert-rules.ts
  • apps/ai/src/mcp/resources/instructions.ts
  • apps/ai/src/mcp/tools/__tests__/alert-tools.test.ts
  • apps/ai/src/mcp/tools/create-alert-rule.ts
  • apps/ai/src/mcp/tools/get-alert-rule.ts
  • apps/ai/src/mcp/tools/list-alert-checks.ts
  • apps/ai/src/mcp/tools/preview-alert-rule.ts
  • apps/ai/src/mcp/tools/registry.ts
  • apps/ai/src/mcp/tools/update-alert-rule.ts
  • apps/api/src/routes/v2/alert-rules.http.ts
  • apps/ios/Packages/MapleAPI/Sources/MapleAPI/openapi.json
  • apps/landing/src/content/docs/reference/mcp.md
  • apps/web/src/components/ai-elements/tool-metadata.ts
  • apps/web/src/lib/alerts/form-utils.ts
  • packages/backend/src/services/alerts/AlertsService.test.ts
  • packages/backend/src/services/alerts/AlertsService.ts
  • packages/domain/src/http/alerts.ts
  • packages/domain/src/http/v2/alert-rules.ts
  • packages/domain/src/mcp-outputs/alerts.ts
  • packages/domain/src/mcp-outputs/catalog.ts
 ______________________________________________
< Regex can do anything... badly. Let me help. >
 ----------------------------------------------
  \
   \   (\__/)
       (•ㅅ•)
       /   づ
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@maple-review-bot

maple-review-bot Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Maple review

🔴 Confidence 2/5 · risky as written
Tool is read-only and tenant-scoped, but its central warning misses grouped and raw-SQL previews and its structured output is uncapped.
quality 80/100 · 2 warnings · tests partial · risk medium · 1/1 new units observable

Warning

This review ended early; what follows is what it established.

Adds a read-only preview_alert_rule MCP tool that replays a saved rule or a create_alert_rule draft through AlertsService.previewRule, and threads skipReason out through the MCP output, the v2 route and the web mapper. Two defects stand: the tool's no-data warning misses the grouped case it exists for, and its point list is uncapped. apps/ios/.../openapi.json was not read.

  • preview_alert_rule replays a rule per window and reports verdicts, firing spans and warnings
  • AlertRulePreviewPoint now carries skipReason (skip_reason on v2, mapped in the web client)
  • create/update/get_alert_rule and list_alert_checks now link to the new tool
  • create/update-alert-rule schemas and builders are exported for reuse

Findings

🟠 Warning · F1 · "Every window had no data" never fires for a grouped rule

correctness · apps/ai/src/mcp/tools/preview-alert-rule.ts:93-101

The guard windows > 0 skips the warning whenever the preview returned no series, and that is the normal shape of a rule that matched nothing: AlertsService only synthesizes an empty series for ungrouped single-service plans (packages/backend/src/services/alerts/AlertsService.ts:1304), and every raw_query rule counts as grouped (AlertRulesModel isGroupedPlan). So an agent previewing a raw-SQL or group_by rule whose query matches nothing gets groups: [], no warning, and no way to tell it apart from a range the tool clamped — the exact case the tool was added for.

	if (noData === windows) {
🟠 Warning · F2 · Unbounded points array in the structured output

performance · apps/ai/src/mcp/tools/preview-alert-rule.ts:207-217

Every window of every group is copied into structuredContent with no cap: the service bounds windows at MAX_PREVIEW_BUCKETS = 1500 (AlertsService.ts:294) but nothing bounds groups, and a spec rule grouping on a high-cardinality attribute can return thousands. A 24h preview of a 5-minute-window rule grouped by attr.http.url is groups × 288 point objects on the wire, to the MCP client and to the chat UI's __maple_ui payload, while the text the model reads is capped at SHOWN_GROUPS/SHOWN_POINTS.

Cap the rows the output carries — the first `SHOWN_GROUPS` groups' points, say — and report the cut in `output` (`truncation`/`omitted`) the way the text already does, or take a `limit` parameter like the other list tools.
🤖 Prompt to fix all 2 findings with an AI agent
Findings from an automated review of commit 24e1cbd0e4426edc0929c9ec9169724a9f520ca6. Verify each one against the current code before changing anything, fix only those that still apply, and keep each fix to the lines it names.

---

F1 · Warning · correctness · apps/ai/src/mcp/tools/preview-alert-rule.ts:93-101
"Every window had no data" never fires for a grouped rule
The guard `windows > 0` skips the warning whenever the preview returned no series, and that is the normal shape of a rule that matched nothing: `AlertsService` only synthesizes an empty series for ungrouped single-service plans (`packages/backend/src/services/alerts/AlertsService.ts:1304`), and every `raw_query` rule counts as grouped (`AlertRulesModel` `isGroupedPlan`). So an agent previewing a raw-SQL or `group_by` rule whose query matches nothing gets `groups: []`, no warning, and no way to tell it apart from a range the tool clamped — the exact case the tool was added for.
Replace those lines with:
	if (noData === windows) {

---

F2 · Warning · performance · apps/ai/src/mcp/tools/preview-alert-rule.ts:207-217
Unbounded `points` array in the structured output
Every window of every group is copied into `structuredContent` with no cap: the service bounds windows at `MAX_PREVIEW_BUCKETS = 1500` (`AlertsService.ts:294`) but nothing bounds groups, and a spec rule grouping on a high-cardinality attribute can return thousands. A 24h preview of a 5-minute-window rule grouped by `attr.http.url` is groups × 288 point objects on the wire, to the MCP client and to the chat UI's `__maple_ui` payload, while the text the model reads is capped at `SHOWN_GROUPS`/`SHOWN_POINTS`.
Suggested fix: Cap the rows the output carries — the first `SHOWN_GROUPS` groups' points, say — and report the cut in `output` (`truncation`/`omitted`) the way the text already does, or take a `limit` parameter like the other list tools.
What was checked
  • buildUpdatedRequest keeps every field the preview params omit, so saved-rule overrides preserve stored config (update-alert-rule.ts:100-132)
  • The three skip reasons counted by summarizeGroups are the only ones applyEvaluationLogic produces
  • The text output is bounded by SHOWN_GROUPS/SHOWN_POINTS and by the shared 50 KiB truncateToolOutput
Observability coverage: 1 of 1 changes observable
Change Kind Observable Evidence
preview_alert_rule MCP tool inbound entrypoint yes Runs under the executor span that annotates maple.mcp.tool.arguments (apps/ai/src/mcp/tools/registry.ts); warehouse read is spanned by the query engine
Files not reviewed (1)

The review ended before it read these diffs, so nothing above vouches for them.

  • apps/ios/Packages/MapleAPI/Sources/MapleAPI/openapi.json

24e1cbd · Updated on every push. Reply "won't fix" to dismiss a finding, or mention @maple-review-bot to ask about one.

@maple-review-bot maple-review-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 inline notes from Maple's review. The score and summary are in the review comment above.

Comment thread apps/ai/src/mcp/tools/preview-alert-rule.ts
thresholdUpper: preview.thresholdUpper,
minimumSampleCount: ruleRequest.minimumSampleCount ?? 0,
groups,
points: preview.series.flatMap((series) =>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Unbounded points array in the structured output

F2 · Warning · performance

Every window of every group is copied into structuredContent with no cap: the service bounds windows at MAX_PREVIEW_BUCKETS = 1500 (AlertsService.ts:294) but nothing bounds groups, and a spec rule grouping on a high-cardinality attribute can return thousands. A 24h preview of a 5-minute-window rule grouped by attr.http.url is groups × 288 point objects on the wire, to the MCP client and to the chat UI's __maple_ui payload, while the text the model reads is capped at SHOWN_GROUPS/SHOWN_POINTS.

Cap the rows the output carries — the first `SHOWN_GROUPS` groups' points, say — and report the cut in `output` (`truncation`/`omitted`) the way the text already does, or take a `limit` parameter like the other list tools.
🤖 Prompt to fix with an AI agent
In `apps/ai/src/mcp/tools/preview-alert-rule.ts:207-217`: Unbounded `points` array in the structured output.

Every window of every group is copied into `structuredContent` with no cap: the service bounds windows at `MAX_PREVIEW_BUCKETS = 1500` (`AlertsService.ts:294`) but nothing bounds groups, and a spec rule grouping on a high-cardinality attribute can return thousands. A 24h preview of a 5-minute-window rule grouped by `attr.http.url` is groups × 288 point objects on the wire, to the MCP client and to the chat UI's `__maple_ui` payload, while the text the model reads is capped at `SHOWN_GROUPS`/`SHOWN_POINTS`.

Suggested fix: Cap the rows the output carries — the first `SHOWN_GROUPS` groups' points, say — and report the cut in `output` (`truncation`/`omitted`) the way the text already does, or take a `limit` parameter like the other list tools.

Verify the problem exists at that location before changing it, and keep the fix to those lines.

@Makisuo
Makisuo force-pushed the feat/mcp-preview-alert-rule branch from 24e1cbd to 4259130 Compare October 2, 2026 17:48
@maple-review-bot

maple-review-bot Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Note

A newer push replaced 4259130 before its review finished. The latest commit is reviewed in a new comment.

@Makisuo
Makisuo added this pull request to stack #1210 October 2, 2026 17:50
@Makisuo
Makisuo force-pushed the feat/mcp-preview-alert-rule branch from 4259130 to 3308f27 Compare October 2, 2026 17:53
@maple-review-bot

maple-review-bot Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Maple review

🟡 Confidence 3/5 · needs attention
The new behavior is tested, but the structured output stays unbounded in group count, though windows per group are now capped.
quality 90/100 · 1 warning · tests covered · risk medium · 1/1 new units observable

Warning

This review ended early; what follows is what it established.

Adds a read-only preview_alert_rule MCP tool that replays a saved or draft rule through AlertsService.previewRule, carries skipReason on preview points, and charts a no-data series when a query matches nothing. Merge is safe; apps/ios/Packages/MapleAPI/Sources/MapleAPI/openapi.json is a generated spec and was not read.

  • New read-only preview_alert_rule tool replays a saved rule or a create_alert_rule definition
  • AlertRulePreviewPoint carries skipReason; v2 exposes it as skip_reason
  • previewRule now charts a no-data series when a query matches nothing
  • CreateAlertRuleParameters and buildUpdatedRequest exported for reuse

Still open from earlier reviews

Fixed since the last review

  • ✅ F1 · "Every window had no data" never fires for a grouped rule
What was checked
  • The empty-range seed key matches evaluateRule's translation for grouped and raw-SQL plans (AlertRuleModel.ts:189)
  • toIso only sees canonical YYYY-MM-DD HH:mm:ss bounds (datetime.ts:152)
  • Raw-SQL previews still pass requireAdmin (AlertsService.ts:1189)
Observability coverage: 1 of 1 changes observable
Change Kind Observable Evidence
MCP tool preview_alert_rule inbound entrypoint yes Effect.fn("McpTool.previewAlertRule") span with maple.alert.signal_type / result.groupCount (preview-alert-rule.ts:126,194)
Files not reviewed (1)

The review ended before it read these diffs, so nothing above vouches for them.

  • apps/ios/Packages/MapleAPI/Sources/MapleAPI/openapi.json

3308f27 · Updated on every push. Reply "won't fix" to dismiss a finding, or mention @maple-review-bot to ask about one.

An agent writing a raw-SQL rule had no way to see what it would do short
of creating it and reading checks later, which is how a rule that matches
nothing ships looking quiet.

preview_alert_rule replays a saved rule (rule_id, optionally with
overrides) or a create_alert_rule definition over past data through
AlertsService.previewRule: per window the value, sample count and verdict,
skips with their reason, and when it would have fired. It warns when every
window had no data or fell below the minimum sample count. Preview points
now carry skipReason (skip_reason on the v2 API).

create/update/get_alert_rule and list_alert_checks point at it.
…hing

The scheduler turns an empty result into one no-data observation per tick
(per service in multi-service mode), but the preview only charted that for
ungrouped plans, so a raw-SQL rule matching nothing previewed as no series
and preview_alert_rule could never warn about it. preview_alert_rule also
keeps the latest 200 windows per group in its structured output, labels
the window column by its start, and tailors the advice once the range was
clamped.
@Makisuo
Makisuo force-pushed the feat/mcp-preview-alert-rule branch from 3308f27 to 3bb3e2b Compare October 2, 2026 17:56
@maple-review-bot

maple-review-bot Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Maple review

🟡 Confidence 3/5 · needs attention
The one remaining defect is F2: points is now capped per group but still scales with the unbounded group count, which the author should look at.
quality 90/100 · 1 warning · tests covered · risk medium · 2/2 new units observable

Adds the read-only preview_alert_rule MCP tool, threads skipReason through the preview point schemas (v1/v2/MCP output) and seeds a no-data series when a preview matches nothing. Safe to merge apart from the still-open unbounded structured output.

  • preview_alert_rule replays a saved rule or a create_alert_rule draft over a window
  • AlertsService.previewRule emits AlertRulePreviewPoint.skipReason and seeds an empty series
  • skip_reason added to the v2 preview schema and the MCP output schema
  • create/update/get_alert_rule, list_alert_checks and the instructions link to the preview tool

Still open from earlier reviews

What was checked
  • Empty-result seed keys match storage vocabulary: toStorageGroupKey (AlertRuleModel.ts:189) yields "all" for raw_sql/grouped and __total__ for ungrouped, as evaluateRule does (AlertsService.ts:…
  • Multi-service seed keys equal perServiceRules' group keys (AlertRuleModel.ts:225), the same keys the scheduler stores
  • buildUpdatedRequest never reads params.template (update-alert-rule.ts:100), so the 'template is ignored with rule_id' doc holds
Observability coverage: 2 of 2 changes observable
Change Kind Observable Evidence
MCP tool preview_alert_rule (inbound entrypoint) entrypoint yes handler is Effect.fn("McpTool.previewAlertRule") and annotates org, signal type and group count (preview-alert-rule.ts:194); dispatcher adds maple.mcp.tool
Preview warehouse evaluation (spec and raw-SQL reads) outbound yes runs through queryEngine.evaluateSeries / computeAlertBuckets, which annotate alert.preview.buckets and execute via the existing warehouse client (AlertsService.ts:1235, query-engine.ts:2362)

3bb3e2b · Updated on every push. Reply "won't fix" to dismiss a finding, or mention @maple-review-bot to ask about one.

@Makisuo
Makisuo merged commit 3966ffd into main Oct 2, 2026
41 of 42 checks passed
@Makisuo
Makisuo deleted the feat/mcp-preview-alert-rule branch October 2, 2026 18:00
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