#!/usr/bin/env node
/**
* Magento Coding Standards MCP Server
*
* An MCP server that provides Magento 2 coding standards knowledge,
* enabling Claude to write compliant Magento code naturally ("vibe coding").
*
* Tools:
* - get_magento_pattern: Get the correct Magento way to do something
* - validate_code: Validate code against Magento standards
* - check_security: Security-focused validation
* - explain_rule: Explain a specific coding rule
* - list_rules: List all available rules
* - get_rules_summary: Get summary of rules by category
*/
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
ErrorCode,
McpError,
} from '@modelcontextprotocol/sdk/types.js';
import { z } from 'zod';
import {
getMagentoPattern,
validateCode,
checkSecurity,
explainRule,
searchRules,
listRules,
getRulesSummary,
manageTheme,
} from './tools/index.js';
// === Zod Schemas for input validation ===
const GetMagentoPatternSchema = z.object({
task: z.string().min(1, 'Task description is required'),
});
const ValidateCodeSchema = z.object({
code: z.string().min(1, 'Code is required'),
fileType: z.enum(['php', 'phtml', 'js', 'javascript', 'less', 'css']),
});
const CheckSecuritySchema = z.object({
code: z.string().min(1, 'Code is required'),
});
const ExplainRuleSchema = z.object({
ruleName: z.string().min(1, 'Rule name is required'),
});
const ListRulesSchema = z.object({
category: z.string().optional(),
minSeverity: z.number().min(1).max(10).optional(),
searchTerm: z.string().optional(),
});
const ManageThemeSchema = z.object({
action: z.enum(['list', 'set', 'clear', 'info']),
themeId: z.string().optional(),
});
// Create server instance
const server = new Server(
{
name: 'magento-coding-standard-mcp',
version: '1.2.0',
},
{
capabilities: {
tools: {},
},
}
);
// Define all available tools
const TOOLS = [
{
name: 'get_magento_pattern',
description: 'Get the correct Magento 2 way to accomplish a task. Returns the proper pattern, code example, and what to avoid. Use this for "vibe coding" - writing Magento-compliant code naturally.',
inputSchema: {
type: 'object' as const,
properties: {
task: {
type: 'string',
description: 'The task you want to accomplish (e.g., "read a file", "escape HTML", "validate email", "create viewmodel")'
}
},
required: ['task']
}
},
{
name: 'validate_code',
description: 'Validate code against Magento 2 coding standards. Returns violations with severity, line numbers, and fix suggestions.',
inputSchema: {
type: 'object' as const,
properties: {
code: {
type: 'string',
description: 'The code to validate'
},
fileType: {
type: 'string',
enum: ['php', 'phtml', 'js', 'less', 'css'],
description: 'Type of file: php (PHP classes), phtml (templates), js (JavaScript), less/css (stylesheets)'
}
},
required: ['code', 'fileType']
}
},
{
name: 'check_security',
description: 'Perform security-focused validation on code. Checks for XSS vulnerabilities, SQL injection risks, insecure functions, and other security issues.',
inputSchema: {
type: 'object' as const,
properties: {
code: {
type: 'string',
description: 'The code to check for security issues'
}
},
required: ['code']
}
},
{
name: 'explain_rule',
description: 'Get detailed explanation of a Magento coding standard rule including reasoning, bad/good examples, and fix suggestions.',
inputSchema: {
type: 'object' as const,
properties: {
ruleName: {
type: 'string',
description: 'The rule name to explain (e.g., "Magento2.Security.XssTemplate", "XssTemplate", "DiscouragedFunction")'
}
},
required: ['ruleName']
}
},
{
name: 'list_rules',
description: 'List all Magento coding standard rules. Can filter by category (Security, Legacy, PHP, Functions, Templates, Less, etc.), minimum severity (1-10), or search term.',
inputSchema: {
type: 'object' as const,
properties: {
category: {
type: 'string',
description: 'Filter by category (Security, Legacy, PHP, Functions, Classes, Templates, Less, etc.)'
},
minSeverity: {
type: 'number',
description: 'Minimum severity level (1-10, where 10 is most critical)'
},
searchTerm: {
type: 'string',
description: 'Search in rule names and descriptions'
}
}
}
},
{
name: 'get_rules_summary',
description: 'Get a summary of all Magento coding standard rules grouped by category, showing counts of errors and warnings.',
inputSchema: {
type: 'object' as const,
properties: {}
}
},
{
name: 'manage_theme',
description: 'Manage theme-specific coding standards. Themes layer additional rules on top of base Magento standards. Built-in presets: hyva (Alpine.js + TailwindCSS), luma (jQuery + RequireJS + LESS), breeze (Vanilla JS), porto (Luma-based + Porto widgets). Custom themes can be added as JSON files.',
inputSchema: {
type: 'object' as const,
properties: {
action: {
type: 'string',
enum: ['list', 'set', 'clear', 'info'],
description: 'Action to perform: "list" shows available themes, "set" activates a theme, "clear" deactivates theme, "info" shows theme details'
},
themeId: {
type: 'string',
description: 'Theme ID to set or get info for (e.g., "hyva", "luma", "breeze", "porto", or custom theme ID)'
}
},
required: ['action']
}
}
];
// Handle list tools request
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: TOOLS
}));
// Helper to create error response
function errorResponse(message: string) {
return {
content: [{ type: 'text' as const, text: JSON.stringify({ error: true, message }) }],
isError: true as const
};
}
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'get_magento_pattern': {
const parsed = GetMagentoPatternSchema.safeParse(args);
if (!parsed.success) {
return errorResponse(`Invalid input: ${parsed.error.issues.map(i => i.message).join(', ')}`);
}
const result = getMagentoPattern(parsed.data.task);
return {
content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }]
};
}
case 'validate_code': {
const parsed = ValidateCodeSchema.safeParse(args);
if (!parsed.success) {
return errorResponse(`Invalid input: ${parsed.error.issues.map(i => i.message).join(', ')}`);
}
const result = validateCode(parsed.data.code, parsed.data.fileType);
return {
content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }]
};
}
case 'check_security': {
const parsed = CheckSecuritySchema.safeParse(args);
if (!parsed.success) {
return errorResponse(`Invalid input: ${parsed.error.issues.map(i => i.message).join(', ')}`);
}
const result = checkSecurity(parsed.data.code);
return {
content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }]
};
}
case 'explain_rule': {
const parsed = ExplainRuleSchema.safeParse(args);
if (!parsed.success) {
return errorResponse(`Invalid input: ${parsed.error.issues.map(i => i.message).join(', ')}`);
}
const explanation = explainRule(parsed.data.ruleName);
if (!explanation) {
const suggestions = searchRules(parsed.data.ruleName);
return {
content: [{
type: 'text' as const,
text: JSON.stringify({
error: 'Rule not found',
searchTerm: parsed.data.ruleName,
suggestions: suggestions.slice(0, 5),
hint: 'Did you mean one of these rules?'
}, null, 2)
}]
};
}
return {
content: [{ type: 'text' as const, text: JSON.stringify(explanation, null, 2) }]
};
}
case 'list_rules': {
const parsed = ListRulesSchema.safeParse(args);
if (!parsed.success) {
return errorResponse(`Invalid input: ${parsed.error.issues.map(i => i.message).join(', ')}`);
}
const result = listRules(parsed.data);
return {
content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }]
};
}
case 'get_rules_summary': {
const summary = getRulesSummary();
return {
content: [{ type: 'text' as const, text: JSON.stringify(summary, null, 2) }]
};
}
case 'manage_theme': {
const parsed = ManageThemeSchema.safeParse(args);
if (!parsed.success) {
return errorResponse(`Invalid input: ${parsed.error.issues.map(i => i.message).join(', ')}`);
}
const result = manageTheme(parsed.data.action, parsed.data.themeId);
return {
content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }]
};
}
default:
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
}
} catch (error) {
if (error instanceof McpError) {
throw error;
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return {
content: [{
type: 'text' as const,
text: JSON.stringify({ error: true, message: errorMessage, tool: name })
}],
isError: true as const
};
}
});
// Start server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('Magento Coding Standards MCP Server v1.2.0 running on stdio');
console.error('Available tools: get_magento_pattern, validate_code, check_security, explain_rule, list_rules, get_rules_summary, manage_theme');
}
main().catch((error) => {
console.error('Server error:', error);
process.exit(1);
});