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 keys the operator set in this module's `managed_settings` (the agent's // permissions and auto mode, say), under the mesh's own keys, which always win: // 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, in their own settings. // 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"` // ManagedSettings are keys of the agent's managed settings the operator sets, for the mesh or a node. ManagedSettings map[string]any `json:"managed_settings"` } // 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; config is the rest of what was // registered (ADR 0216): its settings laid over the operator's `managed_settings`, its instruction sections // after the mesh's own text. The plugin itself is Marketplace's, and the home's PlaceHome's. func Render(facts Facts, settings Settings, binding *Binding, helperPath string, registered Servers, config Config) map[string]string { return RenderWithMesh(facts, settings, binding, helperPath, registered, config, nil) } // RenderWithMesh is Render with what the mesh said about its tools (novox/hq ADR 0245): the "instead of" // table, after the mesh's own text and before the sections registered for the agent. func RenderWithMesh(facts Facts, settings Settings, binding *Binding, helperPath string, registered Servers, config Config, mesh *MeshTools) 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} // The operator's `managed_settings` (ADR 0213), then what was registered for the mesh, then for this node. managed := mergeSettings(map[string]any{}, settings.ManagedSettings) managed = mergeSettings(managed, RegisteredSettings(config)) // The mesh's own keys are laid last: a setting never replaces them. managed["attribution"] = map[string]any{"commit": "", "pr": ""} managed["allowAllClaudeAiMcps"] = true delete(managed, "apiKeyHelper") // The plugin's marketplace and the plugin itself, as entries in the operator's own maps, the mesh's // entry winning: the operator may know more marketplaces and enable more plugins. for key, value := range MarketplaceKeys() { entries := map[string]any{} if held, ok := managed[key].(map[string]any); ok { for k, v := range held { entries[k] = v } } for k, v := range value.(map[string]any) { entries[k] = v } managed[key] = entries } 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) + InsteadOf(mesh) + InstructionSections(config), } }