73 Commits

Author SHA1 Message Date
kaffa
79254835e9 feat: Zero-downtime certificate management via Runtime API
Changes:
- Replace USR2 signal reload with HAProxy Runtime API for cert updates
  - new ssl cert → set ssl cert → commit ssl cert
  - No connection drops during certificate changes
- Add certificates.json for persistence (domain list only)
- Add haproxy_load_cert tool for manual certificate loading
- Auto-restore certificates on MCP startup
- Update startup sequence to load both servers and certificates

certificates.json format:
{
  "domains": ["inouter.com", "anvil.it.com"]
}

Paths derived from convention:
- Host: /opt/haproxy/certs/{domain}.pem
- Container: /etc/haproxy/certs/{domain}.pem

Total MCP tools: 28 → 29

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 04:23:28 +00:00
kaffa
7ebe204f89 feat: Add certificate coverage check to haproxy_add_domain
When adding a domain, now checks if an SSL certificate covers it:
- Exact match: domain.com.pem
- Wildcard match: parent.com.pem with *.parent.com SAN

Output examples:
- "SSL: Using certificate inouter.com (wildcard)"
- "SSL: No certificate found. Use haproxy_issue_cert(...) to issue one."

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 04:15:02 +00:00
kaffa
dbacb86d60 feat: Add certificate management tools (6 new MCP tools)
New tools for SSL/TLS certificate management via acme.sh:
- haproxy_list_certs: List all certificates with expiry info
- haproxy_cert_info: Get detailed certificate info (expiry, issuer, SANs)
- haproxy_issue_cert: Issue new certificate via Cloudflare DNS validation
- haproxy_renew_cert: Renew specific certificate (with force option)
- haproxy_renew_all_certs: Renew all certificates due for renewal
- haproxy_delete_cert: Delete certificate from acme.sh and HAProxy

Features:
- Automatic PEM deployment to HAProxy certs directory
- HAProxy hot-reload after certificate changes (USR2 signal)
- Cloudflare DNS validation with CF_Token support
- Wildcard certificate support

Total MCP tools: 22 → 28

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 03:55:26 +00:00
kaffa
7bee373684 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>
2026-02-02 03:50:42 +00:00
kaffa
a3d5d61454 docs: Update CLAUDE.md with safety features
- Update server.py line count
- Add Safety Features section documenting:
  - Atomic file writes
  - File locking
  - Disk-first pattern with rollback
  - Command validation
  - Input validation
  - Bulk operation limits

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 14:15:57 +00:00
kaffa
913ba0fdca fix: Final round of improvements
HIGH PRIORITY:
1. Pool allocation race condition
   - Add file locking around entire pool allocation in haproxy_add_domain
   - Prevents concurrent calls from getting same pool

2. haproxy_remove_server - disk-first pattern
   - Remove from config FIRST, then update HAProxy
   - Rollback config on HAProxy failure

3. Wildcard domain prefix validation
   - Reject domains starting with '.'
   - Prevents double-prefix like '..domain.com'

MEDIUM PRIORITY:
4. Variable shadowing fix
   - Rename state_output to servers_state in haproxy_set_domain_state

5. JSON size limit
   - Add MAX_SERVERS_JSON_SIZE = 10000 limit for haproxy_add_servers

6. Remove get_server_suffixes
   - Delete unused abstraction layer
   - Inline logic in restore_servers_from_config and haproxy_add_domain

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 14:15:00 +00:00
kaffa
bdc1f8a279 docs: Update CLAUDE.md with new features
- Update tool count: 21 → 22
- Add haproxy_wait_drain to server management tools
- Update haproxy_add_server: slot=0 for auto-select
- Update server.py description (~1750 lines, 22 tools)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 14:09:04 +00:00
kaffa
8694da0ff1 feat: Complete remaining improvements
MEDIUM PRIORITY:
4. restore operations - use haproxy_cmd_checked()
   - restore_servers_from_config() now validates commands
   - haproxy_restore_state() logs warnings for individual failures

5. container health check - add exception logging
   - logger.warning() for unexpected exceptions

6. haproxy_add_domain - optimize map file reading
   - Read map contents once, reuse for domain check and pool lookup
   - Eliminates redundant file reads

LOW PRIORITY:
7. Remove unused IP_PATTERN constant
   - Was unused since validate_ip() uses ipaddress module

8. haproxy_add_server - auto-select slot
   - slot=0 or slot=-1 finds first available slot
   - Returns error if all slots in use

9. haproxy_wait_drain - new tool
   - Wait for connections to drain before maintenance
   - Polls HAProxy stats until scur=0 or timeout

MCP Tools: 21 → 22

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 14:08:31 +00:00
kaffa
18d0126b15 fix: Improve consistency and add rollback support
1. haproxy_add_servers - disk-first pattern
   - Save ALL servers to config FIRST
   - Then update HAProxy
   - Rollback all on unexpected error
   - Rollback failed slots individually

2. remove_domain_from_config - file locking
   - Add fcntl.LOCK_EX for consistency with other config ops
   - Prevents race conditions during concurrent access

3. haproxy_add_domain - rollback on HAProxy failure
   - Wrap HAProxy map update in try/except
   - Rollback map file if HAProxy command fails
   - Rollback server config if server setup fails

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 14:03:31 +00:00
kaffa
ab5b4aa648 docs: Update CLAUDE.md with new features
- Update tool count: 20 → 21
- Add haproxy_set_domain_state to server management tools
- Add container status to health check example
- Update server.py description (~1600 lines, 21 tools)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 13:59:03 +00:00
kaffa
c48456dc18 feat: Add batch operations, container health, code cleanup
7. Add haproxy_set_domain_state() tool
   - Set all servers of a domain to ready/drain/maint at once
   - Useful for maintenance windows and deployments
   - Reports changed servers and any errors

8. Add container status to haproxy_health()
   - Check HAProxy container state via podman inspect
   - Reports "ok" when running, actual state otherwise
   - Sets overall health to "unhealthy" on container issues

9. Extract configure_server_slot() helper
   - Eliminates duplicated server configuration code
   - Used by haproxy_add_server() and haproxy_add_servers()
   - Cleaner, more maintainable code

MCP Tools: 20 → 21

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 13:58:14 +00:00
kaffa
4e7d0a8969 fix: Improve error handling and reliability
4. Pool exhaustion - explicit error
   - Add NoAvailablePoolError exception class
   - find_available_pool() now raises instead of returning None
   - haproxy_add_domain() catches and returns user-friendly error

5. haproxy_add_server - disk-first pattern
   - Save to config FIRST, then update HAProxy
   - If HAProxy update fails, rollback config automatically
   - Prevents inconsistency between disk and runtime

6. Wildcard removal - log failures
   - Changed silent pass to logger.warning()
   - Failures now visible in logs for debugging
   - Does not block domain removal operation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 13:54:24 +00:00
kaffa
b8fb4e7f4a fix: Add security and reliability improvements
1. File locking for config operations
   - add_server_to_config() and remove_server_from_config() now use
     exclusive file locking (fcntl.LOCK_EX) to prevent race conditions
   - Prevents data loss from concurrent modifications

2. Bulk server limit
   - Add MAX_BULK_SERVERS = 10 constant
   - haproxy_add_servers() now rejects requests exceeding limit
   - Prevents potential DoS via large payloads

3. HAProxy command response validation
   - Add haproxy_cmd_checked() helper function
   - Validates responses for error indicators (No such, not found, etc.)
   - State-modifying commands now properly detect and report failures
   - Read-only commands continue using haproxy_cmd()

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 13:50:47 +00:00
kaffa
7985501d48 docs: Update CLAUDE.md with new features and improvements
- Update tool count: 19 → 20
- Add HAPROXY_CONTAINER environment variable
- Add haproxy_add_servers (bulk operations) to tool list
- Document IPv6 support in haproxy_add_domain
- Add include_wildcards parameter note for haproxy_list_domains
- Update haproxy_reload: now auto-restores servers
- Add Bulk Server Operations section with example
- Update server.py description (~1500 lines, 20 tools)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 13:45:02 +00:00
kaffa
eebb1ca8df feat: Add bulk operations, wildcard support, and stability improvements
1. Add timeout to HAProxy response wait
   - Use select() for non-blocking recv with 30s timeout
   - Prevents indefinite blocking on slow/hung HAProxy

2. Log warnings for failed server clears
   - haproxy_remove_domain now logs warnings instead of silent pass
   - Helps debugging without breaking cleanup flow

3. Add HAPROXY_CONTAINER environment variable
   - Container name now configurable (default: "haproxy")
   - Used in reload_haproxy() and haproxy_check_config()

4. Add haproxy_add_servers() for bulk operations
   - Add multiple servers in single call
   - Accepts JSON array of server configs
   - More efficient than multiple haproxy_add_server calls

5. Add wildcard domain support in haproxy_list_domains()
   - New include_wildcards parameter (default: false)
   - Shows .domain entries with (wildcard) label when enabled

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 13:43:05 +00:00
kaffa
4c4ec24848 refactor: Improve stability and add IPv6 support
1. Fix order: Save to disk FIRST, then update HAProxy
   - Prevents inconsistency if HAProxy update succeeds but disk write fails
   - Data is preserved correctly on restart

2. Add IPv6 support
   - Use Python ipaddress module instead of regex
   - Now accepts both IPv4 and IPv6 addresses

3. Extract atomic_write_file() helper
   - Eliminates duplicated code in save_map_file, save_servers_config, haproxy_save_state
   - Single source of truth for atomic file operations

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 13:38:00 +00:00
kaffa
f17b02fddf fix: Auto-restore servers after haproxy_reload
haproxy_reload now automatically restores server configurations from
servers.json after a successful reload, preventing service disruption.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 13:32:03 +00:00
kaffa
85b4e9b4ed docs: Improve MCP tool docstrings with examples and usage guidance
- haproxy_add_domain: Explain pool backend, link to haproxy_add_server
- haproxy_add_server: Clarify slots 1-10 for load balancing, add examples
- haproxy_remove_server: Add usage example
- haproxy_list_servers: Explain output format with example
- haproxy_list_domains: Add output format example
- haproxy_health: Describe JSON structure and monitoring use case
- haproxy_domain_health: Explain status values (healthy/degraded/down)
- haproxy_set_server_state: Document ready/drain/maint with examples
- haproxy_set_server_weight: Explain weight ratio and soft disable
- haproxy_get_server_health: Clarify vs haproxy_domain_health
- haproxy_get_connections: Add usage examples

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 13:21:03 +00:00
kaffa
d51e982f7c docs: Update CLAUDE.md with health checks and environment variables
- Add Environment Variables table with all configurable options
- Add Health Check section with haproxy_health and haproxy_domain_health examples
- Update MCP Tools count from 17 to 19
- Add new Health Check tools section
- Update server.py description (line count and tool count)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 13:10:42 +00:00
kaffa
28df45900c Add health check endpoints and environment variable configuration
New features:
- haproxy_health: System-level health check (MCP, HAProxy, config files)
- haproxy_domain_health: Domain-specific health check with server status

Environment variables support:
- MCP_HOST, MCP_PORT: MCP server binding
- HAPROXY_HOST, HAPROXY_PORT: HAProxy Runtime API connection
- HAPROXY_STATE_FILE, HAPROXY_MAP_FILE, HAPROXY_SERVERS_FILE: File paths
- HAPROXY_POOL_COUNT, HAPROXY_MAX_SLOTS: Pool configuration
- LOG_LEVEL: Logging level (DEBUG, INFO, WARNING, ERROR)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 13:07:22 +00:00
kaffa
61dd4a69fc Improve code quality based on code review
Major improvements:
- Atomic file writes using temp file + rename pattern
- Structured logging with logging module (replaces print)
- StateField class for HAProxy state field indices
- Helper function get_backend_and_prefix() to reduce duplication
- Consistent exception chaining with 'from e'
- Proper fd/temp_path tracking to prevent resource leaks
- Added IOError handling in server management functions

Technical changes:
- save_map_file, save_servers_config, haproxy_save_state now use
  atomic writes with tempfile.mkstemp() + os.rename()
- Standardized on 'set server state ready' (was 'enable server')
- All magic numbers for state parsing replaced with StateField class

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 12:48:49 +00:00
kaffa
196374e70c Simplify backend configuration to HTTP only
Remove SSL/QUIC backend templates - all backends now use HTTP only
with SSL termination at HAProxy frontend. This improves performance
(~33% faster than HTTPS backends based on benchmarks).

Changes:
- server.py: Remove https_port parameter from all functions
- haproxy.cfg: Remove ssl/h3 server templates from pool backends
- CLAUDE.md: Update docs for HTTP-only backends and acme.sh

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 12:34:47 +00:00
root
432154c850 Initial commit: HAProxy MCP Server
- Zero-reload domain management with map-based routing
- 100 pool backends with 10 server slots each
- Runtime API integration for dynamic configuration
- Auto-restore servers from persistent config on startup
- 17 MCP tools for domain/server management

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 11:37:06 +00:00