refactor: Modularize MCP server with command batching

- Split monolithic mcp/server.py (1874 lines) into haproxy_mcp/ package:
  - config.py: Configuration constants and environment variables
  - exceptions.py: Custom exception classes
  - validation.py: Input validation functions
  - haproxy_client.py: HAProxy Runtime API client with batch support
  - file_ops.py: Atomic file operations with locking
  - utils.py: CSV parsing utilities
  - tools/: MCP tools organized by function
    - domains.py: Domain management (3 tools)
    - servers.py: Server management (7 tools)
    - health.py: Health checks (3 tools)
    - monitoring.py: Monitoring (4 tools)
    - configuration.py: Config management (4 tools)

- Add haproxy_cmd_batch() for sending multiple commands in single TCP connection
- Optimize server operations: 1 connection instead of 2 per server
- Optimize startup restore: All servers in 1 connection (was 2×N)
- Update type hints to Python 3.9+ style (built-in generics)
- Remove unused imports and functions
- Update CLAUDE.md with new structure and performance notes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
kaffa
2026-02-02 03:50:42 +00:00
parent a3d5d61454
commit 7bee373684
19 changed files with 2035 additions and 1876 deletions

View File

@@ -0,0 +1,97 @@
"""Monitoring tools for HAProxy MCP Server."""
from typing import Annotated
from pydantic import Field
from ..exceptions import HaproxyError
from ..validation import validate_backend_name
from ..haproxy_client import haproxy_cmd
from ..utils import parse_stat_csv
def register_monitoring_tools(mcp):
"""Register monitoring tools with MCP server."""
@mcp.tool()
def haproxy_stats() -> str:
"""Get HAProxy status and statistics.
Returns:
Key HAProxy metrics (name, version, uptime, connections, etc.)
"""
try:
result = haproxy_cmd("show info")
stats = {}
for line in result.split("\n"):
if ":" in line:
key, value = line.split(":", 1)
stats[key.strip()] = value.strip()
important = ["Name", "Version", "Uptime_sec", "CurrConns", "MaxConn", "Run_queue", "Tasks"]
output = []
for key in important:
if key in stats:
output.append(f"{key}: {stats[key]}")
return "\n".join(output) if output else result
except HaproxyError as e:
return f"Error: {e}"
@mcp.tool()
def haproxy_backends() -> str:
"""List all HAProxy backends.
Returns:
List of all configured backend names
"""
try:
result = haproxy_cmd("show backend")
backends = [line for line in result.split("\n") if line and not line.startswith("#")]
return "Backends:\n" + "\n".join(f"{b}" for b in backends)
except HaproxyError as e:
return f"Error: {e}"
@mcp.tool()
def haproxy_list_frontends() -> str:
"""List all HAProxy frontends with their status.
Returns:
List of frontends with status and session counts
"""
try:
result = haproxy_cmd("show stat")
frontends = []
for stat in parse_stat_csv(result):
if stat["svname"] == "FRONTEND":
frontends.append(
f"{stat['pxname']}: {stat['status']} (sessions: {stat['scur']})"
)
if not frontends:
return "No frontends found"
return "Frontends:\n" + "\n".join(frontends)
except HaproxyError as e:
return f"Error: {e}"
@mcp.tool()
def haproxy_get_connections(
backend: Annotated[str, Field(default="", description="Optional: Backend name to filter (e.g., 'pool_1'). Empty = all backends")]
) -> str:
"""Get active connection counts per server for monitoring traffic distribution."""
if backend and not validate_backend_name(backend):
return "Error: Invalid backend name (use alphanumeric, underscore, hyphen only)"
try:
result = haproxy_cmd("show stat")
connections = []
for stat in parse_stat_csv(result):
if backend and stat["pxname"] != backend:
continue
if stat["svname"] in ["FRONTEND", "BACKEND"]:
connections.append(
f"{stat['pxname']} ({stat['svname']}): {stat['scur']} current, {stat['smax']} max"
)
elif stat["svname"]:
connections.append(f" - {stat['svname']}: {stat['scur']} connections")
return "\n".join(connections) if connections else "No connection data"
except HaproxyError as e:
return f"Error: {e}"