Hot-Reload Configuration¶
Reload configuration without restarting the server. Add, remove, or modify MCP servers while preserving active connections for unchanged MCP servers.
Quick Start¶
# Start server
mcp-hangar serve --http --port 8000
# Edit config.yaml in another terminal - changes apply automatically
# Or trigger manually
kill -HUP $(pgrep -f "mcp-hangar serve")
Overview¶
| Trigger | Latency | Use Case |
|---|---|---|
| File watcher (watchdog) | ~1s | Development, real-time updates |
| File polling | 5s (configurable) | Environments without inotify/fsevents |
| SIGHUP signal | Immediate | Scripted deployments, CI/CD |
| MCP tool | Immediate | Interactive reload from AI assistant |
All reload operations are atomic: changes are validated before application. Invalid configuration is rejected; current config preserved.
Configuration¶
# Optional: customize hot-reload behavior
config_reload:
enabled: true # default: true
use_watchdog: true # default: true, falls back to polling
interval_s: 5 # polling interval when watchdog unavailable
Options¶
| Option | Type | Default | Description |
|---|---|---|---|
enabled | bool | true | Enable automatic file watching |
use_watchdog | bool | true | Use watchdog library (inotify/fsevents) |
interval_s | int | 5 | Polling interval in seconds |
Triggering Reload¶
Automatic File Watching¶
Enabled by default. Uses watchdog for efficient file system events with polling fallback.
SIGHUP Signal¶
Standard Unix reload pattern. Does not terminate the process.
# Find process
pgrep -f "mcp-hangar serve"
# Reload
kill -HUP <PID>
# One-liner
kill -HUP $(pgrep -f "mcp-hangar serve")
MCP Tool¶
Reload from Claude Desktop or any MCP client:
Response:
{
"status": "success",
"mcp_servers_added": ["new-api"],
"mcp_servers_removed": ["deprecated-service"],
"mcp_servers_updated": ["modified-mcp-server"],
"mcp_servers_unchanged": ["stable-mcp-server"],
"duration_ms": 45.2
}
Reload Behavior¶
| Scenario | Behavior | Final State |
|---|---|---|
| Added | Registered but not started | COLD |
| Removed | Stopped gracefully, then removed | (deleted) |
| Modified | Old stopped, new registered | COLD |
| Unchanged | No action taken | Preserved |
Compared Fields¶
Changes to any of these fields trigger MCP server restart:
| Field | Description |
|---|---|
mode | MCP Server mode (subprocess, docker, remote) |
command | Command and arguments |
image | Container image |
endpoint | Remote endpoint URL |
env | Environment variables |
volumes | Volume mounts |
network | Network mode |
user | User/group ID |
idle_ttl_s | Idle timeout |
health_check_interval_s | Health check interval |
max_consecutive_failures | Failure threshold |
Normalization:
{}is equivalent tonullforenv,resources[]is equivalent tonullforvolumes,command- Missing fields use default values
Examples¶
Add MCP Server¶
Before:
After:
mcp_servers:
math:
mode: subprocess
command: [python, -m, math_server]
filesystem:
mode: subprocess
command: [npx, -y, "@modelcontextprotocol/server-filesystem"]
args: ["/home/user/documents"]
Result: math unchanged, filesystem added in COLD state.
Update MCP Server¶
Before:
After:
mcp_servers:
database:
mode: remote
endpoint: https://db-mcp.internal/v2
idle_ttl_s: 600
env:
POOL_SIZE: "10"
Result: database stopped gracefully, new instance created.
Remove MCP Server¶
Before:
mcp_servers:
legacy-api:
mode: subprocess
command: [python, legacy_server.py]
modern-api:
mode: remote
endpoint: https://api.example.com/mcp
After:
Result: legacy-api stopped and removed, modern-api unchanged.
Change MCP Server Mode¶
Before:
After:
mcp_servers:
search:
mode: docker
image: search-mcp:v2
env:
INDEX_PATH: /data/index
volumes:
- ./data:/data:ro
Result: Subprocess stopped, Docker container started.
Events¶
Hot-reload emits domain events for observability:
| Event | When |
|---|---|
ConfigurationReloadRequested | Before reload starts |
ConfigurationReloaded | After successful reload |
ConfigurationReloadFailed | On validation/apply failure |
ConfigurationReloaded(
config_path="/app/config.yaml",
mcp_servers_added=["new-api"],
mcp_servers_removed=["old-api"],
mcp_servers_updated=["modified"],
mcp_servers_unchanged=["stable"],
reload_duration_ms=42.5,
requested_by="file_watcher"
)
Disabling Hot-Reload¶
SIGHUP and MCP tool remain functional for manual reload.
Monitoring¶
Log Events¶
| Event | Description |
|---|---|
config_reload_worker_started | Worker initialized |
config_file_modified_detected | File change detected |
triggering_config_reload | Reload initiated |
configuration_reloaded | Reload successful |
configuration_reload_failed | Reload failed |
Structured Logs¶
{"event": "configuration_reloaded", "config_path": "config.yaml",
"duration_ms": 42.5, "added": 1, "removed": 0, "updated": 2}
Limitations¶
| Limitation | Workaround |
|---|---|
| MCP Server groups cleared on reload | Groups reconstructed from new config |
| Hot-loaded MCP servers unaffected | Use hangar_unload to manage separately |
| Event store config requires restart | Restart server for event store changes |
| In-flight requests | Completed before MCP server stops (graceful mode) |
Troubleshooting¶
Reload Not Triggering¶
# Check watchdog installed
pip list | grep watchdog
# Check worker status
grep "config_reload_worker" logs/mcp-hangar.log
# Manual reload
kill -HUP $(pgrep -f "mcp-hangar serve")
Invalid Configuration¶
# Validate YAML
python -c "import yaml; yaml.safe_load(open('config.yaml'))"
# Check mcp_servers section
grep -q "^mcp_servers:" config.yaml && echo "OK" || echo "Missing"
# Check logs
grep "configuration_reload_failed" logs/mcp-hangar.log
MCP Server Not Starting¶
# Check mcp_server added
grep "mcp_server_added" logs/mcp-hangar.log
# Check status
hangar_status()
# Start manually
hangar_start(mcp_server="my-mcp-server")
Security¶
| Consideration | Implementation |
|---|---|
| Validation before apply | Invalid config rejected |
| Atomic operations | All-or-nothing semantics |
| Graceful shutdown | Active requests complete first |
| Audit trail | All reloads logged with source |
| File permissions | Ensure config not world-writable |