Files
mcp-gateway-nexus/docs/modules/03-auto-installer.md
drjones 8d3ffef920 docs: initial architecture and design for MCP Nexus
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>
2026-07-07 04:38:27 +00:00

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, registered MCPInstance.
  • 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 MCPInstance in 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), transitioning state through Pulling → Creating → Starting → HealthGating → Running (or Failed).
  • Reads: Package/PackageVersion and config template from the Registry; Recipe for constraints; Secret references 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.config is the rendered template with secret values redacted to references — secret material is never persisted here.

Dependencies

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-id absent 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 stored MCPInstance.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.