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>
176 lines
8.7 KiB
Markdown
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.
|