claude-code: start only the tool servers set at the controller's terminal
mesh/merge-gate pass: builds claude-code → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: THE CHANGE ALTERS ITS OWN CHECK (merge-check.sh, merge-check-typescript.sh): main's version judged it; the change's judges the pull r…
mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate pass: builds claude-code → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: THE CHANGE ALTERS ITS OWN CHECK (merge-check.sh, merge-check-typescript.sh): main's version judged it; the change's judges the pull r…
mesh/delivery superseded: a newer head of the same pull request
A server left in the servers state could still be put there by a holder of the runner's credential; render mcp_servers alone and keep the state for listing and unregistering (hq issue 340). Waits for the laptop's server to be set at the terminal first.
This commit is contained in:
@@ -359,8 +359,9 @@ func (b *bus) join(p Paths, w map[string]string) *ServerView {
|
||||
|
||||
func noOthers() ([]string, error) { return nil, nil }
|
||||
|
||||
// A server registered before stays (novox/hq issue 340): every node takes what the state holds for it, a node
|
||||
// that joins later reads the current set, and an unregistration reaches every node.
|
||||
// A server registered before is still read from the state, to be listed and unregistered (novox/hq issue 340):
|
||||
// every node takes what the state holds for it, a node that joins later reads the current set, and an
|
||||
// unregistration reaches every node. None of it is rendered.
|
||||
func TestEveryNodeRegistrationReachesTheOthersAndALateNodeReadsIt(t *testing.T) {
|
||||
a, wa := node(t, "laptop")
|
||||
s, ws := node(t, "server")
|
||||
@@ -368,7 +369,7 @@ func TestEveryNodeRegistrationReachesTheOthersAndALateNodeReadsIt(t *testing.T)
|
||||
va := b.join(a, wa)
|
||||
b.join(s, ws)
|
||||
b.existing("all.docs", map[string]any{"type": "stdio", "command": "docs-mcp"})
|
||||
if Registered(s)["docs"] == nil || !strings.Contains(ws["managed-mcp.json"], "docs-mcp") {
|
||||
if Registered(s)["docs"] == nil || strings.Contains(ws["managed-mcp.json"], "docs-mcp") {
|
||||
t.Fatalf("the other node did not take it: %v", Registered(s))
|
||||
}
|
||||
late, wl := node(t, "desktop")
|
||||
|
||||
@@ -323,7 +323,7 @@ func tools(p Paths, servers ServerState, view *ServerView, config ConfigState, c
|
||||
return AddAPIKey(p, strings.TrimSpace(name), file, useHere, ask)
|
||||
}},
|
||||
{Name: "claude_code_mcp_list",
|
||||
Description: "The MCP servers registered through this module: those that apply on this node (beside the console, `mesh`, and those set in the module's settings), and every registration on the mesh, by key — `all.<server>` for every node, `<node>.<server>` for one.",
|
||||
Description: "The MCP servers registered through this module before that was refused (novox/hq issue 340): those whose key names this node, and every registration on the mesh, by key — `all.<server>` for every node, `<node>.<server>` for one. None of them starts in a session any more: tool servers are set at the controller's terminal, in this module's mcp_servers setting.",
|
||||
Run: func(map[string]any) (any, error) {
|
||||
keys, err := servers.Keys()
|
||||
return map[string]any{"here": Registered(p), "everywhere": keys}, err
|
||||
|
||||
@@ -172,7 +172,9 @@ func RenderNow(p Paths, write WriteManaged) ([]string, error) {
|
||||
if mesh != nil {
|
||||
mesh.Refused = RefusedCounts(GuardDataOf(p, mesh).Log)
|
||||
}
|
||||
files := RenderWithMesh(facts, settings, binding, p.helper(), Registered(p), config, mesh)
|
||||
// Tool servers come from the operator's `mcp_servers` setting alone, set at the controller's terminal (novox/hq
|
||||
// issue 340): one left in the `servers` state is listed and may be unregistered, and never starts in a session.
|
||||
files := RenderWithMesh(facts, settings, binding, p.helper(), nil, config, mesh)
|
||||
tree, _ := json.Marshal(MarketplaceWith(config, guardFiles(p, mesh)))
|
||||
files[MarketplaceDir+"/"] = string(tree)
|
||||
names := make([]string, 0, len(files))
|
||||
|
||||
@@ -35,10 +35,10 @@ func Registrations(p Paths, servers ServerState, config ConfigState) (map[string
|
||||
}
|
||||
row := map[string]any{"entry": entry, "by": "not recorded: a tool server's registration kept only its entry"}
|
||||
if _, name, ok := strings.Cut(key, "."); ok {
|
||||
row["rendered"] = "no: a tool server is set at the controller's terminal only, in this module's mcp_servers " +
|
||||
"setting (novox/hq issue 340); unregister this one once it is there, if it is still wanted"
|
||||
if problem := EntryProblem(name, entry); problem != "" {
|
||||
row["rendered"] = "no: " + problem
|
||||
} else {
|
||||
row["rendered"] = "yes, on the nodes its key names"
|
||||
}
|
||||
}
|
||||
registered[key] = row
|
||||
|
||||
@@ -199,8 +199,8 @@ func TestWhatWasRegisteredBeforeIsListedAndOnlyTextAndServersStillRender(t *test
|
||||
if strings.Contains(hooks, runs) || skill || strings.Contains(w["managed-settings.json"], `"Bash"`) {
|
||||
t.Fatalf("%s: a registered hook, skill or setting was rendered:\n%s\n%s", when, hooks, w["managed-settings.json"])
|
||||
}
|
||||
if !strings.Contains(w["managed-mcp.json"], "docs-mcp") || !strings.Contains(w["CLAUDE.md"], "Commit in the imperative.") {
|
||||
t.Fatalf("%s: what was registered before and is still taken was not rendered:\n%s", when, w["managed-mcp.json"])
|
||||
if strings.Contains(w["managed-mcp.json"], "docs-mcp") || !strings.Contains(w["CLAUDE.md"], "Commit in the imperative.") {
|
||||
t.Fatalf("%s: a tool server from the bus was rendered, or the instructions were not:\n%s", when, w["managed-mcp.json"])
|
||||
}
|
||||
}
|
||||
rendered("from the module's own file")
|
||||
@@ -223,7 +223,7 @@ func TestWhatWasRegisteredBeforeIsListedAndOnlyTextAndServersStillRender(t *test
|
||||
if ins, _ := cfg["mesh.instructions.conventions"].(map[string]any); !strings.HasPrefix(ins["rendered"].(string), "yes") {
|
||||
t.Errorf("the instructions: %v", ins)
|
||||
}
|
||||
if srv, _ := srvs["laptop.docs"].(map[string]any); srv == nil || !strings.HasPrefix(srv["rendered"].(string), "yes") {
|
||||
if srv, _ := srvs["laptop.docs"].(map[string]any); srv == nil || !strings.HasPrefix(srv["rendered"].(string), "no:") {
|
||||
t.Errorf("the tool server: %v", srvs)
|
||||
}
|
||||
|
||||
@@ -487,3 +487,22 @@ func TestAHomeItemOnTheBusIsNotPlacedInTheOperatorsHome(t *testing.T) {
|
||||
t.Errorf("the render did not say it left the item out: %v", out)
|
||||
}
|
||||
}
|
||||
|
||||
// A tool server is the operator's, set at the controller's terminal (novox/hq issue 340): one set there is
|
||||
// rendered; one left on the bus is not, even for this node.
|
||||
func TestToolServersComeFromTheTerminalAlone(t *testing.T) {
|
||||
p, w := node(t, "laptop")
|
||||
_ = os.WriteFile(p.Settings, []byte(`{"role":"","mcp_servers":{"set-here":{"type":"stdio","command":"set-at-terminal"}}}`), 0o600)
|
||||
b := &bus{kept: map[string]map[string]any{}}
|
||||
b.join(p, w)
|
||||
b.existing("laptop.left", map[string]any{"type": "stdio", "command": "left-on-the-bus"})
|
||||
b.existing("all.everywhere", map[string]any{"type": "http", "url": "https://x.example/mcp"})
|
||||
if _, err := RenderNow(p, writer(w)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mcp := w["managed-mcp.json"]
|
||||
if !strings.Contains(mcp, "set-at-terminal") || strings.Contains(mcp, "left-on-the-bus") || strings.Contains(mcp, "x.example") ||
|
||||
!strings.Contains(mcp, `"mesh"`) {
|
||||
t.Fatalf("managed-mcp.json:\n%s", mcp)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user