Skip to content

[add] MCP tools for the alerting engine - #113

Merged
malickyeu merged 1 commit into
mainfrom
feat/mcp-alert-tools
Jul 31, 2026
Merged

[add] MCP tools for the alerting engine#113
malickyeu merged 1 commit into
mainfrom
feat/mcp-alert-tools

Conversation

@malickyeu

Copy link
Copy Markdown
Contributor

Summary

MCP knew nothing about alerting. Three tools close that:

  • list_alerts — the history, with the same filters the UI has (severity,
    lifecycle kind, container, rule, message text).
  • active_alert_conditions — what is over threshold right now, and for how
    long each has been going.
  • acknowledge_alert — record that a human has seen one.

The first two are separate on purpose. Now that an alert is a condition with a
lifetime rather than a line reprinted every cycle, "what happened" and "what is
wrong now"
are different questions. An assistant that asks the first when it
meant the second will confidently report a problem that resolved an hour ago, so
the tool descriptions say which is which and point diagnosis at the live one.

Type of change

  • Bug fix
  • New feature
  • Docs only
  • Refactor / chore

Checklist

  • go test -short ./... and go vet ./... pass
  • gofmt gate is clean (gofmt -l $(git ls-files '*.go') after staging)
  • Frontend type-checks — N/A (no UI changed)
  • Rebuilt and committed web/dist — N/A
  • Added/updated tests for the change
  • Updated docs/ and added a CHANGELOG.md entry

Notes for reviewers

The security shape here is one the existing systemic tests could not catch.
TestEveryToolConsultsTheAccessGate and TestEveryToolRespectsTokenScope call
each tool with an explicit host_id. list_alerts is an aggregate: called with
no host argument, the gate is consulted once, passes (the local daemon is always
in reach), and the result set would span every host. So host scoping goes into
the query
via AlertQuery.HostIDs, the same way the REST feed does it, and this
PR adds its own pentests.

Two things the tests found, both worth stating:

  1. scopedHostIDs missed admins. They bypass roles, so they have no grants,
    and ReachableHosts reports "no hosts" for them — which would have hidden every
    remote host's alerts from the one principal allowed to see all of them. The REST
    side checks IsAdmin() before ReachableHosts for exactly this reason; I had
    not. Caught because a test failed for a reason I first assumed was the test's
    fault — it was half the test's fault and half a real bug.

  2. The first version of the pentest was vacuous. It asserted on
    scopedHostIDs and on a store query built by the test itself, so deleting the
    scoping from listAlerts changed nothing and it still passed. It now calls the
    tool, and fails under that mutation with
    SECURITY: list_alerts returned an alert from an unreachable host. That is the
    fifth vacuous test caught by mutation testing in this line of work — the pattern
    is always the same: the test restates the logic instead of exercising the caller.

acknowledge_alert is a write even though it changes nothing about a
container. Acknowledging is an attributed claim that somebody looked, so a
read-only user or token is refused, and it is audited as mcp.alert.ack.

Not included: anything that would widen the MCP surface — no rule editing, no
silencing. Both are roadmap items and both deserve their own decision.

list_alerts, active_alert_conditions and acknowledge_alert. The first two
are separate deliberately: now that an alert is a condition with a
lifetime, 'what happened' and 'what is wrong now' are different
questions, and an assistant that asks the first when it meant the second
reports a problem that resolved an hour ago.

Host scoping goes INTO the query. An aggregate read cannot be authorised
by checking one host_id — with no host argument, list_alerts would
otherwise return every host's alerts to a token scoped to one.

Two findings from writing the tests:

- scopedHostIDs missed admins. They bypass roles, so they have no grants
  and ReachableHosts reports no hosts for them — which would have hidden
  every remote alert from the one principal allowed to see all of them.
  The REST side checks IsAdmin first for exactly this reason.
- The first pentest asserted on the helper and the store query, so
  deleting the scoping from listAlerts itself changed nothing and the
  test still passed. It now calls the tool.
Copilot AI review requested due to automatic review settings July 31, 2026 17:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@malickyeu
malickyeu merged commit 7c66aa3 into main Jul 31, 2026
3 checks passed
@malickyeu
malickyeu deleted the feat/mcp-alert-tools branch July 31, 2026 18:24
@malickyeu malickyeu mentioned this pull request Aug 3, 2026
10 tasks
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.

2 participants