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()
|
@mcp.tool()
|
||||||
def haproxy_get_server_health(
|
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:
|
) -> 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):
|
if backend and not validate_backend_name(backend):
|
||||||
return "Error: Invalid backend name (use alphanumeric, underscore, hyphen only)"
|
return "Error: Invalid backend name (use alphanumeric, underscore, hyphen only)"
|
||||||
try:
|
try:
|
||||||
@@ -207,6 +212,9 @@ def register_health_tools(mcp):
|
|||||||
if stat["svname"] not in ["FRONTEND", "BACKEND", ""]:
|
if stat["svname"] not in ["FRONTEND", "BACKEND", ""]:
|
||||||
if backend and stat["pxname"] != backend:
|
if backend and stat["pxname"] != backend:
|
||||||
continue
|
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(
|
servers.append(
|
||||||
f"• {stat['pxname']}/{stat['svname']}: {stat['status']} "
|
f"• {stat['pxname']}/{stat['svname']}: {stat['status']} "
|
||||||
f"(weight: {stat['weight']}, check: {stat['check_status']})"
|
f"(weight: {stat['weight']}, check: {stat['check_status']})"
|
||||||
|
|||||||
@@ -406,6 +406,93 @@ class TestHaproxyGetServerHealth:
|
|||||||
|
|
||||||
assert "No servers found" in result
|
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):
|
def test_get_server_health_haproxy_error(self, mock_socket_class, mock_select, patch_config_paths):
|
||||||
"""HAProxy error returns error message."""
|
"""HAProxy error returns error message."""
|
||||||
def raise_error(*args, **kwargs):
|
def raise_error(*args, **kwargs):
|
||||||
|
|||||||
Reference in New Issue
Block a user