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>
187 lines
8.1 KiB
Markdown
187 lines
8.1 KiB
Markdown
# MCP Package Registry
|
|
|
|
> Module 02 · Plane: Control · Roadmap phase: 2
|
|
> Part of [MCP Nexus architecture](../ARCHITECTURE.md).
|
|
|
|
## Purpose
|
|
|
|
The Package Registry is the catalog that answers one question for the rest of the
|
|
control plane: *"Given a service type, which MCP server should I install, from
|
|
where, at what version, and configured how?"* It maintains a searchable, locally
|
|
cached index of `Package` objects (Architecture §7) synced from one or more
|
|
remote indexes plus operator-authored local packages. It is the source of truth
|
|
for artifact provenance (source, digest, signature) and for the `config_schema`
|
|
that [Recipes](15-smart-recipes.md) bind to at install time.
|
|
|
|
The Registry does not run anything. It resolves and describes. The
|
|
[Auto Installer](03-auto-installer.md) consumes its resolutions; a `Package`
|
|
never becomes an `MCPInstance` inside this module.
|
|
|
|
## Responsibilities
|
|
|
|
- Maintain the local `Package` catalog in the **Registry** store (Architecture §8),
|
|
co-resident with Recipes.
|
|
- **Sync** from multiple remote indexes (a Nexus index is a signed JSON/HTTP
|
|
manifest listing packages) and merge them with local packages, with a
|
|
deterministic precedence order on collision.
|
|
- Provide **resolution**: `service type → recommended Package` (e.g. Home
|
|
Assistant → `homeassistant-mcp`).
|
|
- Provide **version selection**: given a `Package` and a constraint, return a
|
|
concrete version **pinned by digest**.
|
|
- Verify **signatures** on packages and index manifests before a package is
|
|
eligible for install (tenet #5, Architecture §9).
|
|
- Expose the `config_schema` and config **template reference** each package
|
|
carries, so Recipes can render config against it.
|
|
- Full-text and faceted **search** over name, service type, source, and tags for
|
|
the Dashboard and API.
|
|
|
|
## Non-goals
|
|
|
|
- Pulling images or creating containers — that is the [Installer](03-auto-installer.md)
|
|
via the Runtime.
|
|
- Deciding *whether* to install (matching a `DiscoveredResource` to a package) —
|
|
that is [Smart Recipes](15-smart-recipes.md) and the reconciler.
|
|
- Storing secrets or rendered config — templates are declarative; values come
|
|
from [Secrets](07-secrets-manager.md) at install time.
|
|
- Being an OCI registry itself. Nexus reads from GitHub / OCI / Docker Hub /
|
|
self-hosted repos; it does not host artifacts.
|
|
|
|
## Interfaces
|
|
|
|
```go
|
|
// Source of a package artifact (mirrors Package.source in Architecture §7).
|
|
type Source string
|
|
|
|
const (
|
|
SourceGitHub Source = "github"
|
|
SourceOCI Source = "oci"
|
|
SourceDockerHub Source = "dockerhub"
|
|
SourceLocal Source = "local"
|
|
)
|
|
|
|
// A concrete, install-ready version pinned by digest.
|
|
type PackageVersion struct {
|
|
Version string // semver-ish tag, e.g. "1.4.2"
|
|
Ref string // "ghcr.io/project/homeassistant-mcp:1.4.2"
|
|
Digest string // "sha256:..." — the install pin
|
|
Signature Signature
|
|
}
|
|
|
|
type Package struct {
|
|
Name string // "homeassistant-mcp"
|
|
ServiceType string // "home-assistant" — resolution key
|
|
Source Source
|
|
Versions []PackageVersion
|
|
ConfigSchema json.RawMessage // JSON Schema for config
|
|
ConfigTemplateRef string // e.g. "homeassistant.yaml"
|
|
Signature Signature
|
|
}
|
|
|
|
type Registry interface {
|
|
// Resolution: service type -> recommended package.
|
|
Resolve(ctx context.Context, serviceType string) (Package, error)
|
|
// Version selection against a constraint ("", "latest", "~1.4", "1.4.2").
|
|
SelectVersion(ctx context.Context, pkg string, constraint string) (PackageVersion, error)
|
|
Get(ctx context.Context, name string) (Package, error)
|
|
Search(ctx context.Context, q Query) ([]Package, error)
|
|
}
|
|
|
|
// Everything-is-a-plugin (tenet #3): remote index providers are swappable.
|
|
type IndexProvider interface {
|
|
Name() string
|
|
Fetch(ctx context.Context) ([]Package, IndexMeta, error) // signed manifest
|
|
}
|
|
|
|
type SyncManager interface {
|
|
Sync(ctx context.Context) (SyncReport, error) // merge all providers + local
|
|
AddIndex(url string, opts IndexOpts) error
|
|
}
|
|
```
|
|
|
|
HTTP/API surface (control-plane API, behind Auth):
|
|
|
|
- `GET /api/v1/packages?service_type=&source=&q=` — search/list.
|
|
- `GET /api/v1/packages/{name}` — full package incl. versions + schema.
|
|
- `POST /api/v1/packages` — register/upsert a **local** package.
|
|
- `POST /api/v1/registry/sync` — trigger a sync of all configured indexes.
|
|
- `GET /api/v1/registry/indexes` — configured remote indexes + last sync state.
|
|
|
|
## Data
|
|
|
|
- **Reads/writes:** `Package` objects in the **Registry** store (Architecture §8),
|
|
alongside `Recipe`s (which reference packages by name + `ConfigTemplateRef`).
|
|
- **Module-local persistence:** `registry_index` rows (index URL, public key,
|
|
last-synced digest, ETag/cursor), `package_version` rows (digest, ref,
|
|
signature status), and a cached copy of each fetched index manifest for
|
|
offline/deterministic resolution.
|
|
- Config templates referenced by `ConfigTemplateRef` are stored as opaque blobs
|
|
in the Registry store and rendered later by the Installer/Recipe engine.
|
|
|
|
Resolution flow: a `DiscoveredResource.type` (Architecture §7) is mapped by a
|
|
Recipe to a `serviceType`; `Resolve` returns the recommended `Package`;
|
|
`SelectVersion` pins it by digest. Example entry:
|
|
|
|
```
|
|
service_type: home-assistant
|
|
name: homeassistant-mcp
|
|
source: oci
|
|
ref: ghcr.io/project/homeassistant-mcp
|
|
versions: [1.4.2 @ sha256:…, 1.4.1 @ sha256:…]
|
|
config_template_ref: homeassistant.yaml
|
|
```
|
|
|
|
## Dependencies
|
|
|
|
- [Smart Recipes](15-smart-recipes.md) — consumes packages + config templates.
|
|
- [Auto Installer](03-auto-installer.md) — consumes resolved, digest-pinned versions.
|
|
- [Secrets Manager](07-secrets-manager.md) — config templates declare secret refs
|
|
filled at install time, not here.
|
|
- [Security](19-security.md) — signature/trust model for indexes and packages.
|
|
- [Plugin System](11-plugin-system.md) — `IndexProvider` is a plugin point.
|
|
- [Update Manager](10-update-manager.md) — watches index updates as one update source.
|
|
|
|
## Failure modes & handling
|
|
|
|
- **Remote index unreachable:** sync is best-effort; last good cached manifest
|
|
continues to serve `Resolve`/`SelectVersion`. Sync failures are surfaced via
|
|
[Notifications](13-notifications.md), never block the control loop.
|
|
- **Signature verification fails:** the offending package/version is marked
|
|
`untrusted` and excluded from resolution; it is never returned as installable.
|
|
- **Index collision (same package from two indexes):** deterministic precedence
|
|
(local > pinned-trusted index > community index); the shadowed entry is logged.
|
|
- **No package for a service type:** `Resolve` returns a typed `ErrNoPackage`;
|
|
the reconciler leaves the resource un-installed and low-confidence handling
|
|
applies (tenet #6).
|
|
- **Missing/invalid `config_schema`:** package is listed but flagged
|
|
`unschematized`; Recipes referencing it fail validation early, not at runtime.
|
|
|
|
## Security notes
|
|
|
|
- Every remote index manifest and every package is **signature-verified** before
|
|
eligibility (tenet #5, Architecture §9); trust anchors (public keys) are
|
|
configured per index.
|
|
- Versions are always **pinned by digest**; tags are advisory only. The digest is
|
|
what the Installer pulls.
|
|
- The Registry stores no secrets and renders no secret values; templates carry
|
|
only *references*.
|
|
- Mutating API calls (register local package, add index, sync) are audited
|
|
(Architecture §9) and gated by [RBAC](08-rbac.md).
|
|
|
|
## Open questions
|
|
|
|
- Community index governance and the trust/signing model for shared packages
|
|
(mirrors Architecture §12 "Recipe distribution").
|
|
- Do we support multiple recommended packages per service type with a ranking,
|
|
or strictly one canonical recommendation?
|
|
- Should `SelectVersion` honor per-package update policy hints, or is that owned
|
|
entirely by the [Update Manager](10-update-manager.md)?
|
|
- Config-template format: single YAML template vs. a small templating dialect
|
|
shared with Recipes.
|
|
|
|
## Milestone
|
|
|
|
Phase 2 — *Discover → install*. Registry v1 ships with local + GitHub/OCI package
|
|
indexes, digest-pinned versions, config schemas, and a starter package set so the
|
|
[Installer](03-auto-installer.md) can resolve (e.g.) Postgres → `postgres-mcp`
|
|
during the Phase 2 exit-criteria loop.
|