Files
shopify-ai-backup/opencode/mcp-servers/wp-cli-testing/index.js
2026-02-08 20:15:38 +00:00

282 lines
8.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Server } from "@modelcontextprotocol/sdk/server/index.js"
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js"
import { z } from "zod"
// This MCP server wraps the existing external WP testing implementation used by the chat app.
// It is intentionally kept small and env-configurable so host apps can load it only when needed.
const ToolInput = {
plugin_path: z.string().min(1),
plugin_slug: z.string().optional(),
session_id: z.string().optional(),
test_mode: z.enum(["cli", "visual", "both"]).optional().default("cli"),
required_plugins: z
.array(
z.object({
plugin_slug: z.string().min(1),
source: z.enum(["wordpress.org", "url", "local"]).optional(),
source_url: z.string().optional(),
activate: z.boolean().optional(),
}),
)
.optional(),
test_scenarios: z
.array(
z.object({
name: z.string().optional(),
type: z.string().optional(),
url: z.string().optional(),
selector: z.string().optional(),
expected_text: z.string().optional(),
wp_cli_command: z.string().optional(),
shortcode: z.string().optional(),
hook: z.string().optional(),
ajax_action: z.string().optional(),
method: z.string().optional(),
body: z.string().optional(),
assertions: z
.object({
status_code: z.number().optional(),
contains: z.array(z.string()).optional(),
not_contains: z.array(z.string()).optional(),
wp_cli_success: z.boolean().optional(),
})
.optional(),
}),
)
.optional(),
config_overrides: z.record(z.any()).optional(),
}
function toJsonSchema(schema) {
// zod v4 exposes toJSONSchema under z.* in OpenCode, but in plain node usage
// we keep schema definitions explicit below for MCP.
// This helper exists only for future extension.
void schema
return {
type: "object",
additionalProperties: false,
}
}
const TestToolInputJsonSchema = {
type: "object",
additionalProperties: false,
required: ["plugin_path"],
properties: {
plugin_path: { type: "string", description: "Local filesystem path to plugin root" },
plugin_slug: { type: "string", description: "Plugin slug (defaults to folder name)" },
session_id: { type: "string", description: "Optional session id for isolation" },
test_mode: {
type: "string",
enum: ["cli", "visual", "both"],
description: "Testing mode (only 'cli' is implemented in this server)",
},
required_plugins: {
type: "array",
description: "Plugins to install/activate before running scenarios",
items: {
type: "object",
additionalProperties: false,
required: ["plugin_slug"],
properties: {
plugin_slug: { type: "string" },
source: { type: "string", enum: ["wordpress.org", "url", "local"] },
source_url: { type: "string" },
activate: { type: "boolean" },
},
},
},
test_scenarios: {
type: "array",
description: "Scenario list; if omitted, defaults are generated",
items: {
type: "object",
additionalProperties: true,
},
},
config_overrides: {
type: "object",
description: "Overrides for external WP testing configuration (see docs/env vars)",
additionalProperties: true,
},
},
}
const server = new Server(
{
name: "wp-cli-testing",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
},
)
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "test_plugin_external_wp",
description:
"Run WP-CLI based verification against an externally hosted WordPress test site (over SSH).",
inputSchema: TestToolInputJsonSchema,
},
{
name: "external_wp_testing_config",
description:
"Return the resolved external WP testing config (from env vars) and highlight missing/invalid settings.",
inputSchema: {
type: "object",
additionalProperties: false,
properties: {
config_overrides: {
type: "object",
additionalProperties: true,
description: "Optional config overrides merged on top of env defaults",
},
},
},
},
],
}
})
server.setRequestHandler(CallToolRequestSchema, async (req) => {
const toolName = req.params.name
const args = (req.params.arguments ?? {})
// Lazy-load to avoid paying cost unless tool is actually invoked.
// 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") {
throw new Error("Required exports not found on module")
}
} catch (error) {
return {
content: [
{
type: "text",
text: `wp-cli-testing MCP server error: Failed to load chat/external-wp-testing.js - ${error.message}`,
},
],
isError: true,
}
}
if (toolName === "external_wp_testing_config") {
const overrides =
args && typeof args === "object" && (args.config_overrides && typeof args.config_overrides === "object")
? args.config_overrides
: {}
const config = getExternalTestingConfig(overrides)
const missing = []
if (!config.wpHost) missing.push("TEST_WP_HOST (or EXTERNAL_WP_HOST)")
if (!config.wpSshUser) missing.push("TEST_WP_SSH_USER (or EXTERNAL_WP_SSH_USER)")
if (!config.wpSshKey) missing.push("TEST_WP_SSH_KEY (or EXTERNAL_WP_SSH_KEY)")
const safeConfig = {
...config,
// avoid leaking filesystem layout too much; keep key path but dont include any private key contents.
wpSshKey: config.wpSshKey,
}
return {
content: [
{
type: "text",
text: JSON.stringify(
{
ok: missing.length === 0,
missing,
config: safeConfig,
},
null,
2,
),
},
],
}
}
if (toolName === "test_plugin_external_wp") {
const parsed = z
.object(ToolInput)
.safeParse(args && typeof args === "object" ? args : {})
if (!parsed.success) {
return {
content: [{ type: "text", text: JSON.stringify({ ok: false, error: parsed.error.message }, null, 2) }],
isError: true,
}
}
if (parsed.data.test_mode !== "cli") {
return {
content: [
{
type: "text",
text: JSON.stringify(
{
ok: false,
error:
"Only test_mode='cli' is implemented by this MCP server. Use cli mode or extend the server.",
},
null,
2,
),
},
],
isError: true,
}
}
const tester = createExternalWpTester({})
const result = await tester.runTest(
{
plugin_path: parsed.data.plugin_path,
plugin_slug: parsed.data.plugin_slug,
session_id: parsed.data.session_id,
required_plugins: parsed.data.required_plugins ?? [],
test_scenarios: parsed.data.test_scenarios ?? [],
test_mode: "cli",
},
{
configOverrides:
parsed.data.config_overrides && typeof parsed.data.config_overrides === "object"
? parsed.data.config_overrides
: {},
},
)
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
}
}
return {
content: [{ type: "text", text: `Unknown tool: ${toolName}` }],
isError: true,
}
})
const transport = new StdioServerTransport()
await server.connect(transport)