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

TriggerLatencyUse Case
File watcher (watchdog)~1sDevelopment, real-time updates
File polling5s (configurable)Environments without inotify/fsevents
SIGHUP signalImmediateScripted deployments, CI/CD
MCP toolImmediateInteractive 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

OptionTypeDefaultDescription
enabledbooltrueEnable automatic file watching
use_watchdogbooltrueUse watchdog library (inotify/fsevents)
interval_sint5Polling 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:

hangar_reload_config()                    # Graceful reload
hangar_reload_config(graceful=false)      # Immediate shutdown

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

ScenarioBehaviorFinal State
AddedRegistered but not startedCOLD
RemovedStopped gracefully, then removed(deleted)
ModifiedOld stopped, new registeredCOLD
UnchangedNo action takenPreserved

Compared Fields

Changes to any of these fields trigger MCP server restart:

FieldDescription
modeMCP Server mode (subprocess, docker, remote)
commandCommand and arguments
imageContainer image
endpointRemote endpoint URL
envEnvironment variables
volumesVolume mounts
networkNetwork mode
userUser/group ID
idle_ttl_sIdle timeout
health_check_interval_sHealth check interval
max_consecutive_failuresFailure threshold

Normalization:

  • {} is equivalent to null for env, resources
  • [] is equivalent to null for volumes, command
  • Missing fields use default values

Examples

Add MCP Server

Before:

mcp_servers:
  math:
    mode: subprocess
    command: [python, -m, math_server]

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:

mcp_servers:
  database:
    mode: remote
    endpoint: https://db-mcp.internal/v1
    idle_ttl_s: 300

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:

mcp_servers:
  modern-api:
    mode: remote
    endpoint: https://api.example.com/mcp

Result: legacy-api stopped and removed, modern-api unchanged.

Change MCP Server Mode

Before:

mcp_servers:
  search:
    mode: subprocess
    command: [python, -m, search_v1]

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:

EventWhen
ConfigurationReloadRequestedBefore reload starts
ConfigurationReloadedAfter successful reload
ConfigurationReloadFailedOn 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

config_reload:
  enabled: false

SIGHUP and MCP tool remain functional for manual reload.

Monitoring

Log Events

EventDescription
config_reload_worker_startedWorker initialized
config_file_modified_detectedFile change detected
triggering_config_reloadReload initiated
configuration_reloadedReload successful
configuration_reload_failedReload failed

Structured Logs

{"event": "configuration_reloaded", "config_path": "config.yaml",
 "duration_ms": 42.5, "added": 1, "removed": 0, "updated": 2}

Limitations

LimitationWorkaround
MCP Server groups cleared on reloadGroups reconstructed from new config
Hot-loaded MCP servers unaffectedUse hangar_unload to manage separately
Event store config requires restartRestart server for event store changes
In-flight requestsCompleted 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

ConsiderationImplementation
Validation before applyInvalid config rejected
Atomic operationsAll-or-nothing semantics
Graceful shutdownActive requests complete first
Audit trailAll reloads logged with source
File permissionsEnsure config not world-writable