Hide empty MAINT slots from get_server_health output by default
Reduces MCP response from ~12.3k tokens (~1000 lines) to only active servers. Empty pool slots (MAINT with no health check) are filtered by default. Added show_all parameter to optionally show all slots. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -195,9 +195,14 @@ def register_health_tools(mcp):
|
||||
|
||||
@mcp.tool()
|
||||
def haproxy_get_server_health(
|
||||
backend: Annotated[str, Field(default="", description="Optional: Backend name to filter (e.g., 'pool_1'). Empty = all backends")] = ""
|
||||
backend: Annotated[str, Field(default="", description="Optional: Backend name to filter (e.g., 'pool_1'). Empty = all backends")] = "",
|
||||
show_all: Annotated[bool, Field(default=False, description="Show all slots including empty MAINT slots. Default: only active/configured servers")] = False,
|
||||
) -> str:
|
||||
"""Get health status of all servers (low-level view). For domain-specific, use haproxy_domain_health."""
|
||||
"""Get health status of all servers (low-level view). For domain-specific, use haproxy_domain_health.
|
||||
|
||||
By default, hides empty pool slots (MAINT with no health check) to reduce output size.
|
||||
Use show_all=True to see all slots including unconfigured ones.
|
||||
"""
|
||||
if backend and not validate_backend_name(backend):
|
||||
return "Error: Invalid backend name (use alphanumeric, underscore, hyphen only)"
|
||||
try:
|
||||
@@ -207,6 +212,9 @@ def register_health_tools(mcp):
|
||||
if stat["svname"] not in ["FRONTEND", "BACKEND", ""]:
|
||||
if backend and stat["pxname"] != backend:
|
||||
continue
|
||||
# Skip empty pool slots (MAINT with no health check = unconfigured)
|
||||
if not show_all and stat["status"] == "MAINT" and not stat["check_status"]:
|
||||
continue
|
||||
servers.append(
|
||||
f"• {stat['pxname']}/{stat['svname']}: {stat['status']} "
|
||||
f"(weight: {stat['weight']}, check: {stat['check_status']})"
|
||||
|
||||
@@ -406,6 +406,93 @@ class TestHaproxyGetServerHealth:
|
||||
|
||||
assert "No servers found" in result
|
||||
|
||||
def test_get_server_health_hides_empty_maint_slots(self, mock_socket_class, mock_select, patch_config_paths, response_builder):
|
||||
"""Empty MAINT slots (no health check) are hidden by default."""
|
||||
mock_sock = mock_socket_class(responses={
|
||||
"show stat": response_builder.stat_csv([
|
||||
{"pxname": "pool_1", "svname": "pool_1_1", "status": "UP", "weight": 1, "check_status": "L4OK"},
|
||||
{"pxname": "pool_1", "svname": "pool_1_2", "status": "MAINT", "weight": 1, "check_status": ""},
|
||||
{"pxname": "pool_1", "svname": "pool_1_3", "status": "MAINT", "weight": 1, "check_status": ""},
|
||||
{"pxname": "pool_2", "svname": "pool_2_1", "status": "MAINT", "weight": 1, "check_status": ""},
|
||||
]),
|
||||
})
|
||||
|
||||
with patch("socket.socket", return_value=mock_sock):
|
||||
from haproxy_mcp.tools.health import register_health_tools
|
||||
mcp = MagicMock()
|
||||
registered_tools = {}
|
||||
|
||||
def capture_tool():
|
||||
def decorator(func):
|
||||
registered_tools[func.__name__] = func
|
||||
return func
|
||||
return decorator
|
||||
|
||||
mcp.tool = capture_tool
|
||||
register_health_tools(mcp)
|
||||
|
||||
result = registered_tools["haproxy_get_server_health"](backend="")
|
||||
|
||||
assert "pool_1_1" in result
|
||||
assert "pool_1_2" not in result
|
||||
assert "pool_1_3" not in result
|
||||
assert "pool_2" not in result
|
||||
|
||||
def test_get_server_health_show_all_includes_empty_slots(self, mock_socket_class, mock_select, patch_config_paths, response_builder):
|
||||
"""show_all=True includes empty MAINT slots."""
|
||||
mock_sock = mock_socket_class(responses={
|
||||
"show stat": response_builder.stat_csv([
|
||||
{"pxname": "pool_1", "svname": "pool_1_1", "status": "UP", "weight": 1, "check_status": "L4OK"},
|
||||
{"pxname": "pool_1", "svname": "pool_1_2", "status": "MAINT", "weight": 1, "check_status": ""},
|
||||
]),
|
||||
})
|
||||
|
||||
with patch("socket.socket", return_value=mock_sock):
|
||||
from haproxy_mcp.tools.health import register_health_tools
|
||||
mcp = MagicMock()
|
||||
registered_tools = {}
|
||||
|
||||
def capture_tool():
|
||||
def decorator(func):
|
||||
registered_tools[func.__name__] = func
|
||||
return func
|
||||
return decorator
|
||||
|
||||
mcp.tool = capture_tool
|
||||
register_health_tools(mcp)
|
||||
|
||||
result = registered_tools["haproxy_get_server_health"](backend="", show_all=True)
|
||||
|
||||
assert "pool_1_1" in result
|
||||
assert "pool_1_2" in result
|
||||
|
||||
def test_get_server_health_keeps_maint_with_check(self, mock_socket_class, mock_select, patch_config_paths, response_builder):
|
||||
"""MAINT servers with health check status are kept (intentionally maintained)."""
|
||||
mock_sock = mock_socket_class(responses={
|
||||
"show stat": response_builder.stat_csv([
|
||||
{"pxname": "pool_1", "svname": "pool_1_1", "status": "MAINT", "weight": 1, "check_status": "L4OK"},
|
||||
]),
|
||||
})
|
||||
|
||||
with patch("socket.socket", return_value=mock_sock):
|
||||
from haproxy_mcp.tools.health import register_health_tools
|
||||
mcp = MagicMock()
|
||||
registered_tools = {}
|
||||
|
||||
def capture_tool():
|
||||
def decorator(func):
|
||||
registered_tools[func.__name__] = func
|
||||
return func
|
||||
return decorator
|
||||
|
||||
mcp.tool = capture_tool
|
||||
register_health_tools(mcp)
|
||||
|
||||
result = registered_tools["haproxy_get_server_health"](backend="")
|
||||
|
||||
assert "pool_1_1" in result
|
||||
assert "MAINT" in result
|
||||
|
||||
def test_get_server_health_haproxy_error(self, mock_socket_class, mock_select, patch_config_paths):
|
||||
"""HAProxy error returns error message."""
|
||||
def raise_error(*args, **kwargs):
|
||||
|
||||
Reference in New Issue
Block a user