completed tools and setup
This commit is contained in:
274
opencode/mcp-servers/wp-cli-testing/index.js
Normal file
274
opencode/mcp-servers/wp-cli-testing/index.js
Normal file
@@ -0,0 +1,274 @@
|
||||
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; 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
|
||||
|
||||
if (typeof createExternalWpTester !== "function" || typeof getExternalTestingConfig !== "function") {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text:
|
||||
"wp-cli-testing MCP server misconfigured: could not load chat/external-wp-testing.js exports.",
|
||||
},
|
||||
],
|
||||
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 don’t 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)
|
||||
Reference in New Issue
Block a user