From d001b46b34c34966d7318298c9d27a5d444d5f24 Mon Sep 17 00:00:00 2001 From: 123kupola Date: Wed, 24 Jun 2026 09:07:48 +0200 Subject: [PATCH 1/2] feat(mother): add node-register MCP tool for USB hw-probe registration New packaging/mother/node-register-mcp accepts JSON-RPC tools/call, inserts hw_profile into mother_hive.usb_nodes, and returns the row with auto-derived capabilities (derive_capabilities trigger fires). Requires one-time PostgreSQL setup on mother: CREATE ROLE colibri WITH LOGIN; GRANT CONNECT ON DATABASE mother_hive TO colibri; GRANT INSERT, UPDATE ON usb_nodes TO colibri; GRANT USAGE ON SEQUENCE usb_nodes_id_seq TO colibri; Also updates docs to reflect 0.12 daemon behavior: hw-probe is collected by the daemon (not the agent) and passed via CLAWDIE_HW_PROFILE env var. COLIBRI_AUTOSPAWN_ARGS default is binary-dependent (zot->rpc, others->--mode json). --- docs/SECURITY-BASELINE.md | 1 + docs/SETUP-USB-TO-MOTHER.md | 16 ++++---- docs/USB-MOTHER-MCP-CONNECTION.md | 26 ++++++------ packaging/mother/node-register-mcp | 66 ++++++++++++++++++++++++++++++ 4 files changed, 88 insertions(+), 21 deletions(-) create mode 100755 packaging/mother/node-register-mcp diff --git a/docs/SECURITY-BASELINE.md b/docs/SECURITY-BASELINE.md index cb5f666..c8c8c6b 100644 --- a/docs/SECURITY-BASELINE.md +++ b/docs/SECURITY-BASELINE.md @@ -64,6 +64,7 @@ Clawdie USB. Derived from the OSA security audit on 2026-06-23. | COLIBRI_MCP_EXTERNAL_CALL | `grep EXTERNAL_CALL /usr/local/etc/colibri/provider.env` | `COLIBRI_MCP_EXTERNAL_CALL=1` | | geodesic-dome-mcp installed | `ls /usr/local/bin/geodesic-dome-mcp` | exists, executable | | build-colibri.sh installed | `ls /usr/local/bin/build-colibri.sh` | exists, executable | +| node-register-mcp installed | `ls /usr/local/bin/node-register-mcp` | exists, executable | | colibri-mcp-ssh wrapper | `ls /usr/local/bin/colibri-mcp-ssh` | exists, executable | ## OSA-specific exceptions (production) diff --git a/docs/SETUP-USB-TO-MOTHER.md b/docs/SETUP-USB-TO-MOTHER.md index 99022b1..036a04c 100644 --- a/docs/SETUP-USB-TO-MOTHER.md +++ b/docs/SETUP-USB-TO-MOTHER.md @@ -200,12 +200,11 @@ HW_JSON=$(sudo clawdie-hw-probe 2>/dev/null) # 2. View what would be sent to mother echo "$HW_JSON" | python3.11 -m json.tool | head -15 -# 3. Send to mother via MCP (once node_register tool exists on mother) -# For now: manual insert via SSH to mother -echo "$HW_JSON" | ssh m0th3r 'cat - | sudo -u postgres psql -d mother_hive \ - -c "INSERT INTO usb_nodes (hostname, hw_profile, status) \ - VALUES ('\''$(hostname)'\'', '\''$(cat)'\''::jsonb, '\''online'\'') \ - ON CONFLICT (hostname) DO UPDATE SET hw_profile = EXCLUDED.hw_profile, last_seen = now();"' +# 3. Send to mother via MCP (node_register tool: packaging/mother/node-register-mcp) +# The 0.12 daemon collects hw-probe at autospawn time and passes it to agents +# via CLAWDIE_HW_PROFILE env var. For manual testing, pipe JSON-RPC directly: +printf '{"jsonrpc":"2.0","method":"tools/call","id":1,"params":{"name":"node_register","arguments":{"hostname":"%s","hw_profile":%s}}}\n' \ + "$(hostname)" "$HW_JSON" | ssh m0th3r 'cat - | /usr/local/bin/node-register-mcp' # 4. Verify on mother ssh m0th3r 'sudo -u postgres psql -d mother_hive \ @@ -219,8 +218,9 @@ Once the daemon is restarted with `COLIBRI_AUTOSPAWN=YES` and `COLIBRI_AUTOSPAWN_BINARY=zot`: 1. **zot autospawns** — DeepSeek API key from seed partition -2. **zot's first action**: runs `clawdie-hw-probe`, captures JSON -3. **zot calls mother**: `colibri_external_mcp_call_tool(server="m0th3r", tool="n0d3_r3g1st3r", arguments={hw_profile: ...})` +2. **Daemon collects hw-probe** — runs `clawdie-hw-probe` at spawn time, passes result + to zot as `CLAWDIE_HW_PROFILE` env var (zot doesn't run hw-probe itself in 0.12) +3. **zot reads env var and calls mother**: `colibri_external_mcp_call_tool(server="m0th3r", tool="node_register", arguments={hostname:..., hw_profile:...})` 4. **Mother stores** the hardware profile in PostgreSQL 5. **Capability trigger fires** — derives `has_gpu`, `ram_gb`, `cpu_cores`, `geodesic_dome_mcp` etc. 6. **Mother returns capabilities** — zot now knows what this node can do diff --git a/docs/USB-MOTHER-MCP-CONNECTION.md b/docs/USB-MOTHER-MCP-CONNECTION.md index 0eb1f9f..32dfcee 100644 --- a/docs/USB-MOTHER-MCP-CONNECTION.md +++ b/docs/USB-MOTHER-MCP-CONNECTION.md @@ -149,11 +149,12 @@ derived capabilities (`has_gpu`, `ram_gb`, `cpu_cores`, etc.). The operator places `mother-mcp` and `mother-mcp.pub` in the seed dir, reboots, and the key is automatically installed. -2. **Autospawn integration**: After autospawn (zot starts), the agent's first - action should be to run `clawdie-hw-probe` and call +2. **Autospawn integration**: In 0.12, the daemon collects `clawdie-hw-probe` + at autospawn time and passes it to the agent as `CLAWDIE_HW_PROFILE` env var. + The agent's first action should read that env var and call `colibri_external_mcp_call_tool(server="mother", tool="node_register")`. - This requires a `node_register` MCP tool on mother (not yet implemented — - currently only `geodesic-dome` and `mother-build` are registered). + The `node_register` MCP tool is now implemented at + `packaging/mother/node-register-mcp`. 3. **Tailscale dependency**: The USB needs Tailscale to be up before MCP works. This is already the case — Tailscale auth is part of the bootstrap flow. @@ -161,12 +162,11 @@ derived capabilities (`has_gpu`, `ram_gb`, `cpu_cores`, etc.). ## Implementation priority -| Step | Effort | Blocked by | -| ---------------------------------------- | ----------------- | -------------- | -| Step 1: SSH key + config | Manual (one-time) | Nothing | -| Step 2: Enable external MCP | 1 line | Nothing | -| Step 3: external-mcp.json | 10 lines JSON | Nothing | -| Step 4: Install hw-probe | Copy file | 0.12 branch | -| Step 5: Restart + verify | 2 commands | Steps 1-4 | -| Step 6: Seed partition auto-key | Code change | 0.12 ISO build | -| Step 7: node_register MCP tool on mother | New MCP tool | Step 6 | +| Step | Effort | Blocked by | +| ------------------------------- | ----------------- | -------------- | --- | --------------------------------------- | ----------------- | --------- | +| Step 1: SSH key + config | Manual (one-time) | Nothing | +| Step 2: Enable external MCP | 1 line | Nothing | +| Step 3: external-mcp.json | 10 lines JSON | Nothing | +| Step 4: Install hw-probe | Copy file | 0.12 branch | +| Step 5: Restart + verify | 2 commands | Steps 1-4 | +| Step 6: Seed partition auto-key | Code change | 0.12 ISO build | \n | Step 7: Install node_register on mother | Copy + psql grant | Steps 1-5 | diff --git a/packaging/mother/node-register-mcp b/packaging/mother/node-register-mcp new file mode 100755 index 0000000..1903244 --- /dev/null +++ b/packaging/mother/node-register-mcp @@ -0,0 +1,66 @@ +#!/bin/sh +# node-register MCP tool — register a USB node's hardware profile in PostgreSQL. +# +# Accepts a JSON-RPC tools/call request on stdin, inserts the hw_profile into +# mother_hive.usb_nodes, and returns the result to stdout. The +# derive_capabilities() trigger auto-computes has_gpu, gpu_vendor, +# can_run_local_llm, has_wifi, etc. on INSERT. +# +# Expected input: +# {"jsonrpc":"2.0","method":"tools/call","id":1,"params":{"name":"node_register","arguments":{"hostname":"clawdie-usb","hw_profile":{...}}}} +# +# Output on success: +# {"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"{\"registered\":true,\"hostname\":\"clawdie-usb\",\"capabilities\":{...}}"}]}} +# +# PostgreSQL access: peer auth for the 'colibri' OS user. The operator must run +# once on mother (as postgres): +# CREATE ROLE colibri WITH LOGIN; +# GRANT CONNECT ON DATABASE mother_hive TO colibri; +# GRANT INSERT, UPDATE ON usb_nodes TO colibri; +# GRANT USAGE ON SEQUENCE usb_nodes_id_seq TO colibri; +set -eu + +DB="mother_hive" + +# Read JSON-RPC from stdin +INPUT=$(cat) +ID=$(echo "$INPUT" | jq -r '.id // "1"') +HOSTNAME=$(echo "$INPUT" | jq -r '.params.arguments.hostname // ""') +HW_PROFILE=$(echo "$INPUT" | jq -c '.params.arguments.hw_profile // {}') + +if [ -z "$HOSTNAME" ]; then + printf '{"jsonrpc":"2.0","id":%s,"error":{"code":-1,"message":"missing required argument: hostname"}}\n' "$ID" + exit 1 +fi + +if [ "$HW_PROFILE" = "{}" ]; then + printf '{"jsonrpc":"2.0","id":%s,"error":{"code":-1,"message":"missing required argument: hw_profile"}}\n' "$ID" + exit 1 +fi + +# Escape for psql string literal: double any single quotes, wrap in E'...' +HW_ESCAPED=$(printf '%s' "$HW_PROFILE" | sed "s/'/''/g") +HOST_ESCAPED=$(printf '%s' "$HOSTNAME" | sed "s/'/''/g") + +# Insert/update. The derive_capabilities() trigger fires on INSERT and +# UPDATE OF hw_profile, so capabilities are always fresh. +SQL="INSERT INTO usb_nodes (hostname, hw_profile, status, last_seen) +VALUES (E'${HOST_ESCAPED}', E'${HW_ESCAPED}'::jsonb, 'online', now()) +ON CONFLICT (hostname) DO UPDATE +SET hw_profile = EXCLUDED.hw_profile, + status = 'online', + last_seen = now()" + +RESULT=$(psql -d "$DB" -tAc "BEGIN; $SQL; SELECT json_build_object( + 'registered', true, + 'hostname', hostname, + 'capabilities', capabilities +) FROM usb_nodes WHERE hostname = E'${HOST_ESCAPED}'; COMMIT;" 2>&1) || { + printf '{"jsonrpc":"2.0","id":%s,"error":{"code":-1,"message":"psql failed: %s"}}\n' \ + "$ID" "$(printf '%s' "$RESULT" | sed 's/"/\\"/g' | tr '\n' ' ')" + exit 1 +} + +# RESULT is the JSON object from json_build_object +printf '{"jsonrpc":"2.0","id":%s,"result":{"content":[{"type":"text","text":"%s"}]}}\n' \ + "$ID" "$(printf '%s' "$RESULT" | sed 's/"/\\"/g')" -- 2.45.3 From a03c4a6b543dd207819d6794c3f3da16bb8d039a Mon Sep 17 00:00:00 2001 From: Sam & Claude Date: Wed, 24 Jun 2026 09:18:12 +0200 Subject: [PATCH 2/2] =?UTF-8?q?fix(build):=20rename=20osa-mother-2026=20?= =?UTF-8?q?=E2=86=92=20mother-mcp=20in=20key=20paths?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/build.sh b/build.sh index fc8ae15..c05f0f2 100755 --- a/build.sh +++ b/build.sh @@ -515,8 +515,8 @@ check_release_gate() { # A baked mother SSH private key must never reach a publicly hosted release # image. Fail fast here so a release build aborts in seconds; the image # assembly step also refuses to copy it, as defense in depth. - if [ -f "/home/clawdie/.ssh/osa-mother-2026" ]; then - echo "ERROR: mother SSH key present on build host (/home/clawdie/.ssh/osa-mother-2026) — refuse to bake it into a release image. Remove it, or build with BUILD_CHANNEL=dev." + if [ -f "/home/clawdie/.ssh/mother-mcp" ]; then + echo "ERROR: mother SSH key present on build host (/home/clawdie/.ssh/mother-mcp) — refuse to bake it into a release image. Remove it, or build with BUILD_CHANNEL=dev." _release_errors=$(( _release_errors + 1 )) fi @@ -1736,13 +1736,13 @@ EOF # Pre-stage mother connectivity key if present on the build host. # Lets the live USB node SSH into the mother server (osa) without # manual key exchange. Public key is already in mother authorized_keys. - _mother_key_src="/home/clawdie/.ssh/osa-mother-2026" + _mother_key_src="/home/clawdie/.ssh/mother-mcp" if [ -f "${_mother_key_src}" ]; then [ "${BUILD_CHANNEL}" = "release" ] && { echo "ERROR: refusing to bake mother SSH key into a release image"; exit 1; } mkdir -p "${MOUNT_POINT}/home/clawdie/.ssh" - cp "${_mother_key_src}" "${MOUNT_POINT}/home/clawdie/.ssh/osa-mother-2026" - chmod 0600 "${MOUNT_POINT}/home/clawdie/.ssh/osa-mother-2026" + cp "${_mother_key_src}" "${MOUNT_POINT}/home/clawdie/.ssh/mother-mcp" + chmod 0600 "${MOUNT_POINT}/home/clawdie/.ssh/mother-mcp" echo " Staged mother SSH key for USB→mother connectivity." fi chmod 0755 \ -- 2.45.3