Index
Feed
Written by rowanp · Jun 23, 2026
MCP servers die quietly mid-session and the agent just... stops using the tool. Run long-lived servers under a supervisor (launchd or systemd), health-check them on a timer, and log restarts so you can see which server is flaky.
stdio servers are children of the agent process; the agent restarts them and there's nothing to supervise. This recipe is for servers you run as standalone HTTP/SSE services — shared Postgres MCP, a team Sentry MCP, anything with its own port.
claude mcp listA listening port proves nothing — wedged servers keep their sockets. Probe the MCP endpoint with an initialize request and require a sane JSON-RPC response within a timeout.
#!/usr/bin/env bash
# /usr/local/bin/mcp-health — exit 0 healthy, 1 not
set -u
URL="http://127.0.0.1:8931/mcp"
resp=$(curl -sf --max-time 5 -X POST "$URL" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"healthcheck","version":"1.0"}}}') || exit 1
echo "$resp" | grep -q '"serverInfo"' || exit 1! Common failures
Did this recipe work for you?
Sign in to add your report — every count here is backed by a named account.
launchd restarts the process on exit and throttles crash loops for free. Save as ~/Library/LaunchAgents/com.myteam.mcp-postgres.plist.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
<key>Label</key><string>com.myteam.mcp-postgres</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/npx</string>
<string>-y</string>
<string>@modelcontextprotocol/server-postgres</string>
<string>--port</string><string>8931</string>
</array>
<key>KeepAlive</key><true/>
<key>ThrottleInterval</key><integer>10</integer>
<key>StandardErrorPath</key><string>/tmp/mcp-postgres.err.log</string>
</dict></plist>Bootstrap the job, then kill the process on purpose and watch launchd bring it back. If you skip the kill test you don't have supervision, you have hope.
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.myteam.mcp-postgres.plist
launchctl print gui/$(id -u)/com.myteam.mcp-postgres | grep state
pkill -f server-postgres && sleep 12 && /usr/local/bin/mcp-health && echo 'auto-restart OK'Restart=always plus a watchdog timer running the health check. WatchdogSec would be cleaner but most MCP servers don't speak sd_notify, so an external check that restarts on failure is the pragmatic version.
# ~/.config/systemd/user/mcp-postgres.service
[Unit]
Description=Postgres MCP server
[Service]
ExecStart=/usr/bin/npx -y @modelcontextprotocol/server-postgres --port 8931
Restart=always
RestartSec=5
[Install]
WantedBy=default.target
# ~/.config/systemd/user/mcp-health.timer runs mcp-health.service every 60s;
# the service runs: /usr/local/bin/mcp-health || systemctl --user restart mcp-postgresRestart counts are your signal-to-noise meter. A server restarting daily has a real bug — supervision is a tourniquet, not a cure. Check the journal weekly.
systemctl --user daemon-reload
systemctl --user enable --now mcp-postgres.service mcp-health.timer
journalctl --user -u mcp-postgres --since '7 days ago' | grep -c 'Started' Related records
Strong evidence gets promoted into the record above.
The kill test in step 4 is the right instinct. I'd add: test the failure you actually have. Ours was OOM, not crash — the kernel killed the server under memory pressure and launchd restarted it into the same pressure. MemoryHigh= in the systemd unit was the real fix.
Sign in to join the discussion, vote, and verify fixes.
Read the manual footnote: launchd's ThrottleInterval is a minimum time between starts, not a delay before restart. If your server exits instantly, launchd may still consider it 'running too briefly' and back off harder. The 10s value in the plist is right for most cases; don't set it to 0.