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>
This commit is contained in:
175
docs/modules/03-auto-installer.md
Normal file
175
docs/modules/03-auto-installer.md
Normal file
@@ -0,0 +1,175 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user