proxy-aiops 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. mcp_server/__init__.py +1 -0
  2. mcp_server/_shared.py +108 -0
  3. mcp_server/server.py +40 -0
  4. mcp_server/tools/__init__.py +1 -0
  5. mcp_server/tools/analysis.py +170 -0
  6. mcp_server/tools/certs.py +29 -0
  7. mcp_server/tools/configread.py +50 -0
  8. mcp_server/tools/routes.py +52 -0
  9. mcp_server/tools/services.py +74 -0
  10. mcp_server/tools/status.py +47 -0
  11. mcp_server/tools/traffic.py +36 -0
  12. mcp_server/tools/undo.py +121 -0
  13. mcp_server/tools/writes.py +265 -0
  14. proxy_aiops/__init__.py +14 -0
  15. proxy_aiops/cli/__init__.py +5 -0
  16. proxy_aiops/cli/_common.py +78 -0
  17. proxy_aiops/cli/_root.py +68 -0
  18. proxy_aiops/cli/analyze.py +81 -0
  19. proxy_aiops/cli/certs.py +43 -0
  20. proxy_aiops/cli/configcmd.py +107 -0
  21. proxy_aiops/cli/doctor.py +21 -0
  22. proxy_aiops/cli/init.py +166 -0
  23. proxy_aiops/cli/overview.py +16 -0
  24. proxy_aiops/cli/routes.py +59 -0
  25. proxy_aiops/cli/secret.py +105 -0
  26. proxy_aiops/cli/server.py +74 -0
  27. proxy_aiops/cli/services.py +54 -0
  28. proxy_aiops/cli/undo.py +62 -0
  29. proxy_aiops/config.py +174 -0
  30. proxy_aiops/connection.py +189 -0
  31. proxy_aiops/doctor.py +97 -0
  32. proxy_aiops/governance/__init__.py +40 -0
  33. proxy_aiops/governance/audit.py +377 -0
  34. proxy_aiops/governance/budget.py +225 -0
  35. proxy_aiops/governance/decorators.py +482 -0
  36. proxy_aiops/governance/paths.py +23 -0
  37. proxy_aiops/governance/patterns.py +378 -0
  38. proxy_aiops/governance/policy.py +430 -0
  39. proxy_aiops/governance/sanitize.py +45 -0
  40. proxy_aiops/governance/undo.py +218 -0
  41. proxy_aiops/ops/__init__.py +2 -0
  42. proxy_aiops/ops/_util.py +86 -0
  43. proxy_aiops/ops/analysis.py +469 -0
  44. proxy_aiops/ops/certs.py +146 -0
  45. proxy_aiops/ops/configread.py +94 -0
  46. proxy_aiops/ops/overview.py +55 -0
  47. proxy_aiops/ops/routes.py +209 -0
  48. proxy_aiops/ops/services.py +304 -0
  49. proxy_aiops/ops/status.py +73 -0
  50. proxy_aiops/ops/traffic.py +178 -0
  51. proxy_aiops/ops/writes.py +174 -0
  52. proxy_aiops/platform.py +371 -0
  53. proxy_aiops/secretstore.py +304 -0
  54. proxy_aiops-0.1.0.dist-info/METADATA +213 -0
  55. proxy_aiops-0.1.0.dist-info/RECORD +58 -0
  56. proxy_aiops-0.1.0.dist-info/WHEEL +4 -0
  57. proxy_aiops-0.1.0.dist-info/entry_points.txt +3 -0
  58. proxy_aiops-0.1.0.dist-info/licenses/LICENSE +21 -0
mcp_server/__init__.py ADDED
@@ -0,0 +1 @@
1
+ """MCP server package for proxy-aiops."""
mcp_server/_shared.py ADDED
@@ -0,0 +1,108 @@
1
+ """Shared MCP server primitives: the FastMCP instance, connection helper,
2
+ error sanitisation, and the ``@tool_errors`` decorator.
3
+
4
+ Tool modules under ``mcp_server/tools/`` import ``mcp`` from here and register
5
+ their ``@mcp.tool()`` functions onto it. ``mcp_server/server.py`` then imports
6
+ those modules and runs the server.
7
+
8
+ Keep ``Optional[X]`` (never PEP 604 ``X | None``) in any FastMCP-reflected
9
+ tool signature — on older mcp/pydantic the union eval'd to ``types.UnionType``
10
+ crashes FastMCP's ``issubclass`` check.
11
+ """
12
+
13
+ import functools
14
+ import logging
15
+ import os
16
+ from collections.abc import Callable
17
+ from pathlib import Path
18
+ from typing import Any, Optional
19
+
20
+ from mcp.server.fastmcp import FastMCP
21
+
22
+ from proxy_aiops.config import load_config
23
+ from proxy_aiops.connection import ConnectionManager, ProxyApiError
24
+ from proxy_aiops.governance import sanitize
25
+ from proxy_aiops.platform import UnsupportedOperation
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+ _DOCTOR_HINT = "Run 'proxy-aiops doctor' to verify connectivity and credentials."
30
+
31
+
32
+ def _safe_error(exc: Exception, tool: str) -> str:
33
+ """Return an agent-safe error string; log full detail server-side only."""
34
+ logger.error("Tool %s failed", tool, exc_info=True)
35
+ _passthrough = (
36
+ ValueError,
37
+ FileNotFoundError,
38
+ KeyError,
39
+ PermissionError,
40
+ TimeoutError,
41
+ ConnectionError,
42
+ ProxyApiError,
43
+ UnsupportedOperation,
44
+ )
45
+ if isinstance(exc, _passthrough):
46
+ return sanitize(str(exc), 400)
47
+ return f"{type(exc).__name__}: operation failed."
48
+
49
+
50
+ def tool_errors(shape: str = "dict") -> Callable:
51
+ """Wrap a tool body in the canonical try/except → ``_safe_error`` pattern.
52
+
53
+ Place this *between* ``@governed_tool`` and the function so the audit
54
+ decorator and FastMCP still see the original signature.
55
+ """
56
+
57
+ def decorator(func: Callable) -> Callable:
58
+ name = func.__name__
59
+
60
+ @functools.wraps(func)
61
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
62
+ try:
63
+ return func(*args, **kwargs)
64
+ except Exception as e: # noqa: BLE001 — sanitised below
65
+ msg = _safe_error(e, name)
66
+ if shape == "list":
67
+ return [{"error": msg, "hint": _DOCTOR_HINT}]
68
+ if shape == "str":
69
+ return f"Error: {msg} {_DOCTOR_HINT}"
70
+ return {"error": msg, "hint": _DOCTOR_HINT}
71
+
72
+ return wrapper
73
+
74
+ return decorator
75
+
76
+
77
+ mcp = FastMCP(
78
+ "proxy-aiops",
79
+ instructions=(
80
+ "Reverse-proxy / load-balancer operations (preview) over Traefik, Caddy "
81
+ "and HAProxy: version/entrypoints, routes (routers / caddy routes / "
82
+ "frontends), services and server-level upstream health, middlewares, "
83
+ "TLS certificate inventory, traffic/error counters, and config "
84
+ "snapshot/search. Flagship analyses: backend_health_rca, "
85
+ "cert_expiry_sweep, error_rate_rca, route_conflict_analysis — "
86
+ "transparent heuristics that show their numbers. Governed writes — "
87
+ "caddy config set/delete/load (prior-config captured, undo-recorded) "
88
+ "and haproxy runtime server state (ready/drain/maint) and weight "
89
+ "(reversible) — with a dry_run preview; delete/load are risk=high with "
90
+ "an approver. A per-target 'platform' field selects the API shape, and "
91
+ "an explicit support matrix raises teaching errors for ops a platform "
92
+ "cannot do (e.g. traefik writes belong to its providers). Every tool "
93
+ "runs through the proxy-aiops governance harness (audit / budget / "
94
+ "risk-tier / undo). Do NOT use for firewall rules — use firewall-aiops."
95
+ ),
96
+ )
97
+
98
+ _conn_mgr: Optional[ConnectionManager] = None
99
+
100
+
101
+ def _get_connection(target: Optional[str] = None) -> Any:
102
+ """Return a proxy connection, lazily initialising the manager."""
103
+ global _conn_mgr # noqa: PLW0603
104
+ if _conn_mgr is None:
105
+ config_path_str = os.environ.get("PROXY_AIOPS_CONFIG")
106
+ config_path = Path(config_path_str) if config_path_str else None
107
+ _conn_mgr = ConnectionManager(load_config(config_path))
108
+ return _conn_mgr.connect(target)
mcp_server/server.py ADDED
@@ -0,0 +1,40 @@
1
+ """MCP server wrapping proxy-aiops operations (stdio transport).
2
+
3
+ Thin adapter layer: each ``@mcp.tool()`` function (in ``mcp_server/tools/``)
4
+ delegates to the ``proxy_aiops`` ops package and is wrapped with the
5
+ proxy-aiops ``@governed_tool`` harness (audit / budget / undo / risk-tier).
6
+
7
+ Standalone, self-governed reverse-proxy operations (preview) over Traefik,
8
+ Caddy and HAProxy: status/routes/services/upstreams/certs/traffic/config
9
+ reads, four flagship analyses, and governed writes (caddy config set/delete/
10
+ load, haproxy server state/weight).
11
+
12
+ Source: https://github.com/AIops-tools/Proxy-AIops
13
+ License: MIT
14
+ """
15
+
16
+ import logging
17
+
18
+ from mcp_server._shared import _safe_error, mcp, tool_errors
19
+
20
+ # Importing the tool modules registers every @mcp.tool() onto the shared
21
+ # `mcp` instance. Order does not matter; each module is self-contained.
22
+ from mcp_server.tools import ( # noqa: F401 — side effects
23
+ analysis,
24
+ certs,
25
+ configread,
26
+ routes,
27
+ services,
28
+ status,
29
+ traffic,
30
+ undo,
31
+ writes,
32
+ )
33
+
34
+ __all__ = ["mcp", "main", "_safe_error", "tool_errors"]
35
+
36
+
37
+ def main() -> None:
38
+ """Run the MCP server over stdio."""
39
+ logging.basicConfig(level=logging.INFO)
40
+ mcp.run(transport="stdio")
@@ -0,0 +1 @@
1
+ """MCP tool modules — importing each registers its @mcp.tool() functions."""
@@ -0,0 +1,170 @@
1
+ """Flagship proxy-analysis MCP tools (read-only)."""
2
+
3
+ from typing import Any, Optional
4
+
5
+ from mcp_server._shared import _get_connection, mcp, tool_errors
6
+ from proxy_aiops.governance import governed_tool
7
+ from proxy_aiops.ops import analysis as ops
8
+
9
+
10
+ @mcp.tool()
11
+ @governed_tool(risk_level="low")
12
+ @tool_errors("dict")
13
+ def backend_health_rca(
14
+ service: Optional[str] = None,
15
+ upstreams: Optional[list[dict[str, Any]]] = None,
16
+ target: Optional[str] = None,
17
+ ) -> dict:
18
+ """[READ] Down upstreams grouped per service, each mapped to cause + action.
19
+
20
+ The flagship availability RCA: pulls server-level upstream health, groups
21
+ per service, and classifies each outage/degradation (connection refused,
22
+ L4/L7 health-check failure class, DNS, admin maint/drain, all-servers-down)
23
+ with a recommended action. Every finding carries its numbers. Pass
24
+ 'upstreams' for pure analysis, or a target to pull live.
25
+
26
+ Args:
27
+ service: Optional service/backend filter when pulling live.
28
+ upstreams: Injected rows {service, server, address, status, checkInfo};
29
+ skips the live pull.
30
+ target: Proxy target name from config; omit for the default.
31
+
32
+ Returns dict: {servicesEvaluated, outages, degraded, findings:[{service,
33
+ serversTotal, up, down, maint, failingServers, cause, action}], note}.
34
+ """
35
+ if upstreams is None:
36
+ from proxy_aiops.ops import services as svc_ops
37
+
38
+ pulled = svc_ops.list_upstreams(_get_connection(target), service)
39
+ if "error" in pulled:
40
+ return pulled
41
+ upstreams = pulled.get("upstreams", [])
42
+ return ops.backend_health_rca(upstreams)
43
+
44
+
45
+ @mcp.tool()
46
+ @governed_tool(risk_level="low")
47
+ @tool_errors("dict")
48
+ def cert_expiry_sweep(
49
+ warn_days: float = 30.0,
50
+ critical_days: float = 7.0,
51
+ port: int = 443,
52
+ certs: Optional[list[dict[str, Any]]] = None,
53
+ target: Optional[str] = None,
54
+ ) -> dict:
55
+ """[READ] TLS cert inventory bucketed by days-to-expiry + renewal hints.
56
+
57
+ The flagship cert sweep for traefik/caddy targets: collects the TLS domain
58
+ inventory, live-probes each domain's served leaf cert (bounded handshake),
59
+ and buckets by expiry: expired / critical / warning / ok — with a
60
+ platform-specific renewal hint (ACME resolver / storage checks). Pass
61
+ 'certs' for pure analysis over {domain, daysToExpiry} rows. haproxy targets
62
+ return the support matrix's teaching note.
63
+
64
+ Args:
65
+ warn_days: Days-to-expiry at/below which a cert is a warning (default 30).
66
+ critical_days: Days at/below which a cert is critical (default 7).
67
+ port: TLS port to probe (default 443).
68
+ certs: Injected rows {domain, daysToExpiry, notAfter?}; skips pull+probe.
69
+ target: Proxy target name from config; omit for the default.
70
+
71
+ Returns dict: {certsEvaluated, expired, critical, warning, ok, unknown,
72
+ certificates (soonest first), thresholds, renewalHint, note}.
73
+ """
74
+ platform = ""
75
+ if certs is None:
76
+ from proxy_aiops.ops import certs as cert_ops
77
+
78
+ conn = _get_connection(target)
79
+ platform = conn.target.platform
80
+ pulled = cert_ops.list_certificates(conn, probe=True, port=port)
81
+ if "unsupported" in pulled or "error" in pulled:
82
+ return pulled
83
+ certs = pulled.get("certificates", [])
84
+ return ops.cert_expiry_sweep(
85
+ certs, warn_days=warn_days, critical_days=critical_days, platform=platform
86
+ )
87
+
88
+
89
+ @mcp.tool()
90
+ @governed_tool(risk_level="low")
91
+ @tool_errors("dict")
92
+ def error_rate_rca(
93
+ error_rate_pct: float = 5.0,
94
+ min_requests: float = 30.0,
95
+ counters: Optional[list[dict[str, Any]]] = None,
96
+ target: Optional[str] = None,
97
+ ) -> dict:
98
+ """[READ] Rank services by 5xx share vs the fleet baseline, cause + action.
99
+
100
+ The flagship error RCA: reads per-service status-code counters (traefik
101
+ /metrics, haproxy stats), flags services whose 5xx rate crosses the
102
+ threshold with enough traffic, and maps the dominant code to a cause
103
+ (503 no-upstream / 502 conn-fail / 504 timeout / 500 app error). Every
104
+ entry carries its numbers and its multiple vs the fleet baseline. Pass
105
+ 'counters' for pure analysis. caddy targets return the support matrix's
106
+ teaching note (no per-route counters).
107
+
108
+ Args:
109
+ error_rate_pct: 5xx %% at/above which a service is flagged (default 5.0).
110
+ min_requests: Minimum requests before a service can be flagged (default 30).
111
+ counters: Injected rows {service, total, codes:{...}} and/or
112
+ classes:{"5xx": n}; skips the live pull.
113
+ target: Proxy target name from config; omit for the default.
114
+
115
+ Returns dict: {servicesEvaluated, flaggedCount, fleetErrorRatePct,
116
+ thresholds, flagged:[{service, requestsTotal, errors5xx, errorRatePct,
117
+ dominantCode, vsBaselineX, severity, cause, action}], note}.
118
+ """
119
+ if counters is None:
120
+ from proxy_aiops.ops import traffic as traffic_ops
121
+
122
+ pulled = traffic_ops.error_counters(_get_connection(target))
123
+ if "error" in pulled:
124
+ return pulled
125
+ counters = pulled.get("services", [])
126
+ return ops.error_rate_rca(
127
+ counters, error_rate_pct=error_rate_pct, min_requests=min_requests
128
+ )
129
+
130
+
131
+ @mcp.tool()
132
+ @governed_tool(risk_level="low")
133
+ @tool_errors("dict")
134
+ def route_conflict_analysis(
135
+ routes: Optional[list[dict[str, Any]]] = None,
136
+ services: Optional[list[dict[str, Any]]] = None,
137
+ target: Optional[str] = None,
138
+ ) -> dict:
139
+ """[READ] Shadowed routes, dead routes, and redirect loops (static).
140
+
141
+ The flagship routing hygiene analysis: fetches the route table and service
142
+ list and statically finds (1) routes fully covered by an earlier/higher-
143
+ priority route (they can never match), (2) routes pointing at a missing
144
+ service or one with zero servers up, and (3) redirect chains that loop.
145
+ Every finding names the covering route / missing service. Pass 'routes'
146
+ (and optionally 'services') for pure analysis.
147
+
148
+ Args:
149
+ routes: Injected rows {name, hosts, paths, priority, service, enabled,
150
+ redirectTo}; skips the live pull.
151
+ services: Injected rows {name, serversTotal, serversUp} for dead-route
152
+ detection.
153
+ target: Proxy target name from config; omit for the default.
154
+
155
+ Returns dict: {routesEvaluated, shadowedCount, deadCount,
156
+ redirectLoopCount, shadowedRoutes, deadRoutes, redirectLoops, note}.
157
+ """
158
+ if routes is None:
159
+ from proxy_aiops.ops import routes as route_ops
160
+ from proxy_aiops.ops import services as svc_ops
161
+
162
+ conn = _get_connection(target)
163
+ pulled = route_ops.list_routes(conn)
164
+ if "error" in pulled:
165
+ return pulled
166
+ routes = pulled.get("routes", [])
167
+ if services is None:
168
+ pulled_svcs = svc_ops.list_services(conn)
169
+ services = pulled_svcs.get("services", []) if "error" not in pulled_svcs else None
170
+ return ops.route_conflict_analysis(routes, services)
@@ -0,0 +1,29 @@
1
+ """TLS certificate MCP tools (read-only)."""
2
+
3
+ from typing import Optional
4
+
5
+ from mcp_server._shared import _get_connection, mcp, tool_errors
6
+ from proxy_aiops.governance import governed_tool
7
+ from proxy_aiops.ops import certs as ops
8
+
9
+
10
+ @mcp.tool()
11
+ @governed_tool(risk_level="low")
12
+ @tool_errors("dict")
13
+ def list_certificates(
14
+ probe: bool = False,
15
+ port: int = 443,
16
+ target: Optional[str] = None,
17
+ ) -> dict:
18
+ """[READ] TLS domain inventory for a traefik/caddy target; optionally
19
+ handshake-probe each domain for its live expiry.
20
+
21
+ On haproxy this returns the support matrix's teaching note (certs are .pem
22
+ files in haproxy.cfg).
23
+
24
+ Args:
25
+ probe: If True, TLS-handshake each domain (bounded) to read expiry.
26
+ port: TLS port to probe (default 443).
27
+ target: Proxy target name from config; omit for the default.
28
+ """
29
+ return ops.list_certificates(_get_connection(target), probe=probe, port=port)
@@ -0,0 +1,50 @@
1
+ """Config-tree MCP tools (read-only)."""
2
+
3
+ from typing import Optional
4
+
5
+ from mcp_server._shared import _get_connection, mcp, tool_errors
6
+ from proxy_aiops.governance import governed_tool
7
+ from proxy_aiops.ops import configread as ops
8
+
9
+
10
+ @mcp.tool()
11
+ @governed_tool(risk_level="low")
12
+ @tool_errors("dict")
13
+ def config_snapshot(target: Optional[str] = None) -> dict:
14
+ """[READ] The live config tree (caddy /config/) or merged dynamic state
15
+ (traefik /api/rawdata), sanitised and bounded. haproxy returns the support
16
+ matrix's teaching note.
17
+
18
+ Args:
19
+ target: Proxy target name from config; omit for the default.
20
+ """
21
+ return ops.config_snapshot(_get_connection(target))
22
+
23
+
24
+ @mcp.tool()
25
+ @governed_tool(risk_level="low")
26
+ @tool_errors("dict")
27
+ def search_config(query: str, target: Optional[str] = None) -> dict:
28
+ """[READ] Search the config tree for a string; returns matching config
29
+ paths (on caddy, directly usable by get/set_config_value).
30
+
31
+ Args:
32
+ query: Case-insensitive substring to find in keys and values.
33
+ target: Proxy target name from config; omit for the default.
34
+ """
35
+ return ops.search_config(_get_connection(target), query)
36
+
37
+
38
+ @mcp.tool()
39
+ @governed_tool(risk_level="low")
40
+ @tool_errors("dict")
41
+ def get_config_value(path: str, target: Optional[str] = None) -> dict:
42
+ """[READ] One value out of the caddy config tree by config path
43
+ (e.g. apps/http/servers/srv0/routes/0). Off-caddy platforms return the
44
+ support matrix's teaching note.
45
+
46
+ Args:
47
+ path: Slash-separated config path (dot-segments rejected).
48
+ target: Proxy target name from config; omit for the default.
49
+ """
50
+ return ops.get_config_value(_get_connection(target), path)
@@ -0,0 +1,52 @@
1
+ """Route MCP tools — routers / caddy routes / frontends (read-only)."""
2
+
3
+ from typing import Optional
4
+
5
+ from mcp_server._shared import _get_connection, mcp, tool_errors
6
+ from proxy_aiops.governance import governed_tool
7
+ from proxy_aiops.ops import routes as ops
8
+
9
+
10
+ @mcp.tool()
11
+ @governed_tool(risk_level="low")
12
+ @tool_errors("dict")
13
+ def list_routes(host: Optional[str] = None, target: Optional[str] = None) -> dict:
14
+ """[READ] Routes normalised across platforms: {name, hosts, paths,
15
+ priority, service, tls, enabled, redirectTo}.
16
+
17
+ Traefik router names and caddy config paths (apps/http/servers/...) come
18
+ back exactly as the other tools expect them.
19
+
20
+ Args:
21
+ host: Optional hostname filter (keeps host-less catch-alls).
22
+ target: Proxy target name from config; omit for the default.
23
+ """
24
+ return ops.list_routes(_get_connection(target), host)
25
+
26
+
27
+ @mcp.tool()
28
+ @governed_tool(risk_level="low")
29
+ @tool_errors("dict")
30
+ def route_detail(name: str, target: Optional[str] = None) -> dict:
31
+ """[READ] One route's full detail by name (from list_routes).
32
+
33
+ Args:
34
+ name: Route name — traefik router name, caddy route config path, or
35
+ haproxy frontend name.
36
+ target: Proxy target name from config; omit for the default.
37
+ """
38
+ return ops.route_detail(_get_connection(target), name)
39
+
40
+
41
+ @mcp.tool()
42
+ @governed_tool(risk_level="low")
43
+ @tool_errors("dict")
44
+ def find_route(host: str, path: str = "/", target: Optional[str] = None) -> dict:
45
+ """[READ] Which routes would serve a host/path (static match, best first).
46
+
47
+ Args:
48
+ host: Hostname to match (e.g. app.example.com).
49
+ path: Request path to match (default /).
50
+ target: Proxy target name from config; omit for the default.
51
+ """
52
+ return ops.find_route(_get_connection(target), host, path)
@@ -0,0 +1,74 @@
1
+ """Service / upstream MCP tools (read-only)."""
2
+
3
+ from typing import Optional
4
+
5
+ from mcp_server._shared import _get_connection, mcp, tool_errors
6
+ from proxy_aiops.governance import governed_tool
7
+ from proxy_aiops.ops import services as ops
8
+
9
+
10
+ @mcp.tool()
11
+ @governed_tool(risk_level="low")
12
+ @tool_errors("dict")
13
+ def list_services(target: Optional[str] = None) -> dict:
14
+ """[READ] Services / backends with per-service server-up counts.
15
+
16
+ Args:
17
+ target: Proxy target name from config; omit for the default.
18
+ """
19
+ return ops.list_services(_get_connection(target))
20
+
21
+
22
+ @mcp.tool()
23
+ @governed_tool(risk_level="low")
24
+ @tool_errors("dict")
25
+ def service_detail(name: str, target: Optional[str] = None) -> dict:
26
+ """[READ] One service/backend's full detail by name (from list_services).
27
+
28
+ Args:
29
+ name: Service name — traefik service name, caddy route config path, or
30
+ haproxy backend name.
31
+ target: Proxy target name from config; omit for the default.
32
+ """
33
+ return ops.service_detail(_get_connection(target), name)
34
+
35
+
36
+ @mcp.tool()
37
+ @governed_tool(risk_level="low")
38
+ @tool_errors("dict")
39
+ def list_upstreams(service: Optional[str] = None, target: Optional[str] = None) -> dict:
40
+ """[READ] Server-level upstream health rows: {service, server, address,
41
+ status(up/down/maint/drain), checkInfo, weight}. Feeds backend_health_rca.
42
+
43
+ Args:
44
+ service: Optional service/backend filter.
45
+ target: Proxy target name from config; omit for the default.
46
+ """
47
+ return ops.list_upstreams(_get_connection(target), service)
48
+
49
+
50
+ @mcp.tool()
51
+ @governed_tool(risk_level="low")
52
+ @tool_errors("dict")
53
+ def upstream_detail(service: str, server: str, target: Optional[str] = None) -> dict:
54
+ """[READ] One upstream server's health/state row.
55
+
56
+ Args:
57
+ service: Service/backend name.
58
+ server: Server name or address (from list_upstreams).
59
+ target: Proxy target name from config; omit for the default.
60
+ """
61
+ return ops.upstream_detail(_get_connection(target), service, server)
62
+
63
+
64
+ @mcp.tool()
65
+ @governed_tool(risk_level="low")
66
+ @tool_errors("dict")
67
+ def list_middlewares(target: Optional[str] = None) -> dict:
68
+ """[READ] Middlewares (traefik). On caddy/haproxy this returns the support
69
+ matrix's teaching note (their equivalents live inside routes / haproxy.cfg).
70
+
71
+ Args:
72
+ target: Proxy target name from config; omit for the default.
73
+ """
74
+ return ops.list_middlewares(_get_connection(target))
@@ -0,0 +1,47 @@
1
+ """Status MCP tools — overview, version, entrypoints (read-only)."""
2
+
3
+ from typing import Optional
4
+
5
+ from mcp_server._shared import _get_connection, mcp, tool_errors
6
+ from proxy_aiops.governance import governed_tool
7
+ from proxy_aiops.ops import overview as overview_ops
8
+ from proxy_aiops.ops import status as ops
9
+
10
+
11
+ @mcp.tool()
12
+ @governed_tool(risk_level="low")
13
+ @tool_errors("dict")
14
+ def proxy_overview(target: Optional[str] = None) -> dict:
15
+ """[READ] One-shot summary: platform/version + route/service counts +
16
+ upstream up/down health.
17
+
18
+ Args:
19
+ target: Proxy target name from config; omit for the default.
20
+ """
21
+ return overview_ops.proxy_overview(_get_connection(target))
22
+
23
+
24
+ @mcp.tool()
25
+ @governed_tool(risk_level="low")
26
+ @tool_errors("dict")
27
+ def version_info(target: Optional[str] = None) -> dict:
28
+ """[READ] Version / build info (traefik /api/version, haproxy /v2/info;
29
+ caddy returns a teaching note — its admin API has no version endpoint).
30
+
31
+ Args:
32
+ target: Proxy target name from config; omit for the default.
33
+ """
34
+ return ops.version_info(_get_connection(target))
35
+
36
+
37
+ @mcp.tool()
38
+ @governed_tool(risk_level="low")
39
+ @tool_errors("dict")
40
+ def list_entrypoints(target: Optional[str] = None) -> dict:
41
+ """[READ] Listeners: traefik entrypoints / caddy server listen addresses /
42
+ haproxy frontends — where traffic enters this proxy.
43
+
44
+ Args:
45
+ target: Proxy target name from config; omit for the default.
46
+ """
47
+ return ops.list_entrypoints(_get_connection(target))
@@ -0,0 +1,36 @@
1
+ """Traffic / error-counter MCP tools (read-only)."""
2
+
3
+ from typing import Optional
4
+
5
+ from mcp_server._shared import _get_connection, mcp, tool_errors
6
+ from proxy_aiops.governance import governed_tool
7
+ from proxy_aiops.ops import traffic as ops
8
+
9
+
10
+ @mcp.tool()
11
+ @governed_tool(risk_level="low")
12
+ @tool_errors("dict")
13
+ def traffic_stats(target: Optional[str] = None) -> dict:
14
+ """[READ] Per-service traffic snapshot: requests, latency/rate/sessions
15
+ where the platform exposes it (traefik /metrics, haproxy stats; caddy
16
+ returns the support matrix's teaching note).
17
+
18
+ Args:
19
+ target: Proxy target name from config; omit for the default.
20
+ """
21
+ return ops.traffic_stats(_get_connection(target))
22
+
23
+
24
+ @mcp.tool()
25
+ @governed_tool(risk_level="low")
26
+ @tool_errors("dict")
27
+ def error_counters(target: Optional[str] = None) -> dict:
28
+ """[READ] Per-service request/status-code counters (feeds error_rate_rca).
29
+
30
+ traefik: parsed from the /metrics text endpoint (per-code); haproxy: Data Plane
31
+ stats (per-class hrsp_*); caddy: teaching note (no per-route counters).
32
+
33
+ Args:
34
+ target: Proxy target name from config; omit for the default.
35
+ """
36
+ return ops.error_counters(_get_connection(target))