# External WP-CLI Testing - Implementation Reference

## Complete Data Flow

### 1. User Enables Toggle in Builder

**File:** `chat/public/builder.js`

```javascript
// Line ~36
builderState.externalTestingEnabled = false  // default

// User clicks toggle → sets to true
builderState.externalTestingEnabled = true

// When sending message (line ~3967)
const payload = {
  content: messageContent,
  model,
  cli,
  externalTestingEnabled: !!builderState.externalTestingEnabled
}
```

---

### 2. Chat Server Receives Message

**File:** `chat/server.js`

```javascript
// Line ~8804
const wpCliMcpEnabled = message?.externalTestingEnabled === true

// Line ~8885-8901: If enabled, inject MCP server
if (wpCliMcpEnabled) {
  const wpMcpServerPath = path.resolve(__dirname, '../opencode/mcp-servers/wp-cli-testing/index.js')
  
  executionEnv.OPENCODE_EXTRA_MCP_SERVERS = JSON.stringify([
    {
      name: 'wp-cli-testing',
      command: 'node',
      args: [wpMcpServerPath],
      disabled: false
    }
  ])
} else {
  delete executionEnv.OPENCODE_EXTRA_MCP_SERVERS  // Safety gate
}

// Pass env to OpenCode
await opencodeManager.executeInSession(..., { env: executionEnv })
```

---

### 3. OpenCode Reads Environment Variable

**File:** `opencode/packages/opencode/src/config/config.ts`

```typescript
// Line ~233-294: Parse OPENCODE_EXTRA_MCP_SERVERS
const extraMcpRaw = process.env.OPENCODE_EXTRA_MCP_SERVERS
if (extraMcpRaw && typeof extraMcpRaw === "string") {
  const parsed = JSON.parse(extraMcpRaw)
  if (Array.isArray(parsed)) {
    result.mcp ??= {}
    for (const entry of parsed) {
      result.mcp[entry.name] = {
        type: "local",
        command: [...commandParts, ...argsParts],
        env: envRecord,
        enabled: true,
        ...
      }
    }
  }
}
```

Result: OpenCode config now has `mcp['wp-cli-testing']` configured

---

### 4. OpenCode Starts MCP Server

**File:** `opencode/packages/opencode/src/mcp/index.ts`

OpenCode's MCP system automatically:
1. Reads `config.mcp['wp-cli-testing']`
2. Spawns: `node opencode/mcp-servers/wp-cli-testing/index.js`
3. Connects via stdio transport
4. Calls `listTools()` to get available tools

---

### 5. MCP Server Registers Tools

**File:** `opencode/mcp-servers/wp-cli-testing/index.js`

```javascript
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: "test_plugin_external_wp",
        description: "Run WP-CLI based verification...",
        inputSchema: { ... }
      },
      {
        name: "external_wp_testing_config",
        description: "Return resolved config...",
        inputSchema: { ... }
      }
    ]
  }
})
```

---

### 6. AI Invokes test_plugin_external_wp Tool

**File:** `opencode/packages/opencode/src/session/prompt.ts`

OpenCode's session handler:
1. Tool calls go through `resolveTools()` (line ~668)
2. MCP tools added via `MCP.tools()` (line ~745)
3. AI invokes tool with parameters

```javascript
test_plugin_external_wp({
  plugin_path: "/workspace/my-plugin",
  plugin_slug: "my-plugin",
  test_scenarios: [...]
})
```

---

### 7. MCP Server Handles Tool Call

**File:** `opencode/mcp-servers/wp-cli-testing/index.js`

```javascript
server.setRequestHandler(CallToolRequestSchema, async (req) => {
  const toolName = req.params.name
  const args = req.params.arguments

  // Import external-wp-testing module
  const externalTesting = await import("../../../chat/external-wp-testing.js")
  const mod = externalTesting.default ?? externalTesting
  
  const tester = mod.createExternalWpTester({})
  
  const result = await tester.runTest(
    {
      plugin_path: args.plugin_path,
      plugin_slug: args.plugin_slug,
      ...
    },
    { configOverrides: {} }
  )
  
  return { content: [{ type: "text", text: JSON.stringify(result) }] }
})
```

---

### 8. External WP Testing Executes

**File:** `chat/external-wp-testing.js`

```javascript
async function runTest(input, opts) {
  const config = getExternalTestingConfig(opts.configOverrides)
  
  // Queue the test
  return queue.enqueue(async () => {
    // 1. Create subsite (if multisite enabled)
    if (config.enableMultisite) {
      subsite = await createSubsite(config, sessionId)
      // Runs: wp site create --slug=test-abc123
    }
    
    // 2. Upload plugin via SCP
    if (pluginPath) {
      await installPluginFromLocal(config, pluginPath, pluginSlug, sessionId)
    }
    
    // 3. Install required plugins
    const installedPlugins = await installRequiredPlugins(config, subsite.url, requiredPlugins)
    
    // 4. Activate test plugin
    if (pluginSlug) {
      await runSshCommand(config, 
        buildWpCliCommand(config, subsite.url, `plugin activate ${pluginSlug}`)
      )
    }
    
    // 5. Run test scenarios
    const cliTests = await runTestScenarios(config, subsite.url, scenarios)
    
    // 6. Schedule cleanup
    if (config.autoCleanup && subsite.slug) {
      setTimeout(() => { deleteSubsite(config, subsite.slug) }, config.cleanupDelayMs)
    }
    
    return { ok: true, test_results: { cli_tests: cliTests }, ... }
  })
}
```

---

### 9. SSH Commands Execute on WordPress Server

**Functions:**
- `buildWpCliCommand(config, url, command)` → `wp --path=/var/www/html --url=https://site.com/test-abc command`
- `runSshCommand(config, command)` → Executes via SSH with key auth

**Example Commands Executed:**
```bash
# Create subsite
ssh -i ~/.ssh/wp-key wordpress@wp-test.com "wp --path=/var/www/html site create --slug=test-abc123"

# Copy plugin
scp -i ~/.ssh/wp-key -r /local/plugin wordpress@wp-test.com:/var/www/html/wp-content/plugins/

# Activate plugin
ssh -i ~/.ssh/wp-key wordpress@wp-test.com "wp --path=/var/www/html --url=https://wp-test.com/test-abc123 plugin activate my-plugin"

# Run test
ssh -i ~/.ssh/wp-key wordpress@wp-test.com "wp --path=/var/www/html --url=https://wp-test.com/test-abc123 plugin status my-plugin"

# Later: Delete subsite
ssh -i ~/.ssh/wp-key wordpress@wp-test.com "wp --path=/var/www/html site delete --slug=test-abc123 --yes"
```

---

### 10. Results Return to AI

**Return Path:**
```
WordPress Server (via SSH)
  ↓ command output
external-wp-testing.js runTest()
  ↓ formatted result
MCP Server CallToolRequestSchema handler
  ↓ JSON response
OpenCode MCP client
  ↓ tool result
AI Session Handler
  ↓ assistant message
Chat Server
  ↓ SSE stream
Builder UI
```

**Result Format:**
```json
{
  "ok": true,
  "session_id": "abc123",
  "subsite_url": "https://wp-test.example.com/test-abc123",
  "test_results": {
    "mode": "cli",
    "cli_tests": {
      "passed": 3,
      "failed": 0,
      "results": [...]
    }
  },
  "installed_plugins": [...],
  "duration": 15230,
  "cleanup_scheduled": "2026-02-08T11:30:00Z"
}
```

---

## Key Files

| File | Purpose |
|------|---------|
| `chat/public/builder.js` | Builder UI toggle state |
| `chat/server.js` | Gate MCP injection via env var |
| `opencode/packages/opencode/src/config/config.ts` | Parse OPENCODE_EXTRA_MCP_SERVERS |
| `opencode/packages/opencode/src/mcp/index.ts` | Load and connect MCP servers |
| `opencode/mcp-servers/wp-cli-testing/index.js` | MCP server implementation |
| `chat/external-wp-testing.js` | Core testing logic |

---

## Environment Variables Flow

```
Host Environment
  ↓
Chat Server process.env
  ↓
executionEnv = {...process.env, OPENCODE_EXTRA_MCP_SERVERS: "..."}
  ↓
OpenCode spawned with executionEnv
  ↓
process.env.TEST_WP_HOST → external-wp-testing.js getExternalTestingConfig()
  ↓
SSH connection using TEST_WP_SSH_KEY
  ↓
Commands execute on WordPress server
```

**Required Environment Variables:**
- `TEST_WP_HOST`
- `TEST_WP_SSH_USER`
- `TEST_WP_SSH_KEY`
- `TEST_WP_PATH`
- `TEST_WP_BASE_URL`
- etc. (see EXTERNAL_WP_CLI_TESTING_SETUP.md)

---

## Security Gates

1. **Builder Toggle** - User must explicitly enable
2. **Environment Variable Gate** - Only set when toggle is on
3. **MCP Loading** - Only loads when env var present
4. **SSH Key Auth** - Requires valid key file
5. **Tool Gating** - Tools not available if toggle off

When toggle is OFF:
- `externalTestingEnabled: false` in payload
- `wpCliMcpEnabled = false` on server
- `OPENCODE_EXTRA_MCP_SERVERS` is deleted from env
- MCP server never loads
- Tools never available to AI

---

## Testing The Flow

### 1. Check Environment
```bash
echo $TEST_WP_HOST
echo $TEST_WP_SSH_KEY
```

### 2. Test SSH Connection
```bash
ssh -i $TEST_WP_SSH_KEY $TEST_WP_SSH_USER@$TEST_WP_HOST "wp site list"
```

### 3. Enable Toggle in Builder
- Open Builder
- Enable "External WP CLI testing"
- Verify toggle shows enabled state

### 4. Check Server Logs
```bash
tail -f logs/chat-server.log | grep "WP CLI Testing"
# Should see: "Enabling WP CLI Testing MCP server"
```

### 5. Ask AI to Test
```
"Create a plugin and test it on the external WordPress server"
```

### 6. Verify Subsite Created
```bash
ssh -i $TEST_WP_SSH_KEY $TEST_WP_SSH_USER@$TEST_WP_HOST "wp site list | grep test-"
```

---

## Multisite Architecture

```
WordPress Multisite Network
├── Main Site (/)
│   └── Core WP, Plugins, Themes (shared)
├── Subsite: /test-abc123 (Session 1)
│   ├── Isolated content/options
│   ├── Test Plugin A activated
│   └── Tests running
├── Subsite: /test-def456 (Session 2)
│   ├── Isolated content/options
│   ├── Test Plugin B activated
│   └── Tests running
└── Subsite: /test-xyz789 (Session 3)
    └── ...
```

**Concurrency:** 20+ simultaneous tests via queue system + isolated subsites

**Cleanup:** Each subsite auto-deleted after 1 hour (configurable via `TEST_CLEANUP_DELAY`)
