Refs #2 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TWLFEoRaRafJm1SpdhWQ6F
xy — HTTP MCP server supervisor
Daemon + CLI that launches and supervises HTTP-based MCP servers.
Build
cargo build --release
Run
target/release/xy daemon # foreground
Drop a server definition into $XDG_CONFIG_HOME/xy/servers/<name>.kdl
(see examples/insikt.kdl) and xy reload.
Commands:
xy list
xy status <name>
xy start <name|--all>
xy stop <name|--all>
xy restart <name|--all>
xy reload
xy logs <name> [--tail N] [--follow]
Exit codes: 0 success, 1 operational error, 2 daemon unreachable, 3 config invalid.
Waiting on a dependency
A server can declare a precondition that must hold before it is started:
wait-for {
path "~/.orbstack/run/docker.sock"
timeout "180s"
interval "1s"
}
Exactly one condition per block — path "<p>" (the path exists),
tcp "<host:port>" (a connection succeeds), or command "<c>" with optional
args (the process exits 0). timeout defaults to 120s and interval to 1s.
While waiting the server reports state waiting, and xy status <name> shows
which condition it is blocked on and for how long. If the timeout expires the
server is marked failed and is never spawned.
Waiting does not consume the restart budget: a slow dependency costs patience, not retries. This is what stops a Docker-backed server from being marked failed at login while the Docker daemon is still starting.
Servers that read stdin
MCP servers are stdio-first by convention, and several that also speak HTTP
still start their stdio transport unconditionally. Under the launchd agent the
daemon's stdin is /dev/null, so such a server sees EOF on its first read and
shuts down seconds after binding its port.
stdin picks what the child gets on fd 0:
stdin "keep-open"
inherit(default) — the child inherits the daemon's stdin.null—/dev/null; a read sees EOF immediately.keep-open— a pipe thatxyholds open for the child's lifetime and never writes to, so a read blocks instead of seeing EOF.
Use keep-open for a server you want supervised in HTTP mode that insists on
running its stdio transport anyway.
Start on login (macOS)
xy service install # write the LaunchAgent, load it, start the daemon
xy service uninstall # unload and remove the agent
xy service start # load an installed agent
xy service stop # unload until the next login
xy service status # show agent state
xy service install snapshots the current PATH into the agent, because a
launchd agent otherwise inherits only /usr/bin:/bin:/usr/sbin:/sbin and
supervised servers would not find their toolchains. Re-run with --force
after installing a new toolchain to refresh the snapshot.
xy service stop lasts until the next login. To disable start-on-login
permanently, use xy service uninstall.
The daemon writes to $XDG_STATE_HOME/xy/logs/daemon.log. Failures that happen
before the daemon starts logging — a missing binary, a malformed plist — are
visible only to launchd:
launchctl print gui/$UID/se.aceofba.xy