Upstreams and packages
Each server is exactly one of:
package: an npm or PyPI package the hub installs, then runs over stdio;command: an executable already on the host, over stdio;url: a remote server over Streamable HTTP, or legacy SSE.
Lifecycle
Section titled “Lifecycle”States: installing, starting, ready, degraded (was ready, reconnecting), failed (never got
ready, retrying), stopped.
A server is ready once initialize and tools/list succeed. A lost connection or a crashed
child schedules a retry with exponential backoff, from 1 second up to 5 minutes. A server that is
not ready exposes no tools, and every change to an upstream’s tools rebuilds the routing table and
sends tools/list_changed to connected clients. Servers start in parallel and fail independently.
Servers can also be switched off from the UI or over MCP without touching the file. That state is
kept in state.json, on top of what the file says, so it works with a read-only server file.
Isolation
Section titled “Isolation”A stdio child is spawned without a shell, with its arguments as an array. Its environment is a
minimal base (PATH, HOME and the like) plus the env its entry declares, and nothing else: the
hub’s own secrets and other servers’ tokens never reach it. Install commands get the same scrubbed
environment, plus the proxy and registry variables they need.
Packages and versions
Section titled “Packages and versions”Packages are installed by the hub, never fetched on the fly:
$MCPYARD_DATA_DIR/packages/<server>/<version>/- npm:
npm install --ignore-scripts --prefix <dir> <pkg>@<version>, then the bin is run from<dir>/node_modules/.bin/. - PyPI:
uv venv <dir>, thenuv pip install --no-build --python <dir> <pkg>==<version>, run from<dir>/bin/.
A marker file written after a successful install tells a complete directory from an interrupted
one. Install-time code is off by default (no npm lifecycle scripts, wheels only); allowScripts
turns it on per server.
Each server has one version source:
- Pinned:
versionis in the file. That version runs; only an edit changes it. - Hub-owned:
versionis omitted. The first start installs the latest and records it inversions.json; later starts reuse the recorded version, so a restart never upgrades anything.
Registries are asked for the latest version on a period, one request per package, with the result cached for that period. An update of a hub-owned package runs side by side:
- install the new version into its own directory;
- start it next to the running one;
- wait for
initializeandtools/list; - switch traffic to it, then stop the old process;
- record the new version and keep the previous one for rollback; delete older directories.
If any step fails, the old version keeps serving and the error is kept on the server. Updates are
triggered by a person (UI, yard-update_*) or, when switched on, by the nightly schedule.
Node’s timers top out at about 24.8 days (MAX_TIMER): longer periods are clamped to it.