tree-ast-grep-mcp
Version:
Simple, direct ast-grep wrapper for AI coding agents. Zero abstractions, maximum performance.
175 lines • 6.69 kB
JavaScript
import * as fs from 'fs/promises';
import * as path from 'path';
import * as os from 'os';
import { ValidationError } from '../types/errors.js';
/**
* Simple rule builder that generates YAML and runs ast-grep scan
*/
export class SimpleScanTool {
workspaceManager;
binaryManager;
constructor(workspaceManager, binaryManager) {
this.workspaceManager = workspaceManager;
this.binaryManager = binaryManager;
}
async execute(params) {
// Basic validation
if (!params.id || !params.language || !params.pattern) {
throw new ValidationError('id, language, and pattern are required');
}
// Generate simple YAML rule
const yaml = this.buildYaml(params);
// Create temporary rule file
const tempDir = os.tmpdir();
const rulesFile = path.join(tempDir, `rule-${Date.now()}.yml`);
try {
await fs.writeFile(rulesFile, yaml, 'utf8');
// Build scan command
const args = ['scan', '--rule', rulesFile, '--json=stream'];
// Add paths
const paths = params.paths || ['.'];
args.push(...paths);
const result = await this.binaryManager.executeAstGrep(args, {
cwd: this.workspaceManager.getWorkspaceRoot(),
timeout: params.timeoutMs || 30000
});
const findings = this.parseFindings(result.stdout);
return {
yaml,
scan: {
findings,
summary: {
totalFindings: findings.length,
errors: findings.filter(f => f.severity === 'error').length,
warnings: findings.filter(f => f.severity === 'warning').length
}
}
};
}
finally {
// Cleanup
try {
await fs.unlink(rulesFile);
}
catch { }
}
}
buildYaml(params) {
const lines = [
`id: ${params.id}`,
`message: ${JSON.stringify(params.message || params.id)}`,
`severity: ${params.severity || 'warning'}`,
`language: ${params.language}`,
'rule:',
` pattern: ${JSON.stringify(params.pattern)}`
];
// Add simple constraints if provided
if (params.where && params.where.length > 0) {
lines.push('constraints:');
for (const constraint of params.where) {
lines.push(` ${constraint.metavariable}:`);
if (constraint.regex) {
lines.push(` regex: ${JSON.stringify(constraint.regex)}`);
}
else if (constraint.equals) {
// Escape and make exact match
const escaped = constraint.equals.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
lines.push(` regex: ${JSON.stringify('^' + escaped + '$')}`);
}
}
}
// Add fix if provided
if (params.fix) {
lines.push(`fix: ${JSON.stringify(params.fix)}`);
}
return lines.join('\n');
}
parseFindings(stdout) {
const findings = [];
if (!stdout.trim())
return findings;
const lines = stdout.trim().split('\n');
for (const line of lines) {
if (!line.trim())
continue;
try {
const finding = JSON.parse(line);
findings.push({
ruleId: finding.ruleId || 'unknown',
severity: finding.severity || 'info',
message: finding.message || '',
file: finding.file || '',
line: (finding.range?.start?.line || 0) + 1, // Convert to 1-based
column: finding.range?.start?.column || 0,
fix: finding.fix
});
}
catch (e) {
// Skip malformed lines
}
}
return findings;
}
static getSchema() {
return {
name: 'ast_run_rule',
description: '🚀 SIMPLIFIED: Generate and run ast-grep rules with minimal complexity. Fixed constraints and consistent behavior.',
inputSchema: {
type: 'object',
properties: {
id: {
type: 'string',
description: 'Rule ID (e.g., "no-console-log")'
},
language: {
type: 'string',
description: 'Language: javascript, typescript, python, etc.'
},
pattern: {
type: 'string',
description: 'AST pattern: console.log($ARG), function $NAME($PARAMS) { $$$ }, etc.'
},
message: {
type: 'string',
description: 'Human-readable message for findings'
},
severity: {
type: 'string',
enum: ['error', 'warning', 'info'],
default: 'warning',
description: 'Severity level'
},
where: {
type: 'array',
items: {
type: 'object',
properties: {
metavariable: { type: 'string' },
regex: { type: 'string' },
equals: { type: 'string' }
},
required: ['metavariable']
},
description: 'Constraints on metavariables'
},
fix: {
type: 'string',
description: 'Optional fix template'
},
paths: {
type: 'array',
items: { type: 'string' },
description: 'Paths to scan'
},
timeoutMs: {
type: 'number',
default: 30000,
description: 'Timeout in milliseconds'
}
},
required: ['id', 'language', 'pattern']
}
};
}
}
//# sourceMappingURL=simple-scan.js.map