// Package provide is the runtime's half of node-tools being a provider of `mcp-endpoint`: it runs the mesh SDK's // provider helper, so a consumer handed to the provisioner (claude-code's `${bound:mcp-endpoint:port}`) is answered // from an entry node-tools writes in its own bucket, not from the controller's binding file (novox/hq ADR 0349 // clause 12, issue 791). // // The offer carries no credential: the console's authority is the machine's login (ADR 0152, 0175), so the helper // reads nothing from the vault, needs no consumers' file and the entry names none. node-tools is not marked moved: // its manifest says `"answered-by": "helper"` for the provision, and the helper runs whenever the runtime does // (issue 791), as route-proxy's does (issue 792). package provide import ( "context" "encoding/json" "fmt" "time" "git.novox.be/novox/mesh-sdk/go/provider" "github.com/novox/mesh-tools/node-tools/internal/bus" ) const ( // Module is the provider: the runtime's own module. Module = "node-tools" // Provision is what node-tools provides, as its manifest says. Provision = "mcp-endpoint" // ServedPort is the port its manifest serves (`serves.mcp-endpoint.port`): where the console listens. ServedPort = 4270 ) // Offers is what node-tools serves, per provision. func Offers() map[string]provider.Offer { return map[string]provider.Offer{Provision: { Credential: provider.NoCredential, Serves: map[string]any{"port": ServedPort}, }} } // busOf is the helper's bus over the runtime's own state: the provider's bucket is its state `bound`, and the // controller's assignments are the state it reads, `mesh-controller.assignments` (both declared in the manifest). type busOf struct{ conn *bus.Conn } func (b busOf) state(bucket string) (string, error) { switch bucket { case provider.BucketOf(Module): return "bound", nil case provider.AssignmentsBucket: return "mesh-controller.assignments", nil } return "", fmt.Errorf("%s is not a bucket this provider keeps or reads", bucket) } func (b busOf) Watch(ctx context.Context, bucket string, handle func(key string, value []byte)) error { name, err := b.state(bucket) if err != nil { return err } stop, err := b.conn.StateWatch(Module, name, "", func(ch bus.StateChange) error { if ch.Op == "delete" { handle(ch.Key, nil) } else { handle(ch.Key, ch.Value) } return nil }) if err != nil { return err } go func() { <-ctx.Done(); stop() }() return nil } func (b busOf) Put(_ context.Context, bucket, key string, value []byte) error { name, err := b.state(bucket) if err != nil { return err } _, err = b.conn.StatePut(Module, name, key, json.RawMessage(value)) return err } func (b busOf) Delete(_ context.Context, bucket, key string) error { name, err := b.state(bucket) if err != nil { return err } return b.conn.StateDelete(Module, name, key) } // New is the helper answering node-tools's consumers on node. func New(conn *bus.Conn, node string, every time.Duration, logf func(string, ...any)) *provider.Provider { return provider.New(provider.Options{ Node: node, Module: Module, Offers: Offers(), Bus: busOf{conn: conn}, Every: every, Logf: logf, }) } // Run answers the consumers until ctx ends. The state is not reachable until the mesh has issued the runtime its // membership and made the buckets, so a start that cannot watch is tried again, not given up. func Run(ctx context.Context, conn *bus.Conn, node string, every time.Duration, logf func(string, ...any)) { for delay := 2 * time.Second; ctx.Err() == nil; delay = min(delay*2, time.Minute) { attempt, cancel := context.WithCancel(ctx) err := New(conn, node, every, logf).Run(attempt) cancel() if ctx.Err() != nil { return } logf("[mesh-tools] the provider helper stopped: %v; trying again in %s", err, delay) select { case <-ctx.Done(): case <-time.After(delay): } } } // Start runs the helper when this process is the machine's runtime (the node-tools credential, on a machine's // name), with no condition on the environment, and returns what ends it. A runtime on another credential, a module's // own in a test, is not the provider and answers nobody. func Start(conn *bus.Conn, module, node string, every time.Duration, logf func(string, ...any)) (stop func()) { if module != Module || node == "" { return func() {} } ctx, cancel := context.WithCancel(context.Background()) logf("[mesh-tools] answering %s's consumers of %s through the provider helper", Module, Provision) go Run(ctx, conn, node, every, logf) return cancel }