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:
153
internal/domain/domain.go
Normal file
153
internal/domain/domain.go
Normal 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"`
|
||||
}
|
||||
Reference in New Issue
Block a user