# Setup: USB → Mother MCP Connection

23.jun.2026 | 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.hive_nodes`.

## Hosts used in this guide

| Host                | IP (Tailscale)          | User for MCP | Role                                            |
| ------------------- | ----------------------- | ------------ | ----------------------------------------------- |
| `mother` (OSA)      | `<mother-tailscale-ip>` | `colibri`    | Mother — runs PostgreSQL, external MCP servers  |
| `clawdie-usb` (USB) | `<node-tailscale-ip>`   | `clawdie`    | Operator workstation — sends hw-probe to mother |

## How it works

```
┌─ USB ────────────────────────────────────────────────────────┐
│                                                               │
│  colibri-daemon                                               │
│    │                                                          │
│    │ external-mcp.json (baked):                               │
│    │   "mother": {                                            │
│    │     "command": "ssh",                                    │
│    │     "args": ["-i", "/var/db/colibri/.ssh/mother-mcp",   │
│    │              "mother"]                                   │
│    │   }                                                      │
│    │                                                          │
│    │ spawns persistent SSH child (no remote command)          │
│    │ JSON-RPC flows over stdin/stdout ──────────────────────┐ │
│    │                                                         │ │
│    │ clawdie-hw-probe → JSON →                              │ │
│    │   colibri_external_mcp_call_tool(                       │ │
│    │     server="mother", tool="node_register", ...)  ──────┤ │
│    │                                                         │ │
└────┼─────────────────────────────────────────────────────────┘ │
     │                                                            │
     │  Tailscale (WireGuard encrypted)                           │
     │                                                            │
┌────┼─────────────────────────────────────────────────────────┐ │
│    ▼              Mother (OSA)                                │ │
│                                                               │ │
│  /var/db/colibri/.ssh/authorized_keys:                        │ │
│    command="/usr/local/bin/colibri-mcp-ssh",restrict,... ◄────┘ │
│                                                               │
│  colibri-mcp-ssh → starts colibri-mcp in stdio MCP mode       │
│                                                               │
│  PostgreSQL mother_hive.hive_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/mother-mcp << 'KEY'
-----BEGIN OPENSSH PRIVATE KEY-----
<your-private-key-here>
-----END OPENSSH PRIVATE KEY-----
KEY
chmod 600 ~/.ssh/mother-mcp

# SSH config — use Tailscale IP so it works without DNS.
# The Host alias "mother" must match the external-mcp.json entry
# baked into the ISO. The real IP lives only on the seed, never in the repo.
cat > ~/.ssh/config << 'SSH'
Host mother
    HostName <mother-tailscale-ip>
    User colibri
    IdentityFile ~/.ssh/mother-mcp
    IdentitiesOnly yes
    StrictHostKeyChecking accept-new
SSH
chmod 600 ~/.ssh/config

# Test the connection:
ssh mother 'tools' 2>&1 | head -5
# Expected output:
#   colibri_status        Get Colibri daemon status...
#   colibri_snapshot      Get Glasspane snapshot...
#   ...
```

## Step 2: External MCP calls + registry (baked into ISO)

`COLIBRI_MCP_EXTERNAL_CALL=1` and the mother `external-mcp.json` entry
are pre-configured in the ISO image by `scripts/stage-colibri-iso.sh`.
No manual steps are needed — the daemon picks up both on first boot.

## Step 3: 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 mother:
# scp colibri@mother:/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 4: 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 (node_register tool lives in the colibri repo: 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 mother 'cat - | /usr/local/bin/node-register-mcp'

# 4. Verify on mother
ssh mother 'sudo -u postgres psql -d mother_hive \
    -c "SELECT hostname, status, capabilities FROM hive_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. **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="mother", 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
7. **zot logs**: "node registered — 12GB RAM, 6 cores, no GPU, geodesic_dome_mcp=true"

## Troubleshooting

| Symptom                                               | Likely cause                                | Fix                                                                                             |
| ----------------------------------------------------- | ------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| `ssh mother` hangs                                    | Tailscale not up                            | `sudo tailscale up` on USB                                                                      |
| `Permission denied (publickey)`                       | Key not in authorized_keys on mother        | Verify: `cat /var/db/colibri/.ssh/authorized_keys` on mother                                    |
| `Permission denied (publickey)`                       | Key permissions wrong on USB                | `chmod 600 /var/db/colibri/.ssh/mother-mcp` (daemon user) or `~/.ssh/mother-mcp` (clawdie user) |
| `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     | Baked path: `/var/db/colibri/.ssh/mother-mcp`                                                   |
| `error: unrecognized subcommand`                      | SSH wrapper getting non-allowlisted command | Wrapper only allows `""` (stdio) and `"tools"`; `ssh mother tools` is correct                   |
