Coverage for gco_mcp/tools/monitoring.py: 100.00%
43 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-30 21:22 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-30 21:22 +0000
1"""Cluster observability MCP tools (thin wrappers over `gco monitoring`).
3Mirrors :mod:`tools.analytics`: read-only tools are tagged ``safe``, cdk.json
4toggles and user creation are ``low-risk``, and user deletion is ``destructive``
5and gated behind ``GCO_ENABLE_DESTRUCTIVE_OPERATIONS``. There is intentionally no
6``monitoring open`` tool — that is an interactive, long-running port-forward, not
7a request/response operation.
8"""
10import asyncio
12import cli_runner
13from audit import audit_logged
14from server import mcp
17@mcp.tool(tags={"safe", "monitoring"})
18@audit_logged
19async def monitoring_status() -> str:
20 """`gco monitoring status` — show the cluster observability toggle + config."""
21 return await asyncio.to_thread(cli_runner._run_cli, "monitoring", "status")
24@mcp.tool(tags={"safe", "monitoring"})
25@audit_logged
26async def monitoring_users_list() -> str:
27 """`gco monitoring users list` — list Grafana users via the admin API.
29 Reaches Grafana over a `gco monitoring open` port-forward; the admin
30 credential is read from the chart-generated Secret (or $GCO_GRAFANA_ADMIN_PASSWORD).
31 """
32 return await asyncio.to_thread(cli_runner._run_cli, "monitoring", "users", "list")
35# =============================================================================
36# Mutating tools (low-risk)
37# =============================================================================
40@mcp.tool(tags={"low-risk", "monitoring"})
41@audit_logged
42async def enable_monitoring() -> str:
43 """`gco monitoring enable` — flip cluster observability on in cdk.json.
45 Note: this only edits the cdk.json toggle. It does not take effect until
46 ``gco stacks deploy`` (or deploy-all) reinstalls kube-prometheus-stack.
47 """
48 return await asyncio.to_thread(cli_runner._run_cli, "monitoring", "enable", "-y")
51@mcp.tool(tags={"low-risk", "monitoring"})
52@audit_logged
53async def disable_monitoring() -> str:
54 """`gco monitoring disable` — flip cluster observability off in cdk.json.
56 Note: this only edits the cdk.json toggle. The in-cluster stack is removed
57 on the next ``gco stacks deploy``.
58 """
59 return await asyncio.to_thread(cli_runner._run_cli, "monitoring", "disable", "-y")
62@mcp.tool(tags={"low-risk", "monitoring"})
63@audit_logged
64async def monitoring_user_add(username: str, password: str, email: str | None = None) -> str:
65 """`gco monitoring users add` — create a Grafana user via the admin API.
67 Args:
68 username: Grafana login for the new user.
69 password: Password to set for the new user.
70 email: Optional email address for the new user.
71 """
72 args = ["monitoring", "users", "add", "--username", username, "--password", password]
73 if email:
74 args += ["--email", email]
75 return await asyncio.to_thread(cli_runner._run_cli, *args)
78# =============================================================================
79# Destructive tools — gated by GCO_ENABLE_DESTRUCTIVE_OPERATIONS
80# =============================================================================
83import contextlib # noqa: E402
85from feature_flags import FLAG_DESTRUCTIVE_OPERATIONS, is_enabled # noqa: E402
88async def _ctx_warning(message: str) -> None:
89 """Emit ``ctx.warning(...)`` from inside a tool body, no-op when no Context."""
90 try:
91 from fastmcp.server.dependencies import get_context
93 ctx = get_context()
94 except Exception:
95 return
96 with contextlib.suppress(Exception):
97 await ctx.warning(message)
100if is_enabled(FLAG_DESTRUCTIVE_OPERATIONS):
102 @mcp.tool(tags={"destructive", "monitoring"})
103 @audit_logged
104 async def monitoring_user_remove(username: str) -> str:
105 """[gated by GCO_ENABLE_DESTRUCTIVE_OPERATIONS] destructive.
107 `gco monitoring users remove` — delete a Grafana user via the admin
108 API. Cannot be undone — the user and their Grafana-owned resources are
109 permanently removed.
111 Args:
112 username: Grafana login or email to remove.
113 """
114 await _ctx_warning(f"Removing Grafana user {username!r} — this cannot be undone.")
115 return await asyncio.to_thread(
116 cli_runner._run_cli,
117 "monitoring",
118 "users",
119 "remove",
120 "--username",
121 username,
122 "--yes",
123 )