Skip to main content
Glama

CTS MCP Server

by EricA1019
index.tsโ€ข8.76 kB
/** * CTS Audit Tool - MCP Wrapper * * Audits GDScript projects for CTS compliance, code quality, and project structure. * Returns comprehensive reports with scoring and actionable recommendations. */ import { z } from 'zod'; import type { ToolDefinition } from '../../types.js'; import { ALL_RULES, type ComplianceRule, type AuditContext } from './checkers.js'; import { collectMetrics } from './metrics.js'; import { generateReport, formatMarkdown, type RuleResult } from './reporter.js'; import { validateToolResponse } from '../../schemas.js'; import { Errors } from '../../errors.js'; import { truncateLargeArrays, checkResponseSize } from '../../sampling/index.js'; import { executeRulesParallel, shouldUseParallel } from '../../parallel/rule_executor.js'; import { readdirSync } from 'fs'; import { join, relative } from 'path'; import { globalConfigLoader } from '../../config/config_loader.js'; import { getFileHashCache } from '../../cache/file_hash_cache.js'; export const AuditInputSchema = z.object({ projectPath: z.string().describe('Path to the Godot project to audit'), categories: z .array(z.enum(['cts', 'code_quality', 'project_structure'])) .optional() .describe('Categories to audit (default: all)'), minScore: z .number() .min(0) .max(100) .optional() .describe('Minimum score threshold (0-100, default: 0)'), format: z .enum(['json', 'markdown']) .optional() .describe('Output format (default: json)'), noCache: z .boolean() .optional() .describe('Disable file hash cache for this run (default: false)'), }); export type AuditInput = z.infer<typeof AuditInputSchema>; export const auditTool: ToolDefinition = { name: 'cts_audit', description: `Audit a Godot GDScript project for CTS compliance, code quality, and project structure. Returns a comprehensive report with: - Overall score (0-100) and category scores - Violations with file locations and severity - Actionable recommendations with effort estimates - Project metrics (LOC, complexity, test coverage) Categories: - cts: CTS standards (file size, signal-first, hop size, templates) - code_quality: Type hints, error handling, complexity, naming - project_structure: Addon integration, directory organization Usage: { "projectPath": "/path/to/godot/project", "categories": ["cts", "code_quality"], "minScore": 75, "format": "markdown" }`, inputSchema: { type: 'object', properties: { projectPath: { type: 'string', description: 'Path to the Godot project to audit', }, categories: { type: 'array', items: { type: 'string', enum: ['cts', 'code_quality', 'project_structure'], }, description: 'Categories to audit (default: all)', }, minScore: { type: 'number', minimum: 0, maximum: 100, description: 'Minimum score threshold (0-100, default: 0)', }, format: { type: 'string', enum: ['json', 'markdown'], description: 'Output format (default: json)', }, }, required: ['projectPath'], }, }; /** * Create MCP audit handler */ export function createAuditHandler() { return async (input: unknown) => { const startTime = performance.now(); try { // Validate input const validationResult = AuditInputSchema.safeParse(input); if (!validationResult.success) { throw Errors.validationError( 'input', 'AuditInputSchema', validationResult.error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ') ); } const validInput = validationResult.data; // Load project configuration const projectConfig = globalConfigLoader.loadConfig(validInput.projectPath); console.log(`[cts_audit] Loaded config from: ${JSON.stringify(projectConfig.sources)}`); console.log(`[cts_audit] File size limit: ${projectConfig.rules.maxFileLines} lines`); // Recursively collect all files const allFiles: string[] = []; async function collectFiles(dir: string, basePath: string) { const entries = await readdir(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = join(dir, entry.name); if (entry.isDirectory()) { // Skip hidden and generated directories if (!entry.name.startsWith('.') && entry.name !== 'addons') { await collectFiles(fullPath, basePath); } } else { allFiles.push(relative(basePath, fullPath)); } } } await collectFiles(validInput.projectPath, validInput.projectPath); // Filter by categories const selectedCategories = validInput.categories || ['cts', 'code_quality', 'project_structure']; const rules = ALL_RULES.filter((rule) => selectedCategories.includes(rule.category) ); if (rules.length === 0) { throw Errors.invalidInput( 'categories', `No rules found for selected categories: ${selectedCategories.join(', ')}` ); } // Create audit context const context: AuditContext = { projectPath: validInput.projectPath, files: allFiles, config: projectConfig.rules, // Pass config to checkers }; // Collect project metrics const metrics = await collectMetrics(validInput.projectPath, allFiles); // Run compliance checks (parallel or sequential based on project size) let results: RuleResult[]; const useParallel = shouldUseParallel(rules.length, allFiles.length); if (useParallel) { console.log(`โšก Running ${rules.length} rules in parallel (4 workers max)...`); results = await executeRulesParallel( rules, context, 4, // max workers (completed, total, ruleId) => { console.log(`Progress: ${completed}/${total} rules complete (${ruleId})`); } ); } else { console.log(`๐Ÿ”„ Running ${rules.length} rules sequentially (project too small for parallel execution)...`); results = []; for (const rule of rules) { const result = await rule.check(context); results.push({ ...result, rule }); } } // Generate report const report = generateReport(results, metrics); // Check minimum score threshold const minScore = validInput.minScore ?? 0; if (report.overallScore < minScore) { throw Errors.mcpError( 'AUDIT_THRESHOLD_FAILED', `Overall score ${report.overallScore.toFixed(1)}/100 is below threshold ${minScore}/100`, { report, threshold: minScore } ); } const endTime = performance.now(); const duration = endTime - startTime; // Check performance target (<5s) if (duration > 5000) { console.warn( `โš ๏ธ Audit took ${Math.round(duration)}ms (target: <5000ms)` ); } // Format response using BaseToolResponse pattern // Check if response is too large and truncate if needed const truncatedReport = truncateLargeArrays(report, 100); const sizeCheck = checkResponseSize(truncatedReport, 60000); if (sizeCheck.truncated) { console.warn( `โš ๏ธ Audit report truncated from ${Math.round(sizeCheck.originalSize / 1024)}KB to prevent buffer overflow` ); } const response = { success: true as const, timestamp: new Date().toISOString(), toolName: 'cts_audit' as const, duration_ms: Math.round(duration), result: { report: truncatedReport, performance: { durationMs: Math.round(duration), rulesChecked: rules.length, metricsCollected: true, }, format: validInput.format || 'json', markdown: validInput.format === 'markdown' ? formatMarkdown(truncatedReport) : undefined, _sizeInfo: sizeCheck.truncated ? { truncated: true, originalSize: sizeCheck.originalSize, truncatedFields: truncatedReport._truncated, } : undefined, }, }; // Validate response format const validation = validateToolResponse('cts_audit', response); if (!validation.valid) { throw Errors.validationError( 'response', 'AuditResponse', validation.errors?.errors[0]?.message || 'Unknown validation error' ); } return response; } catch (error) { // Re-throw enhanced errors throw error; } }; }

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/EricA1019/CTS_MCP'

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