How it works

A router, a catalog, and a supervisor.

Roost is one program with three jobs. It answers sessions, it remembers what each server offers, and it keeps the servers running only as long as someone needs them.

Illustration: a still harbor at night with small wooden rowboats moored in a ring around one central wooden pier, a lantern glowing at its end.

The problem it replaced

Every Claude Code session used to start its own private copy of every stdio MCP server in its configuration. The servers were stateless wrappers around HTTP APIs, so the copies were identical and almost always idle.

When Roost was designed, 22 servers were configured and four sessions were open. The machine was running 92 server processes that used 5.85 GB of memory, and each new session paid for 22 interpreter startups before it could do anything.

Three parts

The router
Each logical server is mounted at its own path and speaks Streamable HTTP. Roost answers the session's initialize itself, from what the saved snapshot says the server can do, and forwards tool calls to the child.
The catalog
Roost saves each server's tools, resources, and prompts to disk, with a stamp that records when the server's watched files last changed. A tool list is answered from the snapshot, so listing never starts a server.
The supervisor
It keeps a registry of children, starts one on first use, stops it when it has been idle for its limit, and runs a circuit breaker for each. A background task also refreshes snapshots that have gone stale.

Life of a child

A child is one stdio process for one server.

  1. Cold

    No process exists. Tool lists are served from the snapshot. A server with no snapshot at all, on its very first run, is started once to be listed.

  2. Warm

    The first tool call starts the child and opens one connection to it. Every session's calls to that server go through that connection, up to the server's concurrency limit.

  3. Idle

    When nothing has used the child for its idle limit, 30 minutes unless the server says otherwise, Roost stops it. A child with a request in flight is never stopped, and a keep-warm server never is.

A server can also be marked per-session, which gives each session its own child, or passthrough, which relays to a server already running at a URL.

When a server misbehaves

Roost counts consecutive transport failures for each child. After three, the breaker opens: calls fail at once with the child's last lines of error output, so a caller learns why instead of waiting. After 60 seconds the breaker lets one probe call through, and only a successful call resets the count. Merely starting cleanly does not.

A request that is cut off by a deliberate stop (a restart, an idle stop, a reload, or a shutdown) fails with a message saying to retry it, and counts as no failure. A child that dies gives its in-flight calls an error that names it, and the next call starts a fresh one with backoff.

Keeping the catalog true

  • A snapshot refreshes when a child starts and its tool list differs, when a cold server's watched files have changed, and when you run roostctl refresh.
  • If a running server's watched files changed after its process started, refresh restarts it. The old process still holds the old code, so listing from it would be wrong. The restart is reported in the command's output, in the status endpoint, and in the log.
  • When a refresh changes the set of tools, resources, or prompts, Roost sends a list-changed notice to every live session, best effort.
  • Configuration changes (servers added, removed, or edited) are picked up by roostctl reload without restarting the daemon.

What it is built from

What it is built from
Language and librariesPython 3.12, the MCP Python SDK, Starlette, uvicorn, PyYAML, and psutil.
SizeAbout 3,950 lines in 15 files under src/roost.
Tests169 automated tests, including a soak test for rapid restarts and a check against the live fleet.
ProcessA macOS background process under launchd with keep-alive, run directly rather than through a wrapper script, because macOS ties a launchd job's disk permission to its program.
Not a containerA Linux container cannot start the Mac-native environments the servers run in, and a container VM would give back the memory the hub saves.

Child process IDs are found by comparing the process tree before and after a start. The SDK exposes none. They are used for display and logging only, never for correctness.

Contact

Questions about this tool?

Write to Renkara Media Group and a person will answer.