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