# 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'` |