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

176 lines
8.7 KiB
Markdown

# Auto Installer
> Module 03 · Plane: Control · Roadmap phase: 2
> Part of [MCP Nexus architecture](../ARCHITECTURE.md).
## 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](16-infrastructure-discovery.md) abstraction, renders config,
injects secrets at runtime, starts, health-gates, and registers the instance so
the [Router/Gateway](04-gateway.md) 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](02-package-registry.md) (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](07-secrets-manager.md) 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](09-health-monitoring.md)).
- **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](16-infrastructure-discovery.md).
## Non-goals
- Deciding *what* to install or *whether* to (matching + policy) — that is the
reconciler + [Recipes](15-smart-recipes.md).
- Choosing the version or verifying signatures — that is the
[Registry](02-package-registry.md) (the Installer trusts the digest-pinned,
verified version handed to it, and re-checks the pin).
- Ongoing liveness/restart — owned by [Health Monitoring](09-health-monitoring.md).
- Applying version upgrades — a re-install driven by the
[Update Manager](10-update-manager.md) reuses this module.
## Interfaces
```go
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](18-metrics.md).
## 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](02-package-registry.md); `Recipe` for constraints; `Secret`
references resolved via [Secrets](07-secrets-manager.md) 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
- [MCP Package Registry](02-package-registry.md) — resolved, pinned version + template.
- [Smart Recipes](15-smart-recipes.md) — config template + runtime constraints.
- [Secrets Manager](07-secrets-manager.md) — runtime secret injection.
- [Infrastructure Discovery](16-infrastructure-discovery.md) — discovers Runtimes/targets.
- [Health Monitoring](09-health-monitoring.md) — the health gate + post-install liveness.
- [Gateway](04-gateway.md) / [Dynamic Tool Registry](05-dynamic-tool-registry.md) —
consume the registered instance's tools.
- [Security](19-security.md) — sandbox defaults.
- [Update Manager](10-update-manager.md) — 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](10-update-manager.md)).
- **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](04-gateway.md) with no human
action — idempotently, with rollback on failure.