fix(smolmachines): run claude mcp add as node so config lands in node's home
provision_supervise dispatched `claude mcp add --scope user` through `smolvm machine_exec`, which runs as root by default. The MCP entry got written to root's ~/.claude.json — but the agent's claude reads /home/node/.claude.json, so `/mcp` showed "No MCP servers configured" inside the bottle. Wrap the exec in `runuser -u node -- env HOME=/home/node ...` so the config writes to the right home. Same pattern as the interactive exec_claude / Bottle.exec wrappers — `smolvm machine_exec` is always root, so any command that touches user state has to switch UID + set HOME explicitly. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -38,9 +38,16 @@ def provision_supervise(plan: SmolmachinesBottlePlan, target: str) -> None:
|
|||||||
return
|
return
|
||||||
url = plan.agent_supervise_url
|
url = plan.agent_supervise_url
|
||||||
info(f"registering supervise MCP server in agent claude config → {url}")
|
info(f"registering supervise MCP server in agent claude config → {url}")
|
||||||
|
# `claude mcp add --scope user` writes to ~/.claude.json. The
|
||||||
|
# agent is the `node` user; smolvm machine_exec runs as root
|
||||||
|
# by default, so we have to switch user explicitly and set
|
||||||
|
# HOME so the config lands in /home/node/.claude.json (where
|
||||||
|
# the agent's claude actually reads it from).
|
||||||
r = _smolvm.machine_exec(
|
r = _smolvm.machine_exec(
|
||||||
target,
|
target,
|
||||||
[
|
[
|
||||||
|
"runuser", "-u", "node", "--",
|
||||||
|
"env", "HOME=/home/node",
|
||||||
"claude", "mcp", "add",
|
"claude", "mcp", "add",
|
||||||
"--scope", "user",
|
"--scope", "user",
|
||||||
"--transport", "http",
|
"--transport", "http",
|
||||||
|
|||||||
@@ -487,11 +487,15 @@ class TestProvisionSupervise(unittest.TestCase):
|
|||||||
_supervise.provision_supervise(plan, "claude-bottle-demo-abc12")
|
_supervise.provision_supervise(plan, "claude-bottle-demo-abc12")
|
||||||
ex.assert_called_once()
|
ex.assert_called_once()
|
||||||
argv = ex.call_args.args[1]
|
argv = ex.call_args.args[1]
|
||||||
# claude mcp add --scope user --transport http supervise <url>
|
# `claude mcp add --scope user` writes to ~/.claude.json,
|
||||||
# — URL is the agent-side endpoint (host loopback +
|
# and the agent is the `node` user — switch UID + set
|
||||||
# discovered port), not the docker bridge IP.
|
# HOME so the config lands in /home/node/.claude.json,
|
||||||
|
# not root's. URL is the agent-side endpoint (host
|
||||||
|
# loopback + discovered port), not the docker bridge IP.
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
[
|
[
|
||||||
|
"runuser", "-u", "node", "--",
|
||||||
|
"env", "HOME=/home/node",
|
||||||
"claude", "mcp", "add",
|
"claude", "mcp", "add",
|
||||||
"--scope", "user",
|
"--scope", "user",
|
||||||
"--transport", "http",
|
"--transport", "http",
|
||||||
|
|||||||
Reference in New Issue
Block a user