The module is one Go binary the runtime launches: the renderer (its instruction file held byte for byte to the TypeScript one it replaces), the credentials and identity files, the licence flow of ADR 0206 and the MCP servers in state. Keeps the TypeScript module's key files, so a node moving to it keeps its key. The npm package, its tests and its build go. Both binaries were run together under the real runtime on a test bus with postgres and a stub vendor: a login was adopted by one exchange, the node bound and handed an access token, its file left with no refresh token, and no token in either state.
122 lines
4.4 KiB
Go
122 lines
4.4 KiB
Go
package main
|
|
|
|
// What the module writes into the agent's machine-wide managed directory (novox/hq design 36 §1–§4).
|
|
// Pure: composed from the facts the mesh rendered, the settings the operator set and the licence the node
|
|
// holds, so what lands under /etc is tested without a machine.
|
|
//
|
|
// Three files, owned whole by this module:
|
|
//
|
|
// managed-mcp.json the tool servers every session loads: the mesh's console as `mesh`, and the
|
|
// servers the operator declared or registered through this module. Exclusive by
|
|
// the vendor's rule — a server not listed here does not load (operator's choice,
|
|
// 2026-10-03).
|
|
// managed-settings.json the mesh's keys only: the repositories' attribution convention, the claude.ai
|
|
// connectors kept beside the managed servers, and — for an API-key licence only —
|
|
// the key-helper. A person's preferences are theirs.
|
|
// CLAUDE.md how a session on this mesh works, who this node is, the conventions.
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
// ManagedDir is the agent's machine-wide managed directory.
|
|
const ManagedDir = "/etc/claude-code"
|
|
|
|
const meshEntry = "mesh"
|
|
|
|
// Facts are what the mesh rendered for this node.
|
|
type Facts struct {
|
|
Node string `json:"node"`
|
|
Console string `json:"console"`
|
|
}
|
|
|
|
// Settings are the operator's, for the mesh or this node.
|
|
type Settings struct {
|
|
Role string `json:"role"`
|
|
MCPServers map[string]map[string]any `json:"mcp_servers"`
|
|
}
|
|
|
|
// Binding is the licence this node holds, as it was last applied.
|
|
type Binding struct {
|
|
Licence string `json:"licence"`
|
|
Kind string `json:"kind"` // subscription | api-key
|
|
Generation int64 `json:"generation,omitempty"`
|
|
}
|
|
|
|
// Servers are tool server entries by name, in the vendor's `.mcp.json` shape.
|
|
type Servers map[string]map[string]any
|
|
|
|
var serverName = regexp.MustCompile(`^[A-Za-z0-9_-]+$`)
|
|
|
|
// EntryProblem says why the vendor's managed file would not take an entry, or "" when it would: a name of
|
|
// letters, digits, `-` and `_`, and an http/sse server with a url or a stdio server with a command.
|
|
func EntryProblem(name string, entry map[string]any) string {
|
|
if !serverName.MatchString(name) {
|
|
return fmt.Sprintf("%q is not a name the agent takes: letters, digits, - and _", name)
|
|
}
|
|
if name == meshEntry {
|
|
return fmt.Sprintf("%q is the mesh's own entry", meshEntry)
|
|
}
|
|
kind, _ := entry["type"].(string)
|
|
if kind == "" {
|
|
kind = "stdio"
|
|
}
|
|
switch kind {
|
|
case "http", "sse", "streamable-http":
|
|
if u, _ := entry["url"].(string); u != "" {
|
|
return ""
|
|
}
|
|
return fmt.Sprintf("an %s server needs a url", kind)
|
|
case "stdio":
|
|
if c, _ := entry["command"].(string); c != "" {
|
|
return ""
|
|
}
|
|
return "a stdio server needs a command"
|
|
}
|
|
return fmt.Sprintf("%q is not a server type the agent knows (http, sse, stdio)", kind)
|
|
}
|
|
|
|
// jsonFile is a value as the managed files are written: two-space indent, a trailing newline, nothing
|
|
// escaped that need not be.
|
|
func jsonFile(v any) string {
|
|
var b bytes.Buffer
|
|
enc := json.NewEncoder(&b)
|
|
enc.SetEscapeHTML(false)
|
|
enc.SetIndent("", " ")
|
|
_ = enc.Encode(v)
|
|
return b.String()
|
|
}
|
|
|
|
// Render composes the three files. registered — what was registered through this module and applies
|
|
// here — is laid over the servers the operator set in its settings.
|
|
func Render(facts Facts, settings Settings, binding *Binding, helperPath string, registered Servers) map[string]string {
|
|
servers := map[string]any{}
|
|
for _, layer := range []map[string]map[string]any{settings.MCPServers, registered} {
|
|
for name, entry := range layer {
|
|
if EntryProblem(name, entry) != "" {
|
|
continue // the mesh's own entry, or one the agent would refuse
|
|
}
|
|
servers[name] = entry
|
|
}
|
|
}
|
|
servers[meshEntry] = map[string]any{"type": "http", "url": facts.Console}
|
|
|
|
managed := map[string]any{"attribution": map[string]any{"commit": "", "pr": ""}, "allowAllClaudeAiMcps": true}
|
|
if binding != nil && binding.Kind == "api-key" {
|
|
managed["apiKeyHelper"] = helperPath
|
|
}
|
|
role := strings.TrimSpace(settings.Role)
|
|
if role == "" {
|
|
role = "not stated — set it in this module's settings for the node"
|
|
}
|
|
return map[string]string{
|
|
"managed-mcp.json": jsonFile(map[string]any{"mcpServers": servers}),
|
|
"managed-settings.json": jsonFile(managed),
|
|
"CLAUDE.md": instructionsText(facts.Node, role),
|
|
}
|
|
}
|