MCP Nexus is a self-hosted control plane for MCP servers — "Kubernetes for
MCP." Agents connect to one endpoint; Nexus discovers services, installs the
right MCP servers, aggregates them behind a namespaced router, secures access
with auth/RBAC, and heals/updates them via a reconcile loop.
This first commit is design-phase only (no runnable code yet):
- README.md project front door + module map
- docs/ARCHITECTURE.md target design: tenets, two-plane split, reconcile
loop, domain types, storage, security, deployment
- docs/ROADMAP.md phased delivery (foundations -> walking skeleton ->
discover+install -> secure -> operate -> extend/scale)
- docs/modules/01-20 one design doc per module, all cross-linked
Backend stack decision: Go (single static binary, embedded SQLite + embedded
React dashboard). Repo initialized in /root with a whitelist .gitignore so
only project files are tracked.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
8.7 KiB
Auto Installer
Module 03 · Plane: Control · Roadmap phase: 2 Part of MCP Nexus architecture.
Purpose
The Auto Installer is the actor that turns a matched triple —
(DiscoveredResource + Package + Recipe) — into a running, registered
MCPInstance (Architecture §7) with no manual work. It is the act step of
the reconciliation loop (Architecture §6) for new installs: given a plan from the
reconciler, it pulls the pinned image, creates a sandboxed container through
the Runtime abstraction, renders config,
injects secrets at runtime, starts, health-gates, and registers the instance so
the Router/Gateway begins exposing its tools.
Per tenet #2 it is reconcile, not script: the Installer is invoked by the reconciler with a desired spec and must be idempotent — re-running with the same inputs yields no duplicate containers and no duplicate instances.
Responsibilities
- Accept an install
Plan(bound resource + resolved, digest-pinned package version + recipe) and drive it to a healthy, registeredMCPInstance. - Registry lookup / resolution: obtain the concrete
PackageVersion(digest) from the Registry (the reconciler usually pre-resolves; the Installer re-validates the pin). - Pull the image by digest through the target Runtime.
- Create a sandboxed container: least privilege, read-only rootfs where possible, no host network unless the recipe demands it (Architecture §9).
- Render the recipe's config template against the package
config_schema, injecting Secrets at runtime (env/file mount) — never baking secret values into the image, the stored spec, or logs. - Start, then health-gate: block registration until the instance passes an initial readiness check (handed off to Health).
- Register the
MCPInstancein the Inventory store and emit an event so the Router refreshes its upstream set. - Be idempotent and support rollback on any failed step.
- Target multiple Runtimes (Docker first; K8s/containerd later) discovered by Infrastructure Discovery.
Non-goals
- Deciding what to install or whether to (matching + policy) — that is the reconciler + Recipes.
- Choosing the version or verifying signatures — that is the Registry (the Installer trusts the digest-pinned, verified version handed to it, and re-checks the pin).
- Ongoing liveness/restart — owned by Health Monitoring.
- Applying version upgrades — a re-install driven by the Update Manager reuses this module.
Interfaces
type Plan struct {
Resource DiscoveredResource // bound_resource_uuid target
Version PackageVersion // digest-pinned (Registry)
Recipe Recipe // config template + runtime constraints
}
type Installer interface {
// Idempotent: same Plan -> same MCPInstance, no duplicate container.
Install(ctx context.Context, p Plan) (MCPInstance, error)
Rollback(ctx context.Context, instanceID string) error
}
// Runtime abstraction (tenet #3). Docker first; K8s/containerd adapters later,
// selected per-target from Infrastructure Discovery (Module 16).
type Runtime interface {
Name() string // "docker", "k8s", ...
Pull(ctx context.Context, ref, digest string) error
Create(ctx context.Context, spec ContainerSpec) (ContainerRef, error)
Start(ctx context.Context, ref ContainerRef) error
Stop(ctx context.Context, ref ContainerRef) error
Remove(ctx context.Context, ref ContainerRef) error
Inspect(ctx context.Context, ref ContainerRef) (ContainerStatus, error)
}
// Sandbox defaults come from Security (Module 19); recipes may widen with reason.
type ContainerSpec struct {
Ref string
Digest string
Labels map[string]string // includes nexus.instance-id for idempotency
Env []EnvVar // secret refs resolved at start
Mounts []Mount // incl. secret file mounts (tmpfs)
ReadOnlyRoot bool
NoNewPrivs bool
Network NetworkMode
}
Idempotency key: the Installer derives a stable instance-id from
(package, version-digest, bound_resource_uuid, recipe-hash) and stamps it as a
container label and the MCPInstance.id. Before creating anything it queries
the Runtime for a container carrying that label and adopts it instead of
re-creating.
No public HTTP surface of its own; it is invoked in-process by the reconciler. Install progress is observable via Inventory state transitions and Metrics.
Data
- Writes:
MCPInstance{id, package, version, config, container_ref, state, health, bound_resource_uuid}into the Inventory store (Architecture §8), transitioningstatethroughPulling → Creating → Starting → HealthGating → Running(orFailed). - Reads:
Package/PackageVersionand config template from the Registry;Recipefor constraints;Secretreferences resolved via Secrets at start time. - Module-local persistence: an install-attempt journal (step, timestamp, outcome) used for rollback and audit; the previous known-good spec is retained to support Update Manager rollback.
- The stored
MCPInstance.configis the rendered template with secret values redacted to references — secret material is never persisted here.
Dependencies
- MCP Package Registry — resolved, pinned version + template.
- Smart Recipes — config template + runtime constraints.
- Secrets Manager — runtime secret injection.
- Infrastructure Discovery — discovers Runtimes/targets.
- Health Monitoring — the health gate + post-install liveness.
- Gateway / Dynamic Tool Registry — consume the registered instance's tools.
- Security — sandbox defaults.
- Update Manager — re-invokes Install for version upgrades.
Failure modes & handling
- Pull fails (network, missing digest): mark
Failed, no container created, reconciler retries with backoff; nothing to roll back. - Create/start fails: run
Rollback— stop+remove any partial container, release reserved secret mounts, revert Inventory to pre-install state. - Health gate times out: treat as failed install; roll back the new container so the Gateway never sees a broken instance. On an update re-install, restore the previous known-good spec (see Update Manager).
- Duplicate on re-run (idempotency): the label lookup adopts the existing
container; no second container, no second
MCPInstance. - Crash mid-install: the install-attempt journal + label lookup let the next
reconcile cycle resume or clean up orphaned containers (garbage-collect
containers labelled with an
instance-idabsent from Inventory). - Runtime unavailable (e.g. Docker socket down): install is deferred, not errored permanently; data plane is unaffected (Architecture §5).
Security notes
- Containers are sandboxed by default (Architecture §9): least privilege,
NoNewPrivs, read-only rootfs where possible, no host network unless the recipe explicitly requires it with a recorded reason. - Secrets injected at runtime only — env vars or tmpfs file mounts populated
at
Start; never in the image, the storedMCPInstance.config, or logs. - Only digest-pinned, signature-verified versions are installed; the pin is re-validated against the Registry before pull.
- Every install/rollback is an audited mutating control-plane action (Architecture §9) with principal (reconciler/user), target, and outcome.
Open questions
- How much Docker installer semantics map cleanly to a K8s operator vs. a separate controller (Architecture §12 "K8s runtime parity")?
- Concurrency/locking model when two reconcile cycles race on the same
instance-id— advisory lock in Inventory vs. Runtime-level create idempotency. - Orphan GC cadence and safety margin before removing an unrecognized Nexus-labelled container.
- Whether the health gate criteria live in the Recipe or default from the Package
config_schema.
Milestone
Phase 2 — Discover → install. Installer v1 + Docker Runtime satisfies the phase
exit criteria: a Postgres container appears on the LAN and, within one reconcile
cycle, postgres.query is live at the Gateway with no human
action — idempotently, with rollback on failure.