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:
kappa
2026-02-08 12:58:16 +09:00
parent 0084b99f05
commit e0dd3807c6
2 changed files with 97 additions and 2 deletions

View File

@@ -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']})"

View File

@@ -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):