# EasyClaw Agentic Installer — Project Instructions

## What This Is

A ground-up rewrite of the EasyClaw TUI installer. Python 3.10+ Textual app that deploys the OpenClaw framework on headless Linux servers with an integrated AI assistant (Agent Johnny 5, powered by Kimi K2.5 via OpenRouter).

**Current version**: `0.6.0` (Phases 1–6 complete)
**VPS deployment**: `/opt/easyclaw-installer/` on `31.220.49.171` with venv at `.venv/`

---

## Project Structure

```
New easyclaw.help/
├── pyproject.toml                          # hatchling, v0.6.0
├── bootstrap.sh                            # curl-pipe installer script
├── CLAUDE.md                               # this file
├── docker-compose.yml                      # Docker Compose for MCP server
├── .env.example                            # env template (DB path, admin secret, SearXNG)
├── mcp/                                    # EasyClaw MCP Server (Python, Docker)
│   ├── server.py                           # FastMCP 13 tools + admin API + ASGI dispatcher
│   ├── database.py                         # SQLite schema, FTS5, 18 knowledge guides
│   ├── auth.py                             # Bearer token auth, tier checks, contextvars
│   ├── requirements.txt                    # fastmcp, uvicorn, httpx, starlette, python-dotenv
│   └── Dockerfile                          # Python 3.12-slim, single uvicorn worker
├── packages/
│   ├── openclaw-cli/                       # @openclaw/cli npm package
│   │   ├── package.json                    # v0.1.0, bin: openclaw
│   │   ├── bin/openclaw.js                 # CLI entry point (routes subcommands)
│   │   └── lib/
│   │       ├── config.js                   # config init/write/validate
│   │       ├── token.js                    # token generate
│   │       ├── env.js                      # env setup
│   │       ├── workspace.js                # workspace init/validate
│   │       ├── gateway.js                  # gateway install/configure + HTTP server
│   │       ├── service.js                  # systemd unit creation + management
│   │       ├── server.js                   # OpenClaw server process (port 3100)
│   │       ├── mcp.js                      # mcp connect/status
│   │       ├── channels.js                 # channels init/list
│   │       └── health.js                   # health checks (--full)
│   └── openclaw-mcp-tools/                 # @openclaw/mcp-tools npm package
│       ├── package.json                    # v0.1.0, bin: openclaw-mcp
│       ├── bin/openclaw-mcp.js             # CLI entry point
│       └── lib/
│           └── server.js                   # MCP JSON-RPC 2.0 tool server
└── src/easyclaw_installer/
    ├── __init__.py                         # version string
    ├── __main__.py                         # CLI entry: --api-key, --mcp-url args
    ├── app.py                              # Textual App (root) — 60/40 layout
    ├── constants.py                        # API URLs, model ID, limits, MCP defaults
    ├── state.py                            # InstallState dataclass
    ├── messages.py                         # 8 custom Textual Messages
    ├── ui/
    │   ├── log_pane.py                     # Left 60% — RichLog + color-coded results
    │   ├── chat_pane.py                    # Right 40% — RichLog + Input
    │   └── status_bar.py                   # 3-line status bar (progress + counts + keys)
    ├── installer/
    │   ├── base_step.py                    # BaseStep ABC (subprocess streaming)
    │   ├── runner.py                       # StepRunner orchestrator + resume_from()
    │   └── steps/
    │       ├── __init__.py                 # STEP_REGISTRY (15 production steps)
    │       ├── s01_preflight.py            # OS, arch, disk, RAM, root check
    │       ├── s02_nodejs.py               # Node.js 20.x LTS via NodeSource
    │       ├── s03_openclaw_cli.py         # @openclaw/cli global install
    │       ├── s04_mcp_tools.py            # @openclaw/mcp-tools install
    │       ├── s05_config_wizard.py        # openclaw config init
    │       ├── s06_token.py                # openclaw token generate
    │       ├── s07_write_config.py         # openclaw config write
    │       ├── s08_env_vars.py             # environment variables setup
    │       ├── s09_workspace.py            # workspace directory init
    │       ├── s10_validate.py             # config + workspace validation
    │       ├── s11_gateway.py              # API gateway install + configure
    │       ├── s12_systemd.py              # systemd units + service start
    │       ├── s13_mcp_connect.py          # MCP knowledge base connection
    │       ├── s14_channels.py             # communication channel setup
    │       └── s15_health.py               # full health check
    ├── agent/
    │   ├── openrouter.py                   # Sync httpx.Client streaming SSE
    │   ├── tools.py                        # 6 local tools + sync executors
    │   ├── prompts.py                      # System prompt with recovery workflow + troubleshooting
    │   └── brain.py                        # Agent loop (sync, thread-bound, MCP routing)
    └── mcp/
        ├── __init__.py
        ├── client.py                       # JSON-RPC 2.0 client (Streamable HTTP + SSE)
        └── registry.py                     # MCP→OpenAI tool format converter
```

---

## Critical Architecture Rules

### 1. Concurrency Boundaries — DO NOT VIOLATE

| Component | Where it runs | Why |
|-----------|--------------|-----|
| Textual UI | Main asyncio loop | UI must own the main loop |
| StepRunner | Main loop (`@work(exclusive=True)` async) | `asyncio.create_subprocess_shell` is non-blocking |
| **Agent Brain** | **Background thread (`@work(thread=True)`)** | **HTTP streaming + JSON parsing + file I/O would stutter the UI** |

**The Agent Brain is 100% synchronous.** It uses `httpx.Client` (not AsyncClient), `subprocess.run` (not asyncio), and `open()` for file I/O. All UI updates go through `app.call_from_thread(app.post_message, msg)`.

### 2. Chat UI Uses RichLog — NOT VerticalScroll + Static

The chat pane uses `RichLog` for the message history. Do NOT replace it with `VerticalScroll` and dynamically-mounted `Static` widgets — that causes severe rendering lag during streaming. RichLog is O(1) append.

### 3. Streaming Strategy

RichLog can't update the last line in-place. So:
- Brain posts `AgentTyping` → chat shows "thinking..."
- Brain streams internally (no per-token UI updates)
- Brain posts one final `AgentReply` with complete text

### 4. Log Buffer (Agent's Eyes)

`InstallState.log_buffer` is a `deque(maxlen=100)` that captures every line of subprocess output. It's injected into the system prompt on every agent call so Johnny 5 can "see" the terminal. Do NOT increase maxlen past ~200 or it will bloat the system prompt.

### 5. PID Lock

`InstallState.pid_lock` is an `asyncio.Lock` protecting `active_process`/`active_pid`. The agent's `interrupt_active_step` tool reads the process reference from a sync thread — it does NOT acquire the lock (reads are safe; the lock protects writes only).

---

## VPS Deployment

```bash
# SSH into VPS
ssh root@31.220.49.171

# The package lives here
cd /opt/easyclaw-installer

# Venv
source .venv/bin/activate

# Run (uses key from ~/.easyclaw/agent.json)
python -m easyclaw_installer --api-key "$(python3 -c 'import json; print(json.load(open("/root/.easyclaw/agent.json"))["openrouter_key"])')"

# Or set env var
export OPENROUTER_API_KEY=sk-or-v1-...
python -m easyclaw_installer
```

### Deploying Changes

From local Windows, the workflow is:
1. Edit files locally in `C:\Users\me\OneDrive\Automation Projects\New easyclaw.help\`
2. SCP files to VPS: `scp <local_file> root@31.220.49.171:/opt/easyclaw-installer/<remote_path>`
3. Reinstall: `ssh root@31.220.49.171 "/opt/easyclaw-installer/.venv/bin/pip install -e /opt/easyclaw-installer"`
4. Run headless test or interactive test over SSH

**Do NOT pip install locally** — this project targets Linux. The local copy is source-of-truth only.

### Headless Testing Pattern

```python
import asyncio
from easyclaw_installer.app import EasyClawApp

async def main():
    app = EasyClawApp(api_key="sk-or-v1-...")
    async with app.run_test(size=(120, 40)) as pilot:
        await pilot.pause()
        await asyncio.sleep(12)  # let steps run
        # ... interact with pilot ...
        app.save_screenshot("/tmp/screenshot.svg")

asyncio.run(main())
```

---

## Phase Status

| Phase | Status | What It Proved |
|-------|--------|----------------|
| 1. UI & Concurrency Shell | **DONE** | 60/40 split, no stutter under load |
| 2. State Machine & Log Piping | **DONE** | Real subprocess streaming, deque populated, PID tracking |
| 3. AI Brain (Agent Johnny 5) | **DONE** | Kimi K2.5 streaming, tool calling (shell/file/interrupt), proactive intervention |
| 4. Remote MCP Knowledge Base | **DONE** | JSON-RPC 2.0 client (Streamable HTTP + SSE), auto-detection, tool routing |
| 5. Production Steps & Packaging | **DONE** | 15 real steps, bootstrap.sh, precondition checks |
| 6. UI Polish + Agent Recovery + npm Packages | **DONE** | 3-line status bar, color-coded logs, restart_step/resume_pipeline tools, proper Node.js npm packages |
| 7. MCP Server Deployment | **DONE** | Production MCP server at mcp.easyclaw.help — 13 tools, 18 guides, FTS5 search, admin API, auth, rate limiting |

---

## Phase 4: Remote MCP Knowledge Base (COMPLETE)

### What Was Built
- `mcp/client.py` — `MCPClient` with dual-transport support:
  - **Streamable HTTP** (FastMCP >= 2.0): simple POST/response to `/mcp`
  - **SSE fallback** (legacy): GET `/sse` + threaded reader + POST to endpoint
  - Transport auto-detection: tries Streamable HTTP first, falls back to SSE
  - Session management via `Mcp-Session-Id` header
  - JSON-RPC 2.0 initialize/list_tools/call_tool lifecycle
- `mcp/registry.py` — `MCPToolRegistry`:
  - Converts MCP `inputSchema` to OpenAI `parameters` format
  - Prefixes all tool names with `mcp_` for routing
  - `is_mcp_tool()` / `call()` for transparent routing from AgentBrain
- **Brain integration**: lazy MCP init via `ensure_mcp()` (runs in background thread)
  - Tool routing: `mcp_*` names → `MCPToolRegistry.call()` → `MCPClient.call_tool()`
  - MCP tool names injected into system prompt so the agent knows what's available
- **CLI**: `--mcp-url` (default: `https://mcp.easyclaw.help`) and `--no-mcp` flag
- **App**: `init_agent_mcp()` @work(thread=True) kicked off on mount — non-blocking

### Architecture Notes
- MCPClient is 100% synchronous (httpx.Client, not AsyncClient) — matches agent thread constraint
- SSE reader runs in a daemon sub-thread inside the agent thread
- MCP tools gracefully degrade: if server unreachable, agent works with local tools only
- MCP tool results are JSON-wrapped before returning to the agent conversation

## Phase 5: Production Steps & Packaging (COMPLETE)

### What Was Built
- **15 production installation steps** replacing the 3 test steps:
  1. **Preflight** — OS, arch (x86_64/aarch64), disk (>= 2GB), RAM (>= 512MB), root
  2. **Node.js** — Install 20.x LTS via NodeSource (skip if >= 18 found)
  3. **OpenClaw CLI** — `npm install -g /opt/openclaw-cli` (local mock package)
  4. **MCP Tools** — `npm install -g /opt/openclaw-mcp-tools` (local mock package)
  5. **Config Wizard** — `openclaw config init --non-interactive`
  6. **Token** — `openclaw token generate`
  7. **Write Config** — `openclaw config write`
  8. **Env Vars** — `openclaw env setup`
  9. **Workspace** — `openclaw workspace init` at `/var/lib/openclaw`
  10. **Validate** — `openclaw config validate` + `workspace validate`
  11. **Gateway** — `openclaw gateway install` + `configure`
  12. **Systemd** — Create units, daemon-reload, enable + start services
  13. **MCP Connect** — `openclaw mcp connect` + `status`
  14. **Channels** — `openclaw channels init` + `list`
  15. **Health Check** — Service status + `openclaw health --full`
- **`bootstrap.sh`** — curl-pipe installer:
  - Root check (auto-sudo if available)
  - Python >= 3.10 detection (auto-install via apt if missing)
  - Venv creation at `/opt/easyclaw-installer/.venv/`
  - Package install via pip
  - Symlink to `/usr/local/bin/easyclaw-install`
  - Auto-launch of the TUI

### OpenClaw CLI Packages

Real Node.js npm packages live in `packages/` locally and deployed to VPS:

- **`@openclaw/cli`** (`packages/openclaw-cli/`) — Full Node.js CLI with proper `package.json` and `bin` entry
  - Routes 9 subcommand groups via `bin/openclaw.js`
  - Creates real files and directories (`/etc/openclaw/`, `/var/lib/openclaw/`, etc.)
  - `lib/server.js` + `lib/gateway.js` — standalone Node.js HTTP servers for systemd
- **`@openclaw/mcp-tools`** (`packages/openclaw-mcp-tools/`) — MCP JSON-RPC 2.0 tool server
  - 3 tools: `search_knowledge`, `get_config_reference`, `get_troubleshooting`

Steps 3-4 source files reference `@openclaw/cli@latest` / `@openclaw/mcp-tools@latest`, but VPS copies are patched to install from local paths (`/opt/openclaw-cli`, `/opt/openclaw-mcp-tools`) until packages are published to npm.

### Architecture Notes
- Steps use `validate_preconditions()` to enforce dependency ordering
- Timeouts tuned per step: 30s (checks) → 300s (apt/npm installs) → 180s (gateway)
- Retries enabled for network-dependent steps (Node.js, npm, MCP connect)
- Retries disabled for deterministic steps (preflight, config, token)
- Old test steps (s01-s03) kept in source tree but removed from STEP_REGISTRY

### E2E Test Results (2026-03-08)
- **15/15 steps pass** in both offline mode and with Agent Johnny 5 enabled
- Both systemd services run (`openclaw.service` + `openclaw-gateway.service`)
- Full health check passes: Configuration, Token, Workspace, Gateway, MCP all OK
- Agent proactive intervention tested — Johnny 5 correctly diagnoses failures from log context
- Rick manually tested interactive install over SSH — confirmed working
- Mock services respond on ports 3100 (server) and 3200 (gateway)

## Phase 6: UI Polish + Agent Recovery + npm Packages (COMPLETE)

### What Was Built

#### UI Polish
- **3-line StatusBar** (`status_bar.py` — complete rewrite):
  - Line 1: ASCII progress bar `[========>...........] 5/15 Config Wizard`
  - Line 2: Step counts `Completed: 4  |  Failed: 0  |  Status: Running`
  - Line 3: Key hints `q: Quit  |  Tab: Switch pane`
  - Tracks `_completed` / `_failed` counts via `record_step_result(success)`
  - Method named `_refresh_display()` (NOT `_render()` — conflicts with Textual internals)
- **Color-coded step results in LogPane** (`log_pane.py`):
  - New `append_step_result()` method writes Rich `Text` objects
  - Success: green `✓ Step 5/15: Config Wizard — OK`
  - Failure: red `✗ Step 5/15: Config Wizard — FAILED: <error>`
- **App routing** (`app.py`):
  - `on_step_completed` tracks results in `state.step_results`, updates status bar counts, writes color-coded log entries
  - `Binding("q", "quit", "Quit", priority=True)` for clean exit
  - `state.main_loop = asyncio.get_event_loop()` stored on mount (needed for agent tools)

#### Agent Recovery Tools
- **`restart_step` tool** (`tools.py`):
  - Finds step in STEP_REGISTRY by name (case-insensitive)
  - Uses `asyncio.run_coroutine_threadsafe()` to schedule step execution on main event loop
  - Blocks agent thread with `future.result(timeout=...)` waiting for completion
  - Returns JSON `{"success": true/false, "step_name": "..."}`
- **`resume_pipeline` tool** (`tools.py`):
  - Validates step number (1-15), calls `app.run_resume_pipeline(from_step)` via `call_from_thread`
  - Returns immediately — pipeline runs asynchronously
- **StepRunner `resume_from()`** (`runner.py`):
  - Resets state, iterates STEP_REGISTRY from given index onward
  - Same retry/precondition logic as `run_all_steps()`
- **`execute_tool` signature change**: `(name, args, state)` → `(name, args, state, app)` — app needed for cross-thread scheduling
- **Enhanced system prompt** (`prompts.py`):
  - 5-step recovery workflow: read error → investigate → fix → restart_step → resume_pipeline
  - Troubleshooting guide for Node.js, npm, config, token, systemd, gateway, workspace, health check issues
- **State tracking** (`state.py`):
  - `step_results: list` — ordered (name, success) tuples
  - `main_loop: Optional[asyncio.AbstractEventLoop]` — main event loop reference

#### Real OpenClaw npm Packages
- **`packages/openclaw-cli/`** — `@openclaw/cli` v0.1.0:
  - Proper Node.js package replacing the bash mock scripts
  - `bin/openclaw.js` routes 9 subcommand groups to lib/ modules
  - All subcommands create real files/directories (`/etc/openclaw/`, `/var/lib/openclaw/`, etc.)
  - `lib/server.js` + `lib/gateway.js` — standalone Node.js HTTP servers for systemd
  - Gateway has `require.main === module` guard for direct execution
- **`packages/openclaw-mcp-tools/`** — `@openclaw/mcp-tools` v0.1.0:
  - MCP-compatible JSON-RPC 2.0 tool server with 3 tools
  - `search_knowledge`, `get_config_reference`, `get_troubleshooting`
- **Steps s03/s04** updated to reference `@openclaw/cli@latest` / `@openclaw/mcp-tools@latest`
  - VPS copies patched to use local paths until packages are published to npm

### Architecture Notes
- `_refresh_display()` NOT `_render()` — Textual reserves `_render()` for internal Widget rendering pipeline. Using it causes `AttributeError: 'NoneType' object has no attribute 'render_strips'`.
- `restart_step` crosses the thread boundary: agent thread → `asyncio.run_coroutine_threadsafe()` → main loop. The agent thread blocks on `future.result()` while the step runs asynchronously.
- `resume_pipeline` does NOT block — it fires and forgets via `call_from_thread()`.
- `StepCompleted` message has no `total` attribute — use `self.state.total_steps` instead.
- npm packages use `require.main === module` guard so they work both as modules and direct-execution targets for systemd.

### E2E Test Results (2026-03-08, post-Phase 6)
- **15/15 steps pass** with proper Node.js npm packages (replacing bash mocks)
- 3-line status bar renders correctly with progress bar, counts, and key hints
- Color-coded step results appear in log pane (green checkmarks, red X's)
- Both systemd services active (using Node.js `server.js` and `gateway.js`)
- Agent tools `restart_step` and `resume_pipeline` functional
- All `openclaw` CLI subcommands verified working

---

## Gotchas

1. **Python on VPS is externally-managed (PEP 668)** — always use the venv at `/opt/easyclaw-installer/.venv/`
2. **OpenRouter API key** lives in `/root/.easyclaw/agent.json` under `openrouter_key` field
3. **RichLog `markup=False`** in chat_pane — we write Rich `Text` objects directly, not markup strings
4. **RichLog `markup=True`** in log_pane — install logs use Rich markup for colors
5. **`@work(thread=True)` for agent** — if you add new agent methods, they MUST run in a thread worker, never on the main loop
6. **Tool safety blocklist** in `agent/tools.py` — `rm -rf /`, `mkfs`, `dd`, fork bombs are blocked
7. **Conversation trimmed to 30 messages** — see `MAX_CONVERSATION_MESSAGES` in `constants.py`
8. **SVG screenshots** saved by headless tests at `/tmp/easyclaw_*.svg` on VPS — these accumulate, clean periodically
9. **No local WSL2 install** — `pip install -e .` was run locally once by accident and uninstalled. Keep it VPS-only.
10. **The old EasyClaw v0.4.4** lives at `C:\Users\me\OneDrive\Automation Projects\easyclaw.help\` — this is a SEPARATE project, do NOT reference or copy from it
11. **MCP lazy init** — `MCPClient.connect()` blocks for 1-2 seconds; it runs in `@work(thread=True)` via `init_agent_mcp()`, never on the main loop
12. **MCP tool prefix** — all MCP tools are prefixed `mcp_` to avoid name collisions with local tools (e.g., `mcp_search_knowledge`)
13. **MCP transport fallback** — Streamable HTTP tried first, SSE second; the SSE reader uses a daemon sub-thread with a response queue
14. **`--no-mcp` flag** — disables MCP entirely (useful for offline testing or when the server is down)
15. **OpenClaw npm packages** source lives in `packages/openclaw-cli/` and `packages/openclaw-mcp-tools/` locally. On VPS, deployed to `/opt/openclaw-cli/` and `/opt/openclaw-mcp-tools/`. Steps 3-4 install from local paths on VPS. Source files (`s03`, `s04`) reference `@openclaw/cli@latest` but VPS copies are patched to use local paths via sed until packages are published to npm.
16. **Before re-testing the full install**, clean up artifacts: `systemctl stop openclaw openclaw-gateway; rm -rf /etc/openclaw /var/lib/openclaw /var/log/openclaw; npm uninstall -g @openclaw/cli @openclaw/mcp-tools; systemctl daemon-reload`
17. **Never name Textual widget methods `_render()`** — it conflicts with Textual's internal `Widget._render()` and causes `AttributeError: 'NoneType' object has no attribute 'render_strips'`. Use `_refresh_display()` or similar instead.
18. **Agent now has 6 tools** — `run_shell_command`, `read_file`, `write_file`, `interrupt_active_step`, `restart_step`, `resume_pipeline`. The `execute_tool` function signature is `(name, args, state, app)`.
19. **`restart_step` uses `asyncio.run_coroutine_threadsafe()`** to schedule async step execution from the sync agent thread. The agent thread blocks waiting for the result. Do NOT call this from the main loop — it would deadlock.
20. **`StepCompleted` message fields**: `index`, `name`, `success`, `error` — no `total` attribute. Use `self.state.total_steps` for the total count.
21. **FastMCP 3.0.2 API changes** — `stateless_http` is NOT a constructor param. Pass it to `mcp.http_app(stateless_http=True)`. Method is `http_app()` not `streamable_http_app()`.
22. **FastMCP lifespan routing** — The ASGI dispatcher MUST route `scope["type"] == "lifespan"` to the MCP app (not admin app), otherwise FastMCP's `StreamableHTTPSessionManager` task group won't initialize.
23. **FastMCP `host="0.0.0.0"` not needed in constructor** — In FastMCP 3.x, the host/port are set by uvicorn, not the constructor. Only pass the server name to `FastMCP("name")`.
24. **MCP server at `/opt/easyclaw/`** — Docker Compose project. Source files in `mcp/` subdir. Rebuild: `cd /opt/easyclaw && docker compose up -d --build`. The `.env` file has the admin secret and SearXNG URL.
25. **MCP admin secret** — Stored in `/opt/easyclaw/.env` as `ADMIN_SECRET`. Also saved to Google Keep note "EasyClaw MCP Server Credentials - March 2026".
26. **MCP test API key** — `ec_live_a4094f46f6674d00ade8edf6edf077ee` (subscriber tier, user: rick@openclaw.com). Created via admin API for testing.
27. **MCP client auth** — `MCPClient` now accepts `api_key` param. Auth threaded through `--mcp-key` CLI arg → `EasyClawApp(mcp_key=)` → `AgentBrain(mcp_key=)` → `MCPClient(api_key=)`. Also supports `EASYCLAW_MCP_KEY` env var. Without a key, protocol ops (initialize, list_tools) work but tool calls return auth errors.
28. **Legacy MCP server removed** — `/opt/mcp-server/` (293-line prototype, 111MB with venv) has been deleted. The production server is at `/opt/easyclaw/`.

---

## Phase 7: MCP Server Deployment (COMPLETE)

### What Was Built
- **Production MCP server** at `https://mcp.easyclaw.help` — 13 tools, 18 knowledge guides, admin REST API
- **4 Python files** in `mcp/` directory:
  - `server.py` (~1,640 lines) — FastMCP tools, Starlette admin API, raw ASGI dispatcher
  - `database.py` (~1,070 lines) — SQLite schema (7 tables), FTS5, KNOWLEDGE_BASE dict
  - `auth.py` (~155 lines) — Bearer token validation, tier checks, contextvars
  - `requirements.txt` — fastmcp>=2.0.0, uvicorn, httpx, python-dotenv, starlette
- **Docker deployment** — Dockerfile (Python 3.12-slim), docker-compose.yml with Traefik labels
- **Deployed on VPS** at `/opt/easyclaw/` as Docker container `easyclaw-mcp`

### The 13 MCP Tools
| # | Tool | Tier | Description |
|---|------|------|-------------|
| 1 | `search_knowledge` | starter | FTS5 search across 18 guides, top 5 with 600-char previews |
| 2 | `get_full_guide` | starter | Complete guide by topic key |
| 3 | `get_model_recommendation` | starter | Use-case-based model recs (coding, general, fast, cheap, etc.) |
| 4 | `validate_config` | starter | Configuration checklist across 7 categories |
| 5 | `search_web` | starter | SearXNG private web search (Google + DuckDuckGo) |
| 6 | `recommend_skills` | starter | ClawHub skill pack recommendations by category |
| 7 | `generate_config` | starter | openclaw.json templates (7 profiles) |
| 8 | `security_audit` | starter | 11-item security checklist with severity levels |
| 9 | `estimate_cost` | starter | Monthly cost calculator across 12 models |
| 10 | `diagnose_issue` | subscriber | Error pattern matching (20+ patterns) |
| 11 | `validate_deployment` | subscriber | 9-step deployment health check |
| 12 | `get_foreman_config` | subscriber | Complete config + systemd service generation |
| 13 | `troubleshoot_gateway` | subscriber | Guided diagnostics for 7 failure categories |

### The 18 Knowledge Guides
quick_start, openrouter_models, persistent_memory, channels, troubleshooting, skills, security_hardening, automation_crons, sub_agents, browser_automation, voice_tts, docker_deployment, cost_optimization, clawhub_skills_detailed, mcp_integration, configuration_reference, onboarding_wizard, easyclaw_code_guide

### Admin REST API
14 endpoints behind `X-Admin-Secret` header (HMAC timing-safe comparison):
- User management: `POST /admin/users`
- Subscriptions: `POST /admin/subscriptions`, `PUT /admin/subscriptions/status`
- Usage analytics: `GET /admin/usage`
- Lookup: `GET /admin/lookup?email=...`
- Knowledge CRUD: `POST/GET /admin/knowledge`, `POST /admin/knowledge/reload`, `DELETE /admin/knowledge/{topic}`
- OTP: `POST /admin/otp/generate`, `POST /admin/otp/verify`
- Guide purchases: `POST /admin/guide-purchases`, `GET /admin/validate-download`
- Health: `GET /health` (no auth)

### Architecture Notes
- **Raw ASGI dispatcher** — Not Starlette Mount (avoids redirect loops with FastMCP + Traefik)
- **contextvars for auth** — `_auth_context` ContextVar set in ASGI dispatcher, read by tool functions
- **SQLite WAL + FTS5** — Porter stemming, BM25 ranking, LIKE fallback for operator chars
- **Single uvicorn worker** — Matches SQLite's single-writer constraint
- **Two-level knowledge** — Static KNOWLEDGE_BASE dict + dynamic overrides via admin API
- **Bearer token auth** — SHA-256 hash lookup, per-tool tier enforcement, daily rate limiting via SQL
- **SearXNG integration** — Private web search via `http://searxng:8080` on Docker network

### E2E Test Results (2026-03-08)
- Health check: `{"status":"ok","tools":13,"guides":18}`
- MCP initialize handshake: successful (protocolVersion 2025-03-26)
- tools/list: 13 tools with correct JSON Schema input schemas
- search_knowledge: FTS5 returns ranked results with previews
- estimate_cost: formatted cost comparison table
- Auth: invalid keys rejected, valid keys pass through
- Admin API: user creation, subscription creation, knowledge management all working
- HTTPS: valid Let's Encrypt certificate via Traefik

---

## Session Handoff (2026-03-08, updated post-Phase 7 + MCP client auth)

### Current State
- **v0.6.0 — fully built, deployed, and tested.** All 7 phases complete.
- VPS at `31.220.49.171` has the installer at `/opt/easyclaw-installer/` with `easyclaw-install` symlinked globally.
- **MCP server live** at `https://mcp.easyclaw.help` — 13 tools, 18 guides, auth + rate limiting.
- **MCP client auth working** — `MCPClient` sends Bearer tokens, threaded through `--mcp-key` CLI arg / `EASYCLAW_MCP_KEY` env var.
- OpenClaw services are currently **running** (`openclaw.service` + `openclaw-gateway.service` both active on ports 3100/3200).
- All Python source files, npm packages, and MCP server files synced between local Windows and VPS.
- Agent Johnny 5 has 6 local tools + 13 MCP tools for knowledge-augmented installation assistance.
- **Legacy MCP server deleted** — `/opt/mcp-server/` (293-line prototype) removed, freeing 111MB.

### What Was Done This Session
1. **MCP client auth** — Added `api_key` parameter to `MCPClient`, threaded `--mcp-key` CLI arg through `EasyClawApp` → `AgentBrain` → `MCPClient`. Also supports `EASYCLAW_MCP_KEY` env var.
2. **MCP connection verified** — Tested from VPS: Streamable HTTP connects, 13 tools listed, auth enforced (unauthenticated calls rejected), authenticated tool calls return knowledge base content (search_knowledge, estimate_cost, get_model_recommendation all verified).
3. **MCPToolRegistry integration verified** — 13 tools load with `mcp_` prefix, `is_mcp_tool()` routing works correctly, `call()` through registry returns results.
4. **Legacy cleanup** — Removed `/opt/mcp-server/` (293-line prototype, 111MB) and `/opt/mcp-nginx.conf` (empty stale directory).
5. **Documentation** — Updated CLAUDE.md (session handoff, gotchas #27-28), MEMORY.md (Phase 7 status, MCP server patterns, architecture, TODO list).
6. **Files modified and deployed to VPS** — `mcp/client.py`, `agent/brain.py`, `app.py`, `__main__.py` (all re-installed via pip).

### What's Running on the VPS Right Now
| Path | What |
|------|------|
| `/opt/easyclaw-installer/` | EasyClaw TUI installer (Python, editable pip install) |
| `/opt/easyclaw/` | MCP server Docker Compose project (source in `mcp/` subdir) |
| `/opt/openclaw-cli/` | `@openclaw/cli` Node.js package (proper npm package) |
| `/opt/openclaw-mcp-tools/` | `@openclaw/mcp-tools` Node.js package (MCP tool server) |
| `/etc/openclaw/` | Config files created by the installer |
| `/var/lib/openclaw/` | Workspace directories |
| `/var/log/openclaw/` | Log directory |
| Docker: `easyclaw-mcp` | MCP server on port 8000 (behind Traefik → `mcp.easyclaw.help`) |
| systemd: `openclaw.service` | Node.js server on port 3100 (`lib/server.js`) |
| systemd: `openclaw-gateway.service` | Node.js gateway on port 3200 (`lib/gateway.js`) |

### TODO — Next Steps

1. **Publish npm packages to registry** — The `@openclaw/cli` and `@openclaw/mcp-tools` packages are built and tested locally on VPS but not published to npm. Requires a Granular Access Token from npmjs.com (account: `ricklaughunn`, org: `@openclaw`). Once published, remove the `sed` patches on VPS that rewrite install paths.

2. **Publish to PyPI** — The `pyproject.toml` and `bootstrap.sh` are ready. Once the real npm packages exist on the registry, publish `easyclaw-installer` to PyPI so `curl -fsSL https://easyclaw.help/install.sh | bash` works for end users.

3. **Host bootstrap.sh** — Set up `easyclaw.help` domain to serve `bootstrap.sh` at `/install.sh`.

4. **Testing** —
   - Add unit tests for individual steps (currently only E2E)
   - Test on non-Ubuntu distros (the installer assumes apt/systemd)
   - Test the bootstrap.sh on a fresh VM (not just the existing VPS)

5. **Security** — The OpenRouter API key is stored in plaintext at `/root/.easyclaw/agent.json`. Consider encrypting it or using environment variables exclusively.
