Skip to main content
Glama
code-practices.js•9.96 kB
import { ESLint } from 'eslint'; import { validateCode, sanitizeFilename } from '../utils/validation.js'; import { withTimeout, LINT_TIMEOUT_MS } from '../utils/timeout.js'; import { eslintConfig } from '../config/eslint-config.js'; import { productionEslintConfig } from '../config/production-eslint-config.js'; // ESLint instances will be created per request based on production parameter /** * Tool definition for check_code_practices */ export const codePracticesTool = { name: 'check_code_practices', description: 'Analyzes a JavaScript code snippet for best practices using ESLint with pagination and filtering.', inputSchema: { type: 'object', properties: { code: { type: 'string', description: 'The JavaScript code snippet to analyze (max 100KB)', }, filename: { type: 'string', description: 'Optional filename for better context (e.g., \'example.js\')', }, production: { type: 'boolean', description: 'Enable stricter production code checking (default: false)', default: false, }, // Pagination parameters limit: { type: 'number', description: 'Maximum number of issues to return (default: 50, max: 500)', default: 50, minimum: 1, maximum: 500, }, offset: { type: 'number', description: 'Starting index for pagination (default: 0)', default: 0, minimum: 0, }, // Filtering parameters severity: { type: 'string', description: 'Filter by severity level', enum: ['error', 'warning', 'all'], default: 'all', }, ruleCategory: { type: 'string', description: 'Filter by rule category', enum: ['possible-errors', 'best-practices', 'stylistic-issues', 'es6', 'all'], default: 'all', }, // Sorting parameters sortBy: { type: 'string', description: 'Field to sort by', enum: ['line', 'severity', 'ruleId', 'message'], default: 'line', }, sortOrder: { type: 'string', description: 'Sort order', enum: ['asc', 'desc'], default: 'asc', }, }, required: ['code'], }, }; /** * Handles the check_code_practices tool call * @param {Object} args - Tool arguments * @param {string} args.code - Code to analyze * @param {string} args.filename - Optional filename * @param {boolean} args.production - Enable production mode for stricter checking * @returns {Object} MCP response */ export async function handleCodePractices(args) { const { code, filename, production = false, limit = 50, offset = 0, severity = 'all', ruleCategory = 'all', sortBy = 'line', sortOrder = 'asc' } = args.params || args; // Validate input const validation = validateCode(code); if (!validation.valid) { return validation.error; } const safeFilename = sanitizeFilename(filename); // Create ESLint instance with appropriate config const config = production ? productionEslintConfig : eslintConfig; const eslint = new ESLint(config); try { const lintPromise = eslint.lintText(code, { filePath: safeFilename }); const results = await withTimeout(lintPromise, LINT_TIMEOUT_MS, 'Linting'); const result = results[0]; const allMessages = result?.messages || []; const errorCount = result?.errorCount || 0; const warningCount = result?.warningCount || 0; if (allMessages.length === 0) { const modeText = production ? ' (production mode)' : ''; return { content: [{ type: 'text', text: `āœ… The code follows best practices with no issues detected${modeText}.`, }], }; } // Apply filtering let filteredMessages = filterMessages(allMessages, severity, ruleCategory); // Apply sorting filteredMessages = sortMessages(filteredMessages, sortBy, sortOrder); // Apply pagination const totalIssues = filteredMessages.length; const paginatedMessages = filteredMessages.slice(offset, offset + limit); const hasMore = offset + limit < totalIssues; const modeText = production ? ' (Production Mode)' : ''; const response = { summary: { totalIssues: allMessages.length, totalErrors: errorCount, totalWarnings: warningCount, filteredIssues: totalIssues, returnedIssues: paginatedMessages.length, limit, offset, hasMore, sortBy, sortOrder, filters: { severity, ruleCategory } }, issues: paginatedMessages.map(msg => ({ line: msg.line, column: msg.column, severity: msg.severity === 2 ? 'error' : 'warning', message: msg.message, ruleId: msg.ruleId, ruleCategory: getRuleCategory(msg.ruleId), source: msg.source })) }; if (hasMore) { response.summary.nextOffset = offset + limit; } let output = `šŸ” Code Analysis Results${modeText}:\n`; output += `Found ${errorCount} error(s) and ${warningCount} warning(s) total\n`; output += `Showing ${paginatedMessages.length} of ${totalIssues} filtered issues\n\n`; if (paginatedMessages.length > 0) { const errors = paginatedMessages.filter(msg => msg.severity === 2); const warnings = paginatedMessages.filter(msg => msg.severity === 1); if (errors.length > 0) { output += 'āŒ Errors (must fix):\n'; errors.forEach(msg => { output += ` Line ${msg.line}:${msg.column} - ${msg.message} (${msg.ruleId})\n`; }); output += '\n'; } if (warnings.length > 0) { output += 'āš ļø Warnings (should fix):\n'; warnings.forEach(msg => { output += ` Line ${msg.line}:${msg.column} - ${msg.message} (${msg.ruleId})\n`; }); } } output += '\nšŸ’” Suggestions:\n'; output += '- Fix all errors to ensure code reliability\n'; output += '- Address warnings to improve code quality\n'; if (production) { output += '\n- Production mode enforces stricter rules for deployment-ready code\n'; output += '- Remove all console logs, debugger statements, and TODO comments\n'; output += '- Ensure proper error handling and documentation'; } else { output += '\n- Run \'eslint --fix\' locally for automatic fixes where possible\n'; output += '- Use production=true parameter for stricter deployment checks'; } return { content: [{ type: 'text', text: output, }, { type: 'text', text: JSON.stringify(response, null, 2), }], }; } catch (error) { console.error('Error analyzing code:', error); // Sanitize error messages let errorMessage = 'An error occurred while analyzing the code.'; if (error.message === 'Linting timeout exceeded') { errorMessage = 'Analysis timed out. The code might be too complex.'; } else if (error.message.includes('Parsing error')) { errorMessage = 'The code contains syntax errors and cannot be analyzed.'; } return { content: [{ type: 'text', text: `āŒ Error: ${errorMessage}\n\nPlease ensure the code is valid JavaScript.`, }], }; } } /** * Filter messages by severity and rule category */ function filterMessages(messages, severity, ruleCategory) { return messages.filter(msg => { // Filter by severity if (severity !== 'all') { if (severity === 'error' && msg.severity !== 2) return false; if (severity === 'warning' && msg.severity !== 1) return false; } // Filter by rule category if (ruleCategory !== 'all') { const msgCategory = getRuleCategory(msg.ruleId); if (msgCategory !== ruleCategory) return false; } return true; }); } /** * Sort messages based on criteria */ function sortMessages(messages, sortBy, sortOrder) { const sorted = [...messages]; sorted.sort((a, b) => { let aValue, bValue; switch (sortBy) { case 'line': aValue = a.line; bValue = b.line; break; case 'severity': aValue = a.severity; bValue = b.severity; break; case 'ruleId': aValue = a.ruleId || ''; bValue = b.ruleId || ''; break; case 'message': aValue = a.message.toLowerCase(); bValue = b.message.toLowerCase(); break; default: aValue = a.line; bValue = b.line; } if (sortBy === 'line' || sortBy === 'severity') { // Numeric comparison if (sortOrder === 'asc') { return aValue < bValue ? -1 : aValue > bValue ? 1 : 0; } else { return aValue > bValue ? -1 : aValue < bValue ? 1 : 0; } } else { // String comparison if (sortOrder === 'asc') { return aValue.localeCompare(bValue); } else { return bValue.localeCompare(aValue); } } }); return sorted; } /** * Get rule category based on rule ID */ function getRuleCategory(ruleId) { if (!ruleId) return 'unknown'; const errorRules = ['no-undef', 'no-unused-vars', 'no-unreachable', 'no-redeclare']; const bestPracticeRules = ['eqeqeq', 'curly', 'no-eval', 'no-implied-eval', 'no-console']; const stylisticRules = ['indent', 'quotes', 'semi', 'comma-spacing', 'brace-style']; const es6Rules = ['arrow-spacing', 'prefer-const', 'no-var', 'prefer-arrow-callback']; if (errorRules.includes(ruleId)) return 'possible-errors'; if (bestPracticeRules.includes(ruleId)) return 'best-practices'; if (stylisticRules.includes(ruleId)) return 'stylistic-issues'; if (es6Rules.includes(ruleId)) return 'es6'; return 'best-practices'; // Default category }

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/moikas-code/moidvk'

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