big prompt improvement to mcp
This commit is contained in:
370
opencode/mcp-servers/wordpress-validator/index.js
Normal file
370
opencode/mcp-servers/wordpress-validator/index.js
Normal 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)
|
||||
14
opencode/mcp-servers/wordpress-validator/package.json
Normal file
14
opencode/mcp-servers/wordpress-validator/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
@@ -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:**
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user