complered tools 2 and guide

This commit is contained in:
southseact-3d
2026-02-08 20:15:38 +00:00
parent bd6817f697
commit b95efaebea
5 changed files with 1599 additions and 84 deletions

View 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

View File

@@ -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);

View 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

View File

@@ -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.
// 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
const createExternalWpTester = mod?.createExternalWpTester
const getExternalTestingConfig = mod?.getExternalTestingConfig
createExternalWpTester = mod?.createExternalWpTester
getExternalTestingConfig = mod?.getExternalTestingConfig
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,