complered tools 2 and guide
This commit is contained in:
398
EXTERNAL_WP_CLI_TESTING_IMPLEMENTATION.md
Normal file
398
EXTERNAL_WP_CLI_TESTING_IMPLEMENTATION.md
Normal file
@@ -0,0 +1,398 @@
|
||||
# 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`)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -128,12 +128,22 @@ async function createSubsite(config, sessionId) {
|
||||
}
|
||||
|
||||
const url = resolveSubsiteUrl(config, slug);
|
||||
const configureCmd = buildWpCliCommand(
|
||||
|
||||
// Configure permalink structure
|
||||
const permalinkCmd = buildWpCliCommand(
|
||||
config,
|
||||
url,
|
||||
'option update permalink_structure "/%postname%/" && wp rewrite flush'
|
||||
'option update permalink_structure "/%postname%/"'
|
||||
);
|
||||
await runSshCommand(config, configureCmd, { timeout: config.testTimeoutMs });
|
||||
await runSshCommand(config, permalinkCmd, { timeout: config.testTimeoutMs });
|
||||
|
||||
// Flush rewrite rules
|
||||
const rewriteCmd = buildWpCliCommand(
|
||||
config,
|
||||
url,
|
||||
'rewrite flush'
|
||||
);
|
||||
await runSshCommand(config, rewriteCmd, { timeout: config.testTimeoutMs });
|
||||
|
||||
return { slug, url, adminUrl: `${url.replace(/\/$/, '')}/wp-admin` };
|
||||
}
|
||||
@@ -460,7 +470,11 @@ function createExternalWpTester(options = {}) {
|
||||
|
||||
const installedPlugins = await installRequiredPlugins(config, subsite.url, requiredPlugins);
|
||||
if (pluginSlug) {
|
||||
await runSshCommand(config, buildWpCliCommand(config, subsite.url, `plugin activate ${pluginSlug}`));
|
||||
const activateCmd = buildWpCliCommand(config, subsite.url, `plugin activate ${pluginSlug}`);
|
||||
const activateRes = await runSshCommand(config, activateCmd, { timeout: config.testTimeoutMs });
|
||||
if (activateRes.code !== 0) {
|
||||
errors.push(`Failed to activate plugin ${pluginSlug}: ${activateRes.stderr || activateRes.stdout}`);
|
||||
}
|
||||
}
|
||||
|
||||
const scenarios = normalizeTestScenarios(input, pluginSlug, config);
|
||||
|
||||
106
opencode/mcp-servers/wp-cli-testing/README.md
Normal file
106
opencode/mcp-servers/wp-cli-testing/README.md
Normal file
@@ -0,0 +1,106 @@
|
||||
# WP-CLI Testing MCP Server
|
||||
|
||||
This MCP server provides tools for testing WordPress plugins on an external WordPress multisite installation via WP-CLI over SSH.
|
||||
|
||||
## Tools Provided
|
||||
|
||||
### 1. test_plugin_external_wp
|
||||
|
||||
Runs automated CLI tests on an external WordPress installation with full isolation via multisite subsites.
|
||||
|
||||
**Features:**
|
||||
- Automatic subsite provisioning (`wp site create`)
|
||||
- Plugin upload and activation
|
||||
- Dependency installation (WooCommerce, ACF, etc.)
|
||||
- WP-CLI based test execution
|
||||
- Automatic cleanup after delay
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
{
|
||||
"plugin_path": "/workspace/my-plugin",
|
||||
"plugin_slug": "my-plugin",
|
||||
"test_mode": "cli",
|
||||
"required_plugins": [
|
||||
{"plugin_slug": "woocommerce", "activate": true}
|
||||
],
|
||||
"test_scenarios": [
|
||||
{
|
||||
"name": "Plugin activates",
|
||||
"type": "custom",
|
||||
"wp_cli_command": "plugin activate my-plugin",
|
||||
"assertions": {"wp_cli_success": true}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 2. external_wp_testing_config
|
||||
|
||||
Returns the resolved configuration and validates required environment variables.
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
{}
|
||||
```
|
||||
|
||||
**Returns:**
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"missing": [],
|
||||
"config": {
|
||||
"wpHost": "wp-test.example.com",
|
||||
"wpSshUser": "wordpress",
|
||||
"enableMultisite": true,
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## How It's Loaded
|
||||
|
||||
This server is **NOT** loaded automatically. It's injected dynamically by the chat server only when:
|
||||
1. The Builder "External WP CLI testing" toggle is enabled
|
||||
2. The chat server sets `OPENCODE_EXTRA_MCP_SERVERS` environment variable
|
||||
3. OpenCode reads that variable and adds this MCP server to its configuration
|
||||
|
||||
## Configuration
|
||||
|
||||
All configuration is via environment variables (see EXTERNAL_WP_CLI_TESTING_SETUP.md):
|
||||
|
||||
- `TEST_WP_HOST` - WordPress server hostname
|
||||
- `TEST_WP_SSH_USER` - SSH username
|
||||
- `TEST_WP_SSH_KEY` - Path to SSH private key
|
||||
- `TEST_WP_PATH` - WordPress installation path
|
||||
- `TEST_WP_BASE_URL` - Base URL of site
|
||||
- `TEST_WP_MULTISITE` - Enable multisite (default: true)
|
||||
- etc.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
OpenCode (when toggle enabled)
|
||||
↓ loads MCP server via OPENCODE_EXTRA_MCP_SERVERS
|
||||
wp-cli-testing MCP Server
|
||||
↓ imports ../../../chat/external-wp-testing.js
|
||||
External WP Testing Module
|
||||
↓ SSH connection
|
||||
WordPress Multisite Test Server
|
||||
↓ creates subsite
|
||||
Isolated Test Environment
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
This MCP server relies on:
|
||||
- `@modelcontextprotocol/sdk` (provided by OpenCode)
|
||||
- `zod` (provided by OpenCode)
|
||||
- `../../../chat/external-wp-testing.js` (CommonJS module)
|
||||
|
||||
## Notes
|
||||
|
||||
- Only `test_mode: "cli"` is implemented (visual mode not yet supported)
|
||||
- Requires WordPress multisite configured on external server
|
||||
- All plugin operations happen on isolated subsites
|
||||
- Automatic cleanup scheduled after test completion
|
||||
@@ -153,19 +153,26 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
||||
const args = (req.params.arguments ?? {})
|
||||
|
||||
// Lazy-load to avoid paying cost unless tool is actually invoked.
|
||||
// Note: external-wp-testing.js is CommonJS; import default gives module.exports.
|
||||
const externalTesting = await import("../../../chat/external-wp-testing.js")
|
||||
const mod = externalTesting.default ?? externalTesting
|
||||
const createExternalWpTester = mod?.createExternalWpTester
|
||||
const getExternalTestingConfig = mod?.getExternalTestingConfig
|
||||
// Note: external-wp-testing.js is CommonJS; when imported from ESM,
|
||||
// exports are on .default for Node.js CommonJS modules
|
||||
let createExternalWpTester
|
||||
let getExternalTestingConfig
|
||||
|
||||
try {
|
||||
const externalTesting = await import("../../../chat/external-wp-testing.js")
|
||||
const mod = externalTesting.default ?? externalTesting
|
||||
createExternalWpTester = mod?.createExternalWpTester
|
||||
getExternalTestingConfig = mod?.getExternalTestingConfig
|
||||
|
||||
if (typeof createExternalWpTester !== "function" || typeof getExternalTestingConfig !== "function") {
|
||||
if (typeof createExternalWpTester !== "function" || typeof getExternalTestingConfig !== "function") {
|
||||
throw new Error("Required exports not found on module")
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text:
|
||||
"wp-cli-testing MCP server misconfigured: could not load chat/external-wp-testing.js exports.",
|
||||
text: `wp-cli-testing MCP server error: Failed to load chat/external-wp-testing.js - ${error.message}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
|
||||
Reference in New Issue
Block a user