Skip to main content
Glama
server.ts8.91 kB
/** * WPCS MCP Server - Main Implementation */ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, CallToolResult, TextContent, } from '@modelcontextprotocol/sdk/types.js'; import { execSync } from 'child_process'; import { PhpcsRunner } from './phpcs-runner.js'; import { tools } from './tools.js'; import { getStagedPhpFiles, checkPhpcsInstalled, checkWpcsInstalled, formatPath, } from './utils.js'; import { WpcsCheckResult } from './types.js'; export class WpcsMcpServer { private server: Server; private phpcsRunner: PhpcsRunner; constructor() { this.server = new Server( { name: 'wpcs-mcp-server', version: '1.0.0', }, { capabilities: { tools: {}, }, } ); this.phpcsRunner = new PhpcsRunner('WordPress'); this.setupToolHandlers(); } private setupToolHandlers() { // List available tools this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools, })); // Handle tool calls this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; const toolArgs = (args || {}) as Record<string, unknown>; try { switch (name) { case 'wpcs_check_staged': return await this.checkStagedFiles(toolArgs.working_dir as string | undefined); case 'wpcs_check_file': return await this.checkFile( toolArgs.file_path as string, toolArgs.working_dir as string | undefined ); case 'wpcs_check_directory': return await this.checkDirectory( toolArgs.directory as string, toolArgs.working_dir as string | undefined ); case 'wpcs_fix_file': return await this.fixFile( toolArgs.file_path as string, toolArgs.working_dir as string | undefined ); case 'wpcs_pre_commit': return await this.preCommitWorkflow( toolArgs.working_dir as string | undefined, toolArgs.auto_stage !== false ); default: return this.errorResult(`Unknown tool: ${name}`); } } catch (error: unknown) { const err = error as { message?: string }; return this.errorResult(`Tool execution failed: ${err.message || 'Unknown error'}`); } }); } private async checkStagedFiles(workingDir?: string): Promise<CallToolResult> { const stagedFiles = getStagedPhpFiles(workingDir); if (stagedFiles.length === 0) { return this.successResult('No staged PHP files to check.'); } const filePaths = stagedFiles.map((f) => f.path); const result = await this.phpcsRunner.checkFiles(filePaths, workingDir); return this.formatCheckResult(result, workingDir); } private async checkFile(filePath: string, workingDir?: string): Promise<CallToolResult> { if (!filePath) { return this.errorResult('file_path is required'); } const result = await this.phpcsRunner.check(filePath, workingDir); return this.formatCheckResult(result, workingDir); } private async checkDirectory(directory: string, workingDir?: string): Promise<CallToolResult> { if (!directory) { return this.errorResult('directory is required'); } const result = await this.phpcsRunner.check(directory, workingDir); return this.formatCheckResult(result, workingDir); } private async fixFile(filePath: string, workingDir?: string): Promise<CallToolResult> { if (!filePath) { return this.errorResult('file_path is required'); } const result = await this.phpcsRunner.fix(filePath, workingDir); if (!result.success) { return this.errorResult(`Failed to fix file: ${result.diff || 'Unknown error'}`); } if (!result.fixed) { return this.successResult(`No fixable issues found in ${formatPath(filePath, workingDir)}`); } let message = `Fixed ${formatPath(filePath, workingDir)}\n\n`; if (result.remainingIssues) { if (result.remainingIssues.success) { message += 'All issues resolved. File now passes WordPress coding standards.'; } else { message += `Remaining issues:\n${result.remainingIssues.summary}`; for (const file of result.remainingIssues.files) { for (const msg of file.messages) { message += `\n Line ${msg.line}: [${msg.type}] ${msg.message}`; } } } } return this.successResult(message); } private async preCommitWorkflow( workingDir?: string, autoStage: boolean = true ): Promise<CallToolResult> { // Check prerequisites const phpcsCheck = checkPhpcsInstalled(); if (!phpcsCheck.installed) { return this.errorResult(phpcsCheck.error!); } const wpcsCheck = checkWpcsInstalled(); if (!wpcsCheck.installed) { return this.errorResult(wpcsCheck.error!); } // Get staged PHP files const stagedFiles = getStagedPhpFiles(workingDir); if (stagedFiles.length === 0) { return this.successResult( 'PRE-COMMIT: PASSED\n\nNo staged PHP files to check. Commit can proceed.' ); } const filePaths = stagedFiles.map((f) => f.path); // Run fix and check workflow const { checkResult, fixedFiles, reStageCommand } = await this.phpcsRunner.fixAndCheck( filePaths, workingDir ); let message = ''; // Report fixed files if (fixedFiles.length > 0) { message += `AUTO-FIXED ${fixedFiles.length} file(s):\n`; for (const file of fixedFiles) { message += ` - ${formatPath(file, workingDir)}\n`; } message += '\n'; // Re-stage fixed files if (autoStage && reStageCommand) { try { const options = workingDir ? { cwd: workingDir } : {}; execSync(reStageCommand, options); message += 'Fixed files have been re-staged.\n\n'; } catch { message += `Warning: Could not re-stage files. Run manually:\n ${reStageCommand}\n\n`; } } else if (reStageCommand) { message += `Re-stage fixed files with:\n ${reStageCommand}\n\n`; } } // Report final status if (checkResult.canCommit) { message += 'PRE-COMMIT: PASSED\n\n'; message += checkResult.summary; message += '\n\nCommit can proceed.'; } else { message += 'PRE-COMMIT: BLOCKED\n\n'; message += checkResult.summary; message += '\n\nRemaining issues:\n'; for (const file of checkResult.files) { message += `\n${formatPath(file.path, workingDir)} (${file.errors} errors, ${file.warnings} warnings):\n`; for (const msg of file.messages) { const prefix = msg.type === 'ERROR' ? '[ERROR]' : '[WARNING]'; const fixable = msg.fixable ? ' (fixable)' : ''; message += ` Line ${msg.line}: ${prefix} ${msg.message}${fixable}\n`; } } message += '\n\nFix errors before committing.'; } return { content: [ { type: 'text', text: message, } as TextContent, ], isError: !checkResult.canCommit, }; } private formatCheckResult(result: WpcsCheckResult, workingDir?: string): CallToolResult { if (result.success) { return this.successResult(result.summary); } let message = `${result.summary}\n\n`; for (const file of result.files) { message += `${formatPath(file.path, workingDir)} (${file.errors} errors, ${file.warnings} warnings):\n`; for (const msg of file.messages) { const prefix = msg.type === 'ERROR' ? '[ERROR]' : '[WARNING]'; const fixable = msg.fixable ? ' (fixable)' : ''; message += ` Line ${msg.line}, Col ${msg.column}: ${prefix} ${msg.message}${fixable}\n`; message += ` Source: ${msg.source}\n`; } message += '\n'; } if (result.fixableCount > 0) { message += `\nTip: Run wpcs_fix_file to auto-fix ${result.fixableCount} issue(s).`; } return { content: [{ type: 'text', text: message } as TextContent], isError: !result.canCommit, }; } private successResult(message: string): CallToolResult { return { content: [{ type: 'text', text: message } as TextContent], }; } private errorResult(message: string): CallToolResult { return { content: [{ type: 'text', text: `Error: ${message}` } as TextContent], isError: true, }; } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('WPCS MCP Server running on stdio'); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/vapvarun/wpcs-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server