feat: Phase 0/1 — runnable MCP gateway (aggregation + namespaced routing)

Implements the walking skeleton from the roadmap: agents connect to one HTTP
endpoint and Nexus aggregates upstream MCP servers behind it with namespaced
tools. Written in Go (single static binary), no external services required.

What works end-to-end (verified live + hermetic e2e tests):
- MCP protocol layer: JSON-RPC 2.0 + initialize/tools.list/tools.call
  (internal/mcp), with stdio (subprocess) and Streamable HTTP client
  transports, and a reusable stream transport for in-process wiring.
- Router + Dynamic Tool Registry: connects/initializes upstreams in parallel,
  loads tools, namespaces them as {namespace}.{tool}, dispatches tools/call to
  the owning upstream; refreshes on list_changed. A failed upstream stays
  not-ready without taking down the gateway (data plane stays up).
- Gateway: single MCP endpoint (POST /mcp) that is an MCP server to agents,
  plus /healthz and a minimal /metrics exposition.
- CLI (cmd/nexus): `serve`, `connect` (stdio<->HTTP bridge for local agents),
  `demo-mcp` (built-in zero-dep demo server: echo/add/now), `version`.
- Config: declarative YAML with env expansion + validation.
- Store: persistence interfaces + in-memory impl (SQLite lands later).

Foundations for later phases: domain types (ARCHITECTURE §7), two-plane
split, event-driven refresh seam.

Tooling: Makefile (build/test/vet/fmt with version ldflags), config.example
.yaml. GOPATH moved to /go so it doesn't collide with the module root at /root;
.gitignore whitelist extended to track Go sources while ignoring build output.

Tests: unit (registry/namespacing) + full e2e (client -> demo server over
pipes -> router -> HTTP gateway: initialize, aggregated tools/list, tool-call
routing, unknown-tool error). go vet + gofmt clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
drjones
2026-07-07 09:43:08 +00:00
parent 8d3ffef920
commit 72020b1901
25 changed files with 2450 additions and 0 deletions

153
internal/domain/domain.go Normal file
View File

@@ -0,0 +1,153 @@
// Package domain defines the core vocabulary shared across every MCP Nexus
// module, as described in docs/ARCHITECTURE.md §7. These types are the shared
// language of both the data plane (Gateway/Router) and the control plane
// (Discovery/Registry/Installer/…).
package domain
import "time"
// Plane distinguishes the two planes of the system (ARCHITECTURE §5).
type Plane string
const (
PlaneData Plane = "data"
PlaneControl Plane = "control"
)
// InstanceState is the lifecycle state of a managed MCP server (see the Health
// Monitoring module doc for the full state machine).
type InstanceState string
const (
StateUnknown InstanceState = "unknown"
StatePending InstanceState = "pending"
StateStarting InstanceState = "starting"
StateRunning InstanceState = "running"
StateUnhealthy InstanceState = "unhealthy"
StateOffline InstanceState = "offline"
StateUpdating InstanceState = "updating"
StateRestarting InstanceState = "restarting"
StateQuarantine InstanceState = "quarantined"
)
// Health is a coarse health verdict for a resource or instance.
type Health string
const (
HealthUnknown Health = "unknown"
HealthHealthy Health = "healthy"
HealthDegraded Health = "degraded"
HealthUnhealthy Health = "unhealthy"
)
// TransportKind identifies how Nexus talks MCP to an upstream server.
type TransportKind string
const (
// TransportStdio spawns the server as a subprocess and speaks
// newline-delimited JSON-RPC over stdin/stdout.
TransportStdio TransportKind = "stdio"
// TransportHTTP connects to a server over MCP Streamable HTTP.
TransportHTTP TransportKind = "http"
)
// DiscoveredResource is a service found on the network by the Discovery Engine.
type DiscoveredResource struct {
UUID string `json:"uuid"`
Type string `json:"type"`
Version string `json:"version,omitempty"`
IP string `json:"ip,omitempty"`
Hostname string `json:"hostname,omitempty"`
Ports []int `json:"ports,omitempty"`
Capabilities []string `json:"capabilities,omitempty"`
Health Health `json:"health"`
Confidence float64 `json:"confidence"`
Source string `json:"source,omitempty"`
Attributes map[string]string `json:"attributes,omitempty"`
FirstSeen time.Time `json:"first_seen"`
LastSeen time.Time `json:"last_seen"`
}
// PackageSource identifies where an MCP package artifact comes from.
type PackageSource string
const (
SourceGitHub PackageSource = "github"
SourceOCI PackageSource = "oci"
SourceDockerHub PackageSource = "dockerhub"
SourceLocal PackageSource = "local"
)
// Package is a resolvable MCP server artifact from the Registry.
type Package struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
Source PackageSource `json:"source"`
Ref string `json:"ref"` // image ref or repo
Versions []string `json:"versions,omitempty"`
ConfigSchema string `json:"config_schema,omitempty"`
Signature string `json:"signature,omitempty"`
}
// Recipe is a declarative match+install+config rule (Smart Recipes module).
type Recipe struct {
Name string `yaml:"name" json:"name"`
Match RecipeMatch `yaml:"match" json:"match"`
Install RecipeInstall `yaml:"install" json:"install"`
Config map[string]string `yaml:"config" json:"config"`
}
// RecipeMatch is the fingerprint predicate for a recipe.
type RecipeMatch struct {
Type string `yaml:"type,omitempty" json:"type,omitempty"`
Ports []int `yaml:"ports,omitempty" json:"ports,omitempty"`
HTTPTitle []string `yaml:"http_title,omitempty" json:"http_title,omitempty"`
}
// RecipeInstall describes what to install when a recipe matches.
type RecipeInstall struct {
DockerImage string `yaml:"docker_image,omitempty" json:"docker_image,omitempty"`
Package string `yaml:"package,omitempty" json:"package,omitempty"`
}
// MCPInstance is a managed, running MCP server — the unit the reconciler drives.
type MCPInstance struct {
ID string `json:"id"`
Namespace string `json:"namespace"`
Package string `json:"package,omitempty"`
Version string `json:"version,omitempty"`
Transport TransportKind `json:"transport"`
State InstanceState `json:"state"`
Health Health `json:"health"`
BoundUUID string `json:"bound_resource_uuid,omitempty"`
ContainerRef string `json:"container_ref,omitempty"`
Config map[string]string `json:"config,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// Tool is an MCP tool exposed by an instance, namespaced at the Gateway as
// "{namespace}.{name}".
type Tool struct {
Namespace string `json:"namespace"`
Name string `json:"name"` // upstream (un-namespaced) name
QualifiedID string `json:"qualified_id"` // "{namespace}.{name}"
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
// InputSchema is the raw JSON schema object as provided by the upstream.
InputSchema map[string]any `json:"input_schema,omitempty"`
}
// AgentProfile binds an agent identity to a set of allowed roles.
type AgentProfile struct {
ID string `json:"id"`
Identity string `json:"identity"`
AllowedRoles []string `json:"allowed_roles,omitempty"`
}
// Role is an RBAC grant of namespaces/tools to principals.
type Role struct {
Name string `json:"name"`
AllowedNamespaces []string `json:"allowed_namespaces,omitempty"`
AllowedTools []string `json:"allowed_tools,omitempty"`
}