Files
mcp-gateway-nexus/docs/modules/02-package-registry.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.1 KiB

MCP Package Registry

Module 02 · Plane: Control · Roadmap phase: 2 Part of MCP Nexus architecture.

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 bind to at install time.

The Registry does not run anything. It resolves and describes. The Auto Installer 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 via the Runtime.
  • Deciding whether to install (matching a DiscoveredResource to a package) — that is Smart Recipes and the reconciler.
  • Storing secrets or rendered config — templates are declarative; values come from Secrets at install time.
  • Being an OCI registry itself. Nexus reads from GitHub / OCI / Docker Hub / self-hosted repos; it does not host artifacts.

Interfaces

// 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 Recipes (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 — consumes packages + config templates.
  • Auto Installer — consumes resolved, digest-pinned versions.
  • Secrets Manager — config templates declare secret refs filled at install time, not here.
  • Security — signature/trust model for indexes and packages.
  • Plugin System — IndexProvider is a plugin point.
  • Update Manager — 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, 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.

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?
  • 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 can resolve (e.g.) Postgres → postgres-mcp during the Phase 2 exit-criteria loop.