big prompt improvement to mcp

This commit is contained in:
southseact-3d
2026-02-20 18:17:36 +00:00
parent d3580b091a
commit dfc4a0d2a9
9 changed files with 1120 additions and 59 deletions

View File

@@ -0,0 +1,370 @@
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"
import { spawn } from "child_process"
import path from "path"
import { fileURLToPath } from "url"
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const DEFAULT_TIMEOUT = 120000
const ValidateToolInputJsonSchema = {
type: "object",
additionalProperties: false,
required: ["plugin_path"],
properties: {
plugin_path: {
type: "string",
description: "Absolute or relative path to the WordPress plugin directory to validate"
},
verbose: {
type: "boolean",
description: "Include full script output in addition to structured results (default: false)"
},
},
}
const server = new Server(
{
name: "wordpress-validator",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
},
)
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "validate_wordpress_plugin",
description: `Validates a WordPress plugin for security vulnerabilities, coding standards violations, and common runtime errors. Runs comprehensive static analysis including:
- Forbidden/dangerous function detection
- SQL injection pattern detection
- XSS and input sanitization checks
- Nonce and capability verification
- PHP syntax validation
- Duplicate class/function detection
- Class loading validation
- File path security checks
- WordPress deprecated function detection
CRITICAL: This tool MUST be called before completing ANY WordPress plugin work. Do NOT mark work complete until validation passes.
Returns structured results with severity categorization. Use verbose=true only if you need full output.`,
inputSchema: ValidateToolInputJsonSchema,
},
],
}
})
server.setRequestHandler(CallToolRequestSchema, async (req) => {
const toolName = req.params.name
const args = (req.params.arguments ?? {})
if (toolName === "validate_wordpress_plugin") {
const parsed = z
.object({
plugin_path: z.string().min(1),
verbose: z.boolean().optional().default(false),
})
.safeParse(args && typeof args === "object" ? args : {})
if (!parsed.success) {
return {
content: [{
type: "text",
text: JSON.stringify({
passed: false,
errorCount: 1,
warningCount: 0,
summary: `Validation failed: ${parsed.error.message}`,
issues: [{ severity: "error", category: "input", message: parsed.error.message }]
}, null, 2)
}],
isError: true,
}
}
const pluginPath = parsed.data.plugin_path
const verbose = parsed.data.verbose || false
const resolvedPath = path.isAbsolute(pluginPath)
? pluginPath
: path.resolve(process.cwd(), pluginPath)
// Check if directory exists
try {
const fs = await import("fs/promises")
const stat = await fs.stat(resolvedPath)
if (!stat.isDirectory()) {
return {
content: [{
type: "text",
text: JSON.stringify({
passed: false,
errorCount: 1,
warningCount: 0,
summary: `Validation failed: Not a directory: ${resolvedPath}`,
issues: [{ severity: "error", category: "path", message: "Plugin path must be a directory" }]
}, null, 2)
}],
isError: true,
}
}
} catch (err) {
return {
content: [{
type: "text",
text: JSON.stringify({
passed: false,
errorCount: 1,
warningCount: 0,
summary: `Validation failed: Directory not found: ${resolvedPath}`,
issues: [{ severity: "error", category: "path", message: `Plugin directory does not exist: ${resolvedPath}` }]
}, null, 2)
}],
isError: true,
}
}
// Find the validation script
const scriptDir = path.resolve(__dirname, "../../../../scripts")
const bashScript = path.join(scriptDir, "validate-wordpress-plugin.sh")
// Check if script exists
try {
const fs = await import("fs/promises")
await fs.access(bashScript)
} catch {
return {
content: [{
type: "text",
text: JSON.stringify({
passed: false,
errorCount: 1,
warningCount: 0,
summary: "Validation failed: Validation script not found",
issues: [{ severity: "error", category: "setup", message: `Validation script not found: ${bashScript}` }]
}, null, 2)
}],
isError: true,
}
}
// Run the validation script
const output = await runValidationScript(bashScript, resolvedPath, DEFAULT_TIMEOUT)
// Parse the validation output
const result = parseValidationOutput(output)
// Build concise summary
const summaryParts = []
if (result.errorCount > 0) {
summaryParts.push(`${result.errorCount} errors`)
result.passed = false
}
if (result.warningCount > 0) {
summaryParts.push(`${result.warningCount} warnings`)
}
if (result.errorCount === 0 && result.warningCount === 0) {
summaryParts.push("SUCCESS")
result.passed = true
}
result.summary = result.passed
? "All validation checks passed"
: `Validation failed: ${summaryParts.join(", ")}`
// Build output string
let outputText = `<validation_result>\n`
outputText += `<summary>${result.summary}</summary>\n\n`
const errors = result.issues.filter(i => i.severity === "error")
const warnings = result.issues.filter(i => i.severity === "warning")
if (errors.length > 0) {
outputText += `<errors count="${errors.length}">\n`
errors.slice(0, 10).forEach(issue => {
outputText += ` [${issue.category}] ${issue.file ? `${issue.file}:${issue.line} - ` : ""}${issue.message}\n`
})
if (errors.length > 10) {
outputText += ` ... and ${errors.length - 10} more errors\n`
}
outputText += `</errors>\n\n`
}
if (warnings.length > 0) {
outputText += `<warnings count="${warnings.length}">\n`
warnings.slice(0, 5).forEach(issue => {
outputText += ` [${issue.category}] ${issue.file ? `${issue.file}:${issue.line} - ` : ""}${issue.message}\n`
})
if (warnings.length > 5) {
outputText += ` ... and ${warnings.length - 5} more warnings\n`
}
outputText += `</warnings>\n`
}
outputText += `</validation_result>`
if (verbose) {
outputText += `\n\n<raw_output>\n${output.slice(-3000)}\n</raw_output>`
}
return {
content: [{ type: "text", text: outputText }],
}
}
return {
content: [{ type: "text", text: `Unknown tool: ${toolName}` }],
isError: true,
}
})
async function runValidationScript(scriptPath, pluginPath, timeout) {
return new Promise((resolve, reject) => {
const proc = spawn("bash", [scriptPath, pluginPath], {
stdio: ["ignore", "pipe", "pipe"],
detached: process.platform !== "win32",
})
let output = ""
proc.stdout?.on("data", (chunk) => { output += chunk.toString() })
proc.stderr?.on("data", (chunk) => { output += chunk.toString() })
let timedOut = false
const timeoutTimer = setTimeout(() => {
timedOut = true
proc.kill("SIGTERM")
setTimeout(() => proc.kill("SIGKILL"), 5000)
}, timeout)
proc.on("exit", () => {
clearTimeout(timeoutTimer)
if (timedOut) {
output += "\n\n[Validation timed out after " + timeout + "ms]"
}
resolve(output)
})
proc.on("error", (err) => {
clearTimeout(timeoutTimer)
reject(err)
})
})
}
function parseValidationOutput(output) {
const result = {
passed: true,
errorCount: 0,
warningCount: 0,
issues: [],
summary: "",
}
const lines = output.split("\n")
for (const line of lines) {
const trimmed = line.trim()
if (trimmed.includes("✗") || trimmed.includes("FATAL") || trimmed.includes("ERROR")) {
const issue = parseIssueLine(trimmed, "error")
if (issue && !isDuplicateIssue(result.issues, issue)) {
result.issues.push(issue)
result.errorCount++
}
}
if (trimmed.includes("⚠") || trimmed.includes("WARNING")) {
const issue = parseIssueLine(trimmed, "warning")
if (issue && !isDuplicateIssue(result.issues, issue)) {
result.issues.push(issue)
result.warningCount++
}
}
}
return result
}
function parseIssueLine(line, severity) {
const cleanLine = line.replace(/\x1b\[[0-9;]*m/g, "")
const fileMatch = cleanLine.match(/([\w\/\\.-]+\.php):(\d+)/)
const file = fileMatch ? fileMatch[1] : undefined
const lineNum = fileMatch ? parseInt(fileMatch[2], 10) : undefined
const categoryMatch = cleanLine.match(/\[\d+\/\d+\]\s+Checking\s+(?:for\s+)?(.+?)\.\.\./i)
const category = categoryMatch ? categoryMatch[1] : extractCategory(cleanLine)
let message = cleanLine
.replace(/^\s*[✗⚠]\s*/, "")
.replace(/^\s*FATAL:\s*/i, "")
.replace(/^\s*ERROR:\s*/i, "")
.replace(/^\s*WARNING:\s*/i, "")
.replace(/^\s*SECURITY\s+RISK\s+in\s+/, "")
.replace(/^\s*SQL\s+INJECTION\s+RISK\s+in\s+/, "")
.replace(/^\s*Found\s+/, "")
.trim()
if (!message || message.length < 10) {
return null
}
return {
severity,
category,
file,
line: lineNum,
message: message.substring(0, 200),
}
}
function extractCategory(line) {
if (line.includes("forbidden") || line.includes("eval") || line.includes("exec")) {
return "security"
}
if (line.includes("SQL") || line.includes("wpdb")) {
return "sql-injection"
}
if (line.includes("XSS") || line.includes("sanitize") || line.includes("escape")) {
return "xss"
}
if (line.includes("syntax") || line.includes("parse")) {
return "syntax"
}
if (line.includes("duplicate") || line.includes("redeclare")) {
return "duplicates"
}
if (line.includes("missing") || line.includes("undefined")) {
return "undefined"
}
if (line.includes("class") || line.includes("function")) {
return "structure"
}
return "general"
}
function isDuplicateIssue(issues, newIssue) {
return issues.some(i =>
i.message === newIssue.message &&
i.file === newIssue.file &&
i.line === newIssue.line
)
}
const transport = new StdioServerTransport()
await server.connect(transport)

View File

@@ -0,0 +1,14 @@
{
"name": "wordpress-validator",
"version": "1.0.0",
"type": "module",
"description": "MCP server for WordPress plugin validation",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.0",
"zod": "^3.22.0"
}
}

View File

@@ -14,26 +14,30 @@ CRITICAL SECURITY REQUIREMENTS
You must never try to or attempt to or ask the user for permission to edit files outside of the workspace you are editing in.
CRITICAL VALIDATION REQUIREMENTS - YOU MUST USE THE MCP TOOL:
CRITICAL VALIDATION REQUIREMENTS - YOU MUST USE VALIDATION TOOLS:
You have access to a built-in MCP tool called `wordpress-validator:validate_wordpress_plugin` that runs comprehensive validation checks. This tool MUST be called before completing ANY WordPress plugin work.
You have access to validation tools for WordPress plugins. One of these tools MUST be called before completing ANY WordPress plugin work.
AVAILABLE VALIDATION TOOLS (use either):
1. MCP Tool: `wordpress-validator:validate_wordpress_plugin` - Preferred when available
2. Built-in Tool: `validate_wordpress_plugin` - Fallback if MCP not available
MANDATORY VALIDATION WORKFLOW:
1. After creating or modifying any WordPress plugin files, you MUST call the MCP validation tool
1. After creating or modifying any WordPress plugin files, you MUST call a validation tool
2. Use the tool with: `{ "plugin_path": "/absolute/path/to/plugin" }`
3. The tool will return a JSON result with a summary
4. If validation passes: You will see "All validation checks passed"
4. If validation passes: You will see "All validation checks passed"
5. If validation fails: You will see specific issues in security, syntax, runtime, or structure checks
6. Do NOT mark the work complete until you see "✓ All validation checks passed"
6. Do NOT mark the work complete until validation passes
7. If validation fails, fix all reported issues and re-run the tool until it passes
The MCP tool performs the following checks:
The validation tools perform the following checks:
- Security: Forbidden functions, SQL injection patterns, XSS vulnerabilities, nonce/capability checks
- Syntax: PHP syntax validation, coding standards, undefined variables
- Runtime: Duplicate declarations, missing includes, undefined classes/functions
- Structure: Plugin headers, file organization, proper WordPress patterns
CRITICAL: Do not use the old bash scripts directly. Always use the MCP tool for validation.
CRITICAL: Always use one of the validation tools before marking work complete.
STYLING REQUIREMENTS (CRITICAL):
9. **Admin Panel Styling:**

View File

@@ -30,6 +30,13 @@ export namespace SystemPrompt {
return wordpressDetectionCache
}
// Check for forced WordPress mode via environment variable
if (process.env.OPENCODE_FORCE_WORDPRESS === '1') {
wordpressDetectionCache = true
wordpressDetectionCacheTime = now
return true
}
const cwd = Instance.directory
if (!cwd) {
return false