feat(mcp): docker and host-agent tools; TLS and crash-loop cutoffs in Settings
MCP-5 of milestone 461 "MCP server: Steward as an LLM-inspectable, LLM-operable hub". Contributed through each plugin's register_mcp_tools hook, so they exist only while the plugin is enabled. docker (viewer, read-only): - list_containers: filter by host, status, compose project, or problem (restarting, crash_looping, unhealthy, oom_killed, exited_nonzero); problems sort first and each row names what is wrong. - get_container: state, CPU/memory history across the raw/hourly seam, lifecycle events. A unique part of a swarm task name is enough. - list_swarm_services: replicas per node (agent-less placements as ghosts) and the swarm's nodes. - search_container_logs: text (% and _ literal), stream, range, context lines, paging by a full-precision `before` cursor; over-long lines keep both ends and say how much of the middle was cut. - get_docker_disk_usage: bytes per host, reclaimable first. - mcp_overview: failing containers and under-replicated swarm services join get_overview. host_agent: list_agents (reporting / stale / pending, version, OS), and an overview finding for agents registered over 30 minutes ago that never checked in. Settings > Thresholds gains the TLS-expiry warn/crit days (were 14/3 in code) and the crash-loop deaths-per-hour count (3), which get_overview and the docker tools now read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -19,7 +19,6 @@ from ._shape import age_s, db_session, iso, now_utc, rnd
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
TLS_WARN_DAYS = 14
|
||||
FINDINGS_CAP = 50
|
||||
_SEVERITY = {"critical": 0, "warning": 1}
|
||||
|
||||
@@ -39,7 +38,11 @@ def rank(findings: list[dict]) -> list[dict]:
|
||||
-(f.get("age_s") or 0), f.get("subject", "")))
|
||||
|
||||
|
||||
def monitor_findings(monitors, states: dict, host_names: dict, now) -> list[dict]:
|
||||
def monitor_findings(monitors, states: dict, host_names: dict, now,
|
||||
tls: dict | None = None) -> list[dict]:
|
||||
"""Failing monitors, and HTTPS certificates inside the TLS window
|
||||
(`tls` is THRESHOLDS["tls_days"]: warn/crit days left)."""
|
||||
tls = tls or {"warn": 14, "crit": 3}
|
||||
out = []
|
||||
for m in monitors:
|
||||
if not m.enabled:
|
||||
@@ -58,9 +61,9 @@ def monitor_findings(monitors, states: dict, host_names: dict, now) -> list[dict
|
||||
next=f'get_monitor(monitor="{m.id}")'))
|
||||
elif latest.tls_expires_at is not None:
|
||||
days = (latest.tls_expires_at - now).total_seconds() / 86400.0
|
||||
if days <= TLS_WARN_DAYS:
|
||||
if days <= tls["warn"]:
|
||||
out.append(finding(
|
||||
"critical" if days <= 3 else "warning", "tls_expiring", m.name,
|
||||
"critical" if days <= tls["crit"] else "warning", "tls_expiring", m.name,
|
||||
f"TLS certificate for {m.target} expires in {days:.1f} days",
|
||||
ids=ids, next=f'get_monitor(monitor="{m.id}")'))
|
||||
return out
|
||||
@@ -151,9 +154,9 @@ async def get_overview() -> dict:
|
||||
|
||||
Returns `findings` ranked critical first, then longest-standing first:
|
||||
failing monitors (with the error and since when), firing, pending and
|
||||
still-breaching acknowledged alerts, TLS certificates expiring within
|
||||
14 days, host agents that stopped reporting, hosts over the CPU, memory or
|
||||
disk thresholds set under Settings → Thresholds, Ansible runs that failed
|
||||
still-breaching acknowledged alerts, TLS certificates near expiry, host
|
||||
agents that stopped reporting, hosts over their CPU, memory or disk
|
||||
limits (all cutoffs are set under Settings → Thresholds), Ansible runs that failed
|
||||
in the last 24h, and anything enabled plugins add (unhealthy containers,
|
||||
for one). Each finding carries the ids to follow and `next`, the call
|
||||
that shows its evidence.
|
||||
@@ -180,9 +183,10 @@ async def get_overview() -> dict:
|
||||
)).scalars())
|
||||
windows = await alert_svc.list_maintenance_windows(db)
|
||||
|
||||
findings = (monitor_findings(monitors, states, host_names, now)
|
||||
thresholds = app.config.get("THRESHOLDS") or {}
|
||||
findings = (monitor_findings(monitors, states, host_names, now, thresholds.get("tls_days"))
|
||||
+ alert_findings(active)
|
||||
+ agent_findings(hosts, agent, app.config.get("THRESHOLDS") or {})
|
||||
+ agent_findings(hosts, agent, thresholds)
|
||||
+ run_findings(runs))
|
||||
plugin_found, unavailable = await _plugin_findings(getattr(app, "mcp_server", None))
|
||||
findings = rank(findings + plugin_found)
|
||||
|
||||
Reference in New Issue
Block a user