Coverage for session_buddy / tools / health_tools.py: 76.27%
53 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-04 00:43 -0800
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-04 00:43 -0800
1"""Health check MCP tools for session-mgmt-mcp.
3Provides health status endpoints compatible with Docker and Kubernetes
4orchestration systems.
6Phase 10.1: Production Hardening - Health Check Tools
7"""
9from __future__ import annotations
11import time
12import typing as t
14from mcp_common.health import HealthCheckResponse
16# Server start time for uptime calculation
17_SERVER_START_TIME = time.time()
20def _normalize_dict_component(
21 component: dict[str, t.Any],
22) -> t.Any: # Returns MCPComponentHealth
23 """Normalize dictionary component to MCPComponentHealth."""
24 from mcp_common.health import ComponentHealth as MCPComponentHealth
25 from mcp_common.health import HealthStatus as MCPHealthStatus
27 status_value = component.get("status", "degraded")
28 try:
29 status = MCPHealthStatus(status_value)
30 except ValueError:
31 status = MCPHealthStatus.DEGRADED
33 return MCPComponentHealth(
34 name=component.get("name", "unknown"),
35 status=status,
36 message=component.get("message"),
37 latency_ms=component.get("latency_ms"),
38 metadata=component.get("metadata", {}),
39 )
42def _normalize_object_component(
43 component: t.Any,
44) -> t.Any: # Returns MCPComponentHealth
45 """Normalize object component to MCPComponentHealth."""
46 from mcp_common.health import ComponentHealth as MCPComponentHealth
47 from mcp_common.health import HealthStatus as MCPHealthStatus
49 status_attr = getattr(component, "status", MCPHealthStatus.DEGRADED)
50 try:
51 status = (
52 status_attr
53 if isinstance(status_attr, MCPHealthStatus)
54 else MCPHealthStatus(str(status_attr))
55 )
56 except ValueError:
57 status = MCPHealthStatus.DEGRADED
59 return MCPComponentHealth(
60 name=getattr(component, "name", "unknown"),
61 status=status,
62 message=getattr(component, "message", None),
63 latency_ms=getattr(component, "latency_ms", None),
64 metadata=getattr(component, "metadata", {}),
65 )
68def _normalize_components(
69 components: list[t.Any],
70) -> list[t.Any]: # Returns list[MCPComponentHealth]
71 """Normalize health check components to standard format."""
72 from mcp_common.health import ComponentHealth as MCPComponentHealth
74 normalized: list[t.Any] = [] # list[MCPComponentHealth]
75 for component in components:
76 if isinstance(component, MCPComponentHealth): 76 ↛ 77line 76 didn't jump to line 77 because the condition on line 76 was never true
77 normalized.append(component)
78 elif isinstance(component, dict): 78 ↛ 81line 78 didn't jump to line 81 because the condition on line 78 was always true
79 normalized.append(_normalize_dict_component(component))
80 else:
81 normalized.append(_normalize_object_component(component))
83 return normalized
86def _prepare_readiness_result(
87 response: t.Any, # HealthCheckResponse
88) -> dict[str, t.Any]:
89 """Prepare result for readiness check."""
90 result: dict[str, t.Any] = response.to_dict()
91 result["ready"] = response.is_healthy()
92 return result
95def _prepare_liveness_result(
96 response: t.Any, # HealthCheckResponse
97) -> dict[str, t.Any]:
98 """Prepare result for liveness check."""
99 result: dict[str, t.Any] = response.to_dict()
100 result["alive"] = response.is_ready()
101 return result
104async def get_health_status(ready: bool = False) -> dict[str, t.Any]:
105 """Get comprehensive health status of the session management server.
107 Args:
108 ready: If True, use readiness check logic (stricter, for K8s readiness probes)
109 If False, use liveness check logic (looser, for K8s liveness probes)
111 Returns:
112 Dictionary with health status suitable for JSON serialization
114 Example Response:
115 {
116 "status": "healthy",
117 "timestamp": "2025-10-28T12:00:00Z",
118 "version": "1.0.0",
119 "uptime_seconds": 3600.5,
120 "components": [
121 {
122 "name": "database",
123 "status": "healthy",
124 "message": "Database operational",
125 "latency_ms": 12.5
126 },
127 ...
128 ]
129 }
131 Usage:
132 # Kubernetes liveness probe (checks if server should be restarted)
133 await get_health_status(ready=False)
135 # Kubernetes readiness probe (checks if server should receive traffic)
136 await get_health_status(ready=True)
138 # Docker health check
139 await get_health_status()
141 """
142 from session_buddy.health_checks import get_all_health_checks
144 # Get server version
145 try:
146 from session_buddy import __version__
148 version = __version__
149 except (ImportError, AttributeError):
150 version = "unknown"
152 # Run all health checks and normalize
153 components = await get_all_health_checks()
154 normalized_components = _normalize_components(components)
156 # Create health response
157 response = HealthCheckResponse.create(
158 components=normalized_components,
159 version=version,
160 start_time=_SERVER_START_TIME,
161 metadata={"check_type": "readiness" if ready else "liveness"},
162 )
164 # Return appropriate result based on check type
165 return (
166 _prepare_readiness_result(response)
167 if ready
168 else _prepare_liveness_result(response)
169 )
172__all__ = ["get_health_status"]