From d7f583cb8f8c4d011d4cc675973e6c71a3d0622d Mon Sep 17 00:00:00 2001 From: Sam & Claude Date: Tue, 23 Jun 2026 16:46:18 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20USB=E2=86=92mother=20MCP=20setup=20?= =?UTF-8?q?=E2=80=94=20step-by-step=20guide=20with=20real=20hosts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 5 steps from nothing to working: SSH key + config → enable external MCP → register mother server → install hw-probe → restart daemon. Uses real hosts (osa.smilepowered.org at 100.72.229.63, USB as clawdie-usb) with l33t placeholder keys. ASCII architecture diagram showing persistent SSH child process, JSON-RPC over stdin/stdout, mother-side forced-command wrapper. Includes: end-to-end test, future autospawn flow, and troubleshooting table for all common failure modes. --- docs/SETUP-USB-TO-MOTHER.md | 239 ++++++++++++++++++++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100644 docs/SETUP-USB-TO-MOTHER.md diff --git a/docs/SETUP-USB-TO-MOTHER.md b/docs/SETUP-USB-TO-MOTHER.md new file mode 100644 index 00000000..8942c816 --- /dev/null +++ b/docs/SETUP-USB-TO-MOTHER.md @@ -0,0 +1,239 @@ +# Setup: USB → Mother MCP Connection + +2026-06-23 | Step-by-step | Tested on OSA + 0.11 USB + +Connects a booted Clawdie USB to the mother node (OSA) via MCP over SSH. +After setup, `clawdie-hw-probe` runs on the USB, the hardware profile is +sent to mother, and stored in PostgreSQL `mother_hive.usb_nodes`. + +## Hosts used in this guide + +| Host | IP (Tailscale) | User for MCP | Role | +|---|---|---|---| +| `osa.smilepowered.org` | `100.72.229.63` | `colibri` | Mother — runs PostgreSQL, external MCP servers | +| `clawdie-usb` (USB) | `100.66.193.11` | `clawdie` | Operator workstation — sends hw-probe to mother | + +## How it works + +``` +┌─ USB ────────────────────────────────────────────────────────┐ +│ │ +│ colibri-daemon │ +│ │ │ +│ │ external-mcp.json: │ +│ │ "mother": { │ +│ │ "command": "ssh", │ +│ │ "args": ["-i", "~/.ssh/m0th3r-mcp", │ +│ │ "c0l1br1@100.72.229.63", │ +│ │ "colibri-mcp"] │ +│ │ } │ +│ │ │ +│ │ spawns persistent SSH child process │ +│ │ JSON-RPC flows over stdin/stdout ──────────────────────┐ │ +│ │ │ │ +│ │ clawdie-hw-probe → JSON → │ │ +│ │ colibri_external_mcp_call_tool( │ │ +│ │ server="m0th3r", tool="n0d3_r3g1st3r", ...) ──────┤ │ +│ │ │ │ +└────┼─────────────────────────────────────────────────────────┘ │ + │ │ + │ Tailscale (WireGuard encrypted) │ + │ │ +┌────┼─────────────────────────────────────────────────────────┐ │ +│ ▼ Mother (OSA) │ │ +│ │ │ +│ /var/db/colibri/.ssh/authorized_keys: │ │ +│ command="/usr/local/bin/colibri-mcp-ssh",restrict,... ◄────┘ │ +│ │ +│ colibri-mcp-ssh → strips forced-command wrapper │ +│ → passes "tools" subcommand to colibri-mcp │ +│ │ +│ PostgreSQL mother_hive.usb_nodes ← hw-probe JSON stored │ +│ │ +└───────────────────────────────────────────────────────────────┘ +``` + +## Step 1: SSH key + config (on USB) + +Copy the `mother-mcp` private key to the USB. The public key is already +authorized on mother. For production: the seed partition places this +automatically. For testing: scp from OSA or copy manually. + +```bash +# === ON USB, as clawdie === + +mkdir -p ~/.ssh +chmod 700 ~/.ssh + +# Create the private key — replace with real key content +# This key is authorized on mother as: +# command="/usr/local/bin/colibri-mcp-ssh",restrict,no-pty,... +cat > ~/.ssh/m0th3r-mcp << 'KEY' +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACB3aHk0cmUteTB1LXIzYWRpbmctdGgxcwAAAAh3aHk0cmUteTB1LXIzYWRp +bmctdGgxcwAAAAtzc2gtZWQyNTUxOQAAACB3aHk0cmUteTB1LXIzYWRpbmctdGgxcwAAAA +-----END OPENSSH PRIVATE KEY----- +KEY +chmod 600 ~/.ssh/m0th3r-mcp + +# SSH config — use Tailscale IP so it works without DNS +cat > ~/.ssh/config << 'SSH' +Host m0th3r + HostName 100.72.229.63 + User c0l1br1 + IdentityFile ~/.ssh/m0th3r-mcp + IdentitiesOnly yes + StrictHostKeyChecking accept-new +SSH +chmod 600 ~/.ssh/config + +# Test the connection: +ssh m0th3r 'tools' 2>&1 | head -5 +# Expected output: +# colibri_status Get Colibri daemon status... +# colibri_snapshot Get Glasspane snapshot... +# ... +``` + +## Step 2: Enable external MCP calls (on USB) + +```bash +# === ON USB, as clawdie === + +# Add to provider.env +echo 'COLIBRI_MCP_EXTERNAL_CALL=1' | \ + sudo tee -a /usr/local/etc/colibri/provider.env + +# Verify: +grep EXTERNAL_CALL /usr/local/etc/colibri/provider.env +# → COLIBRI_MCP_EXTERNAL_CALL=1 +``` + +## Step 3: Register mother as external MCP server (on USB) + +```bash +# === ON USB, as clawdie === + +sudo tee /usr/local/etc/colibri/external-mcp.json << 'JSON' +{ + "servers": { + "m0th3r": { + "command": "ssh", + "args": [ + "-i", "/home/clawdie/.ssh/m0th3r-mcp", + "-o", "StrictHostKeyChecking=accept-new", + "c0l1br1@100.72.229.63", + "colibri-mcp" + ], + "env": {} + } + } +} +JSON + +# Verify JSON syntax: +python3.11 -m json.tool /usr/local/etc/colibri/external-mcp.json > /dev/null \ + && echo "OK" || echo "INVALID JSON" +``` + +**What happens at daemon startup**: the daemon reads `external-mcp.json`, +spawns `ssh c0l1br1@100.72.229.63 colibri-mcp` as a persistent child process, +and pipes JSON-RPC over stdin/stdout. The mother-side `colibri-mcp-ssh` +wrapper (in `authorized_keys` via `command=`) strips the SSH forced-command +layer and passes subcommands directly to `colibri-mcp`. One SSH connection +per daemon lifetime — no reconnect overhead. + +## Step 4: Install clawdie-hw-probe (on USB) + +```bash +# === ON USB, as clawdie === + +# From the 0.12 feature branch (already committed): +# Option A — from local repo (if unshallowed): +cd /home/clawdie/ai/clawdie-iso +git fetch origin feature/0.12.0 +git show origin/feature/0.12.0:live/operator-session/clawdie-hw-probe \ + | sudo tee /usr/local/bin/clawdie-hw-probe > /dev/null +sudo chmod 755 /usr/local/bin/clawdie-hw-probe + +# Option B — scp from OSA: +# scp clawdie@100.72.229.63:/home/clawdie/ai/clawdie-iso/live/operator-session/clawdie-hw-probe \ +# /tmp/ && sudo install -m 755 /tmp/clawdie-hw-probe /usr/local/bin/ + +# Test: +sudo clawdie-hw-probe 2>/dev/null | python3.11 -m json.tool | head -10 +# Expected: JSON with hostname, ram_gb, cpu_model, disks, network_interfaces... +``` + +## Step 5: Restart daemon + verify (on USB) + +```bash +# === ON USB, as clawdie === + +# Restart the daemon so it picks up external-mcp.json and the env var +sudo service colibri_daemon restart +# Expected: Starting colibri_daemon. +# colibri-daemon socket ready after 1s + +# Verify external MCP tools are visible +colibri-mcp tools 2>&1 | grep external +# Expected: +# colibri_external_mcp_servers List configured external MCP servers... +# colibri_external_mcp_list_tools List tools exposed by... +# colibri_external_mcp_call_tool Call a tool on an external MCP server... + +# List mother's registered tools +colibri-mcp --external-config /usr/local/etc/colibri/external-mcp.json \ + --external-call tools 2>&1 | head -10 +# Expected: tools registered on mother (colibri_status, geodesic_dome, etc.) +``` + +## End-to-end test + +```bash +# === ON USB === + +# 1. Run hw-probe, capture JSON +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();"' + +# 4. Verify on mother +ssh m0th3r 'sudo -u postgres psql -d mother_hive \ + -c "SELECT hostname, status, capabilities FROM usb_nodes;"' +# Expected: both osa.smilepowered.org and clawdie-usb listed +``` + +## What happens after (future — 0.12+) + +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: ...})` +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 +7. **zot logs**: "node registered — 12GB RAM, 6 cores, no GPU, geodesic_dome_mcp=true" + +## Troubleshooting + +| Symptom | Likely cause | Fix | +|---|---|---| +| `ssh m0th3r` hangs | Tailscale not up | `sudo tailscale up` on USB | +| `Permission denied (publickey)` | Key not in authorized_keys on mother | Verify: `cat /var/db/c0l1br1/.ssh/authorized_keys` on OSA | +| `Permission denied (publickey)` | Key permissions wrong on USB | `chmod 600 ~/.ssh/m0th3r-mcp` | +| `daemon: open: Permission denied` | Log file ownership wrong | `chown clawdie: /var/log/colibri/daemon.log` | +| Daemon starts but no external tools | `COLIBRI_MCP_EXTERNAL_CALL` not set | Check provider.env, restart daemon | +| Daemon starts, external tools visible, but calls fail | SSH key path wrong in external-mcp.json | Use absolute path: `/home/clawdie/.ssh/m0th3r-mcp` | +| `error: unrecognized subcommand` | SSH wrapper getting "colibri-mcp tools" instead of "tools" | Use single-word invocation: `ssh m0th3r 'tools'` not `ssh m0th3r 'colibri-mcp tools'` |