# BizNode MCP Remote Control

## Overview

MCP (Model Context Protocol) enables secure remote control of any BizNode node using Ed25519-signed commands. The admin node can send commands to any basic/pro/admin node. Nodes cannot initiate connections to other nodes unless registered in the trusted_nodes database.

## Architecture

```
Admin Node (this machine, port 7777)
    │
    ├─ generate_mcp_key() → Ed25519 signed token
    │
    ├─ POST /api/mcp/execute → target node
    │   ├─ Token verified against trusted admin public key
    │   ├─ Command executed (health_check, restart, logs, etc.)
    │   └─ Response signed with target node's key
    │
    ├─ tools/admin/remote_control.py (CLI)
    │
    └─ @biznode_bot /support (AI-powered diagnostics)

Basic Node (customer, port 7877)
    ├─ identity/admin_public.pem (embedded at build time)
    ├─ Auto-trusts admin on first run
    └─ Accepts MCP commands from admin immediately

Pro Node (customer, port 7977)
    ├─ identity/admin_public.pem (embedded at build time)
    ├─ Auto-trusts admin on first run
    └─ Accepts MCP commands from admin immediately

Security Model:
    Admin → Any Node:  ALLOWED (admin initiates, signed with admin key)
    Node → Other Node: BLOCKED (unless registered in trusted_nodes)
    All commands:      Ed25519 signed, time-limited tokens
    All responses:     Signed by target node's key
```

## Auto-Trust System

### How It Works

Admin's public key is **embedded into every package at build time**. When a customer installs and runs their node for the first time, the node automatically trusts the admin — no manual key exchange needed.

```
BUILD TIME (admin node):
    scripts/build_package.py
    → Copies identity/node_public.pem into package as identity/admin_public.pem

CUSTOMER INSTALLS:
    Node runs for first time
    → services/mcp_service.py get_or_create_keypair() generates node's own Ed25519 keypair
    → auto_trust_admin() reads identity/admin_public.pem
    → Imports admin's key into trusted_nodes DB
    → Node now trusts admin for MCP commands

NODE REGISTERS WITH NODEMONITOR:
    scripts/setup_node.py register_with_nodemonitor()
    → Sends node's public_key_b64 + node_id to nodemonitor
    → Admin can look up customer's key and auto-trust back

SUPPORT:
    Customer opens port → Admin knows IP:port from registration
    Admin → MCP ping/health_check/restart → works immediately
```

### Key Files

| File | What It Does |
|------|-------------|
| `identity/node_private.pem` | Node's Ed25519 private key (chmod 600) |
| `identity/node_public.pem` | Node's Ed25519 public key |
| `identity/admin_public.pem` | Admin's public key (embedded in package at build time) |

## Token Format

```
<base64url(payload_json)>.<base64url(ed25519_signature)>

Payload:
{
    "v": 1,
    "jti": "unique_id",
    "task_id": null,
    "permission": "admin",
    "issued_to": "192.168.1.108:7877",
    "node_id": "fe826ae6ef2a1658",
    "expires": "2026-03-23T17:43:00+00:00"
}
```

## API Endpoints

### POST /api/mcp/execute

Execute an MCP-signed command on a node. Bypasses dashboard auth — uses Ed25519 token verification.

**Request:**
```json
{
    "token": "<mcp_signed_token>",
    "command": "health_check",
    "params": {}
}
```

**Response:**
```json
{
    "success": true,
    "response": {
        "node_id": "fe826ae6ef2a1658",
        "command": "health_check",
        "result": {"services": {"postgresql": "up", "qdrant": "up", "ollama": "up", "dashboard": "up"}},
        "executed_at": "2026-03-22T17:43:05+00:00"
    },
    "signature": "<ed25519_signature_of_response>"
}
```

### GET /api/mcp/identity

Returns the node's public identity card (no auth needed, for discovery).

```json
{
    "node_id": "fe826ae6ef2a1658",
    "display_name": "BizNode-fe826ae6",
    "public_key_b64": "5de-SqzgUhdx_xqt87JZoTSRyg90WcNjBOn_nBoF9XM",
    "endpoint": "",
    "capabilities": ["task_runner", "mcp_auth", "peer_sync", "vector_search"],
    "version": "biznode/1.0",
    "published_tools": [...]
}
```

## Available Commands

| Command | Permission | Parameters | Description |
|---------|-----------|------------|-------------|
| `ping` | any | — | Test connectivity, returns node_id |
| `health_check` | any | — | Check PostgreSQL, Qdrant, Ollama, Dashboard |
| `get_logs` | any | `file`, `lines` | Fetch bot.log or dashboard.log |
| `get_config` | admin, support | — | Get tier info and feature config |
| `restart_service` | admin | `service` | Restart bot, dashboard, or qdrant |
| `update_env` | admin | `key`, `value` | Update .env variable (blocks sensitive keys) |
| `seed_kb` | any | `articles`, `agent_id` | Seed KB articles remotely |
| `get_identity` | any | — | Get node identity card |
| `get_task_stats` | any | — | Get task queue statistics |
| `run_command` | admin | `cmd` | Run shell command (blocks destructive cmds) |

## Permission Levels

| Level | Can Do |
|-------|--------|
| `admin` | Everything — restart, update_env, run_command, all reads |
| `remote_control` | All reads + restart_service + seed_kb |
| `support` | All reads + get_config |
| `diagnose` | health_check, get_logs, get_identity, ping |
| `update` | seed_kb, get_identity |

## Security Safeguards

### Blocked Commands
Shell commands containing these are rejected:
- `rm`, `del`, `format`, `DROP`, `DELETE FROM`, `shutdown`, `reboot`

### Blocked Env Keys
These cannot be updated remotely:
- `DB_PASSWORD`, `STRIPE_SECRET`, `SMTP_PASSWORD`

### Token Expiry
- Default: 1 day for CLI commands
- Configurable via `expires_days` parameter

## Admin CLI Tool

**Location:** `tools/admin/remote_control.py`

### Basic Usage

```bash
# Test connectivity
python tools/admin/remote_control.py ping 192.168.1.108:7877

# Health check
python tools/admin/remote_control.py health_check 192.168.1.108:7877

# Fetch logs
python tools/admin/remote_control.py get_logs 192.168.1.108:7877 --file bot.log --lines 100

# Restart a service
python tools/admin/remote_control.py restart_service 192.168.1.108:7977 --service bot

# Update env variable
python tools/admin/remote_control.py update_env 192.168.1.108:7877 --key OLLAMA_MODEL --value qwen3:14b

# Seed KB articles
python tools/admin/remote_control.py seed_kb 192.168.1.108:7877 --articles "Article 1 text" "Article 2 text"

# Run shell command
python tools/admin/remote_control.py run_command 192.168.1.108:7877 --cmd "python --version"

# Get node identity
python tools/admin/remote_control.py get_identity 192.168.1.108:7877

# Get task stats
python tools/admin/remote_control.py get_task_stats 192.168.1.108:7877
```

### Network Operations

```bash
# Scan local network for BizNode nodes
python tools/admin/remote_control.py scan --subnet 192.168.1

# Trust a node (add to trusted_nodes DB)
python tools/admin/remote_control.py trust 192.168.1.108:7877

# Batch health check all nodes
python tools/admin/remote_control.py batch health_check 192.168.1.108:7877 192.168.1.108:7977
```

### Programmatic Usage

```python
from tools.admin.remote_control import send_command, get_node_identity, trust_node, batch_command

# Single command
result = send_command("192.168.1.108:7877", "health_check")
print(result["response"]["result"]["services"])

# Get identity
identity = get_node_identity("192.168.1.108:7877")
print(identity["node_id"])

# Trust a node
trust_node("192.168.1.108:7877")

# Batch command to multiple nodes
results = batch_command(
    ["192.168.1.108:7877", "192.168.1.108:7977"],
    "health_check"
)
```

## AI-Powered Support via @biznode_bot

### /support Command

Customers can get AI-powered diagnostics and auto-fixes through @biznode_bot.

```
Customer: /support my dashboard is not loading

Bot: "Checking your node... Target: 192.168.1.108:7877"

Bot: "Node Diagnostics (192.168.1.108:7877)
✅ postgresql: up
✅ ollama: up
❌ dashboard: down
✅ qdrant: up

Analysis:
Issues found:
- Dashboard is down

Auto-Fix:
✅ restart_service: Success

Suggestions:
• Dashboard will be restarted automatically"
```

### How AI Support Works

1. Customer sends `/support [issue description]`
2. Bot looks up customer's registered node by telegram_id
3. Bot runs MCP `health_check` on customer's node
4. AI analyzes diagnostic results
5. Bot attempts auto-fix for common issues (restart down services)
6. Bot reports findings, fixes, and suggestions in chat

### Supported Scenarios

| Scenario | Action |
|----------|--------|
| Dashboard down | Auto-restart via MCP |
| PostgreSQL down | Suggest manual restart command |
| Ollama down | Suggest `ollama serve` |
| Qdrant down | Note it's optional, suggest Docker install |
| All services up, issue described | Suggest log check + manual investigation |
| No registered node | Instructions on how to register |
| API-only tier | KB search + LLM for support questions |

### Location in Code

- `docker/bot/run_bot.py` — `cmd_support()`, `_find_customer_node()`, `_run_node_diagnostics()`, `_analyze_diagnostics()`, `_attempt_auto_fix()`

## Node Ports

| Node Type | Default Port | Set By |
|-----------|-------------|--------|
| Admin Node | 7777 | Default (DASHBOARD_PORT env) |
| Basic Node | 7877 | setup_node.py writes to .env |
| Pro Node | 7977 | setup_node.py writes to .env |
| Nodemonitor | 7333 | Fixed |

## Trust Model

### Initial Setup (Zero-Config for Customers)

1. **Admin node** generates Ed25519 keypair on first run (`identity/node_private.pem`, `identity/node_public.pem`)
2. **Build time** — `build_package.py` embeds admin's `node_public.pem` as `admin_public.pem` in every package
3. **Customer installs** — node generates its own keypair on first run
4. **Auto-trust** — `mcp_service.py auto_trust_admin()` imports `admin_public.pem` into `trusted_nodes` DB
5. **Node registers** — `setup_node.py` sends node's public key to nodemonitor
6. **Admin trusts back** — admin calls `trust_node(target)` or auto-imports from nodemonitor

### Key Exchange Flow

```
Build:    Admin public key ──→ embedded in package
Install:  Customer node ──→ auto-trusts admin (reads admin_public.pem)
Register: Customer node ──→ sends public key to nodemonitor
Support:  Admin ──→ MCP commands work immediately (mutual trust)
```

### Security Rules

- **Admin → Any Node**: Always allowed (admin key embedded in every package)
- **Node → Other Node**: Blocked unless registered in `trusted_nodes`
- **Unknown senders**: Rejected (Ed25519 verification fails)
- **Token expiry**: 1 day default, configurable
- **Destructive commands**: Blocked (`rm`, `del`, `DROP`, `shutdown`, etc.)
- **Sensitive env keys**: Cannot be updated remotely (`DB_PASSWORD`, `STRIPE_SECRET`, `SMTP_PASSWORD`)

## Files Reference

| File | Purpose |
|------|---------|
| `services/mcp_service.py` | Ed25519 keypair, token generation/verification, `auto_trust_admin()` |
| `services/node_identity.py` | Identity card generation with published tools |
| `ui/server.py` | `/api/mcp/execute` and `/api/mcp/identity` endpoints, `_execute_mcp_command()` |
| `tools/admin/remote_control.py` | Admin CLI: `send_command()`, `trust_node()`, `scan_network()`, `batch_command()` |
| `scripts/build_package.py` | Embeds `identity/admin_public.pem` into packages |
| `scripts/setup_node.py` | Sends node's public key to nodemonitor during registration |
| `docker/bot/run_bot.py` | `/support` command — AI diagnostics + auto-fix via MCP |
| `memory/database.py` | `trusted_nodes` table for peer key storage |
| `identity/node_private.pem` | This node's Ed25519 private key (chmod 600, never shared) |
| `identity/node_public.pem` | This node's Ed25519 public key |
| `identity/admin_public.pem` | Admin's public key (embedded in client packages at build time) |

## Network Connectivity & NAT

### Connectivity Scenarios

| Scenario | WireGuard? | How MCP Connects | Latency |
|----------|-----------|-----------------|---------|
| **LAN nodes** (192.168.1.x) | No | Direct IP:port | Instant |
| **Customer opens port** (port forward) | No | Public IP:port | Instant |
| **VPS-hosted nodes** | No | Public IP:port | Instant |
| **Customer behind NAT** (no port forward) | Yes OR pull model | WireGuard tunnel or command queue | 0-2 min |

### Option A: Pull Model (No VPN Required) — Recommended

For customers behind NAT who can't open ports. The node polls nodemonitor for pending support commands.

```
Admin posts command to nodemonitor queue
    → Node polls every 2 min (like webhook_sync)
    → Node finds pending command
    → Node executes MCP command locally
    → Node reports result back to nodemonitor
    → Admin reads result from nodemonitor
```

- Uses existing `webhook_sync_service` pattern
- No VPN or port forwarding needed on customer side
- Latency: up to 2 minutes (polling interval)
- Works for all customers, all network types

### Option B: WireGuard BizMesh VPN — Direct Access

For heavy support cases requiring real-time control. Customer's node joins the BizMesh VPN.

```
Customer node → WireGuard → BizMesh VPN (10.50.0.0/24) → Admin MCP directly
```

- **BizMesh** already exists on nodemonitor (10.50.0.0/24)
- Customer gets a 10.50.0.x IP
- Admin can MCP directly to 10.50.0.x:7877
- Zero latency, full real-time control
- Requires WireGuard installation on customer machine

### Option C: SSH Reverse Tunnel — One-Time Access

For emergency support without permanent VPN setup.

```
Customer runs: ssh -R 0:localhost:7877 user@1bz.biz
    → VPS assigns a random port (e.g., 19877)
    → Admin MCPs to localhost:19877 on VPS
    → Tunnel forwards to customer's node
```

- No permanent VPN needed
- Customer initiates the connection (works behind NAT)
- Tunnel closes when SSH session ends
- Good for one-time support sessions

### When to Use What

| Customer Type | Recommended Method |
|--------------|-------------------|
| LAN / same network | Direct MCP (fastest) |
| Has public IP or port forwarding | Direct MCP |
| Behind NAT, regular support | Pull model via nodemonitor |
| Behind NAT, emergency/real-time | SSH reverse tunnel |
| Behind NAT, ongoing heavy support | WireGuard BizMesh |

### Current Status

| Method | Implemented | Status |
|--------|------------|--------|
| Direct MCP (LAN/public IP) | ✅ Yes | Working — tested on 192.168.1.106:7877 |
| Pull model (nodemonitor queue) | ⬜ Planned | Uses existing webhook_sync pattern |
| WireGuard BizMesh | ✅ Exists | On nodemonitor, needs client setup automation |
| SSH reverse tunnel | ⬜ Manual | Customer runs SSH command, no automation yet |

## Tested & Verified

| Test | Result |
|------|--------|
| Admin → Basic node ping | ✅ `pong: true, node_id: 8bdbd86ba0179193` |
| Admin → Basic node health_check | ✅ `postgresql: up, ollama: up` |
| Auto-trust admin key in package | ✅ Basic node trusts admin on first run |
| Ed25519 signed responses | ✅ All responses signed by target node |
| Network scan | ✅ `scan --subnet 192.168.1` finds nodes |
| /support bot command | ✅ Diagnoses + auto-fix via MCP |
