Skip to main content
Glama
sbarron

Ambiance MCP Server

by sbarron

ast_grep_search

Search code using structural pattern matching to find specific syntax patterns across multiple programming languages for refactoring, security auditing, and code analysis.

Instructions

šŸ” AST-Grep structural code search tool

Performs powerful structural code search using ast-grep's pattern matching capabilities. Unlike text-based search, this matches syntactical AST node structures.

Key Features:

  • Structural pattern matching (not just text)

  • Multi-language support (JS, TS, Python, Go, Rust, etc.)

  • Wildcard variables ($VAR, $FUNC, $ARGS)

  • Precise code location information

  • Fast Rust-based execution with performance optimizations

  • Comprehensive pattern validation with helpful error messages

  • 120-second timeout for very large projects

  • Automatic respect for .gitignore files (no manual exclusions needed)

Pattern Syntax:

  • Use $ + UPPERCASE for wildcards: $FUNC, $VAR, $ARGS

  • Patterns look like real code: 'function $NAME($ARGS) { $BODY }'

  • Match specific constructs: 'new $CLASS($ARGS)'

  • Valid characters: (), {}, [], "", '', numbers, operators, keywords

  • NOT regex: do NOT use '|', '.*', '.+', '/pattern/', or escapes like '(' or '{'.

Common Mistakes to Avoid: āŒ Don't use: 'function $FUNC' (ambiguous, multiple AST interpretations) āŒ Don't use: 'export $TYPE' (ambiguous, multiple AST interpretations) āŒ Don't use: '$NAME' (too generic, matches everything) āŒ Don't use: /pattern/ (regex syntax not supported)

āœ… Good Patterns:

  • 'function $NAME($ARGS) { $BODY }' (complete function structure)

  • 'export const $NAME = $VALUE' (exported constant)

  • 'import $NAME from "$MODULE"' (import statement)

  • 'new $CLASS($ARGS)' (constructor call)

  • 'def ' (Python function definitions)

  • 'class $NAME:' (Python class)

  • 'await $PROMISE' inside 'for ($COND) { $BODY }' (relational patterns)

Examples:

  • Find all functions: 'function $NAME($ARGS) { $BODY }'

  • Find all exports: 'export const $NAME = $VALUE'

  • Find imports: 'import $NAME from "$MODULE"'

  • Find class instantiation: 'new $CLASS($ARGS)'

  • Find method calls: '$OBJ.$METHOD($ARGS)'

  • Find async functions: 'async function $NAME($ARGS) { $BODY }'

  • Find arrow functions: 'const $NAME = ($ARGS) => $BODY'

  • Find React components: 'export function $NAME($PROPS) { return $JSX }'

  • Find Python function definitions: 'def '

  • Find Python classes: 'class $NAME:'

Advanced Usage:

  • Use $$$ for zero or more arguments: 'console.log($$$ARGS)'

  • Use relational rules: 'await $PROMISE' inside 'for ($COND) { $BODY }'

  • Use multiple searches for OR conditions (alternation not supported)

Direct CLI Usage (for agents with command line access): Agents with command line access can run ast-grep directly:

Basic usage

npx ast-grep --pattern "function $NAME($ARGS) { $BODY }" --lang ts

Python function definitions

npx ast-grep --pattern "def " --lang py

Python classes

npx ast-grep --pattern "class $NAME:" --lang py

With file filtering (recommended for large projects)

npx ast-grep --pattern "def " --lang py src/**/*.py

JSON output

npx ast-grep --pattern "class $NAME:" --lang py --json=stream

Full documentation

npx ast-grep --help

Note: ast-grep respects .gitignore files automatically - no --exclude-dir flags needed

Use Cases:

  • Code refactoring and migration

  • Finding specific patterns across codebase

  • Security auditing for dangerous patterns

  • Architecture analysis and dependency tracking

  • Finding unused imports or exports

  • API usage analysis

Performance Optimizations for Large Projects:

  • 120-second timeout for very large projects

  • Automatically respects .gitignore files for exclusions

  • For additional exclusions, configure .gitignore in your project

Tips for Large Projects (like D:\Dev\SWE-agent):

  • Use filePattern to search specific directories: "src/**/*.py"

  • Add large directories to .gitignore: node_modules/, tests/, docs/, etc.

  • Consider CLI usage for better performance: npx ast-grep --pattern "import json" --lang py src/**/*.py

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
patternNoAST pattern, not regex. Use $UPPERCASE wildcards. Examples: "$FUNC($ARGS)", "new $CLASS($ARGS)", "import $NAME from "express""
rulePathNoPath to an ast-grep rule file (YAML/JSON). When provided, rule mode is used instead of pattern.
ruleYamlNoInline ast-grep rule content in YAML (JSON is also valid YAML). Will be written to a temp file and used with --rule.
ruleJsonNoInline ast-grep rule object (JSON). Optionally validated against local schemas and written to a temp file for --rule.
projectPathYesProject directory path to search in. Can be absolute or relative to workspace.
languageNoProgramming language (auto-detected if not provided). Supported: js, ts, py, go, rs, java, c, cpp
filePatternNoSpecific directory or file path to search within the project (e.g., "src", "lib", "*.py", "src/**/*.ts"). **RECOMMENDED for large projects** - if not provided, searches entire project respecting .gitignore. Use wildcards like "src/**/*.py" to search recursively in specific directories. This can dramatically improve performance on large codebases.
maxMatchesNoMaximum number of matches to return (default: 100)
includeContextNoInclude surrounding context lines for each match (default: true)
contextLinesNoNumber of context lines to include around matches (default: 3)
respectGitignoreNoRespect .gitignore files and other ignore patterns (default: true)
excludePatternsNoAdditional patterns to exclude from search (e.g., ["test/**", "docs/**"])

Implementation Reference

  • Main handler function for 'ast_grep_search' tool. Validates input, prepares rules if provided, executes ast-grep via child_process.spawn (with multiple fallback methods), parses JSON stream output, handles timeouts, gitignore, exclusions, and returns structured AstGrepResult.
    export async function handleAstGrep(args: any): Promise<AstGrepResult> {
      const startTime = Date.now();
    
      try {
        // Support rule-based execution path in addition to pattern
        const hasRuleInput = !!(args.rulePath || args.ruleYaml || args.ruleJson);
        let pattern: string | undefined;
        let ruleFilePath: string | undefined;
        let tempRule: string | null = null;
        const languageToUse = args.language || inferLanguageFromArgs(args);
    
        if (hasRuleInput) {
          const prepared = await prepareRuleFile(args);
          ruleFilePath = prepared.ruleFilePath;
          tempRule = prepared.isTemporary ? prepared.ruleFilePath : null;
    
          if (prepared.validationErrors && prepared.validationErrors.length > 0) {
            const message =
              'Rule schema validation failed:\n' +
              prepared.validationErrors.map((m: string) => '• ' + m).join('\n');
            throw new Error(message);
          }
        } else {
          // Early validation: comprehensive pattern validation
          if (typeof args.pattern !== 'string' || !args.pattern.trim()) {
            throw new Error(
              'Pattern must be a non-empty string containing an AST pattern (not regex).'
            );
          }
    
          pattern = String(args.pattern);
          const validation = validateAstGrepPattern(pattern);
    
          if (!validation.isValid) {
            const errorMessage = validation.error || 'Invalid AST pattern';
            const suggestions = validation.suggestions || [];
            const fullMessage =
              errorMessage +
              (suggestions.length > 0
                ? '\n\nSuggestions:\n' + suggestions.map(s => '• ' + s).join('\n')
                : '');
            throw new Error(fullMessage);
          }
    
          // Log warnings for potentially problematic patterns
          if (validation.warning) {
            logger.warn('āš ļø Potentially problematic AST pattern', {
              pattern,
              warning: validation.warning,
              suggestions: validation.suggestions,
            });
          }
    
          // Log suggestions for improvement
          if (validation.suggestions && validation.suggestions.length > 0) {
            logger.info('šŸ’” Pattern suggestions available', {
              pattern,
              suggestions: validation.suggestions,
            });
          }
        }
    
        logger.info('šŸ” Executing ast-grep search', {
          mode: hasRuleInput ? 'rule' : 'pattern',
          pattern,
          ruleFilePath,
          projectPath: args.projectPath,
          language: languageToUse,
          filePattern: args.filePattern,
        });
    
        // Validate and resolve the project path
        const projectPath = await validateAndResolvePath(args.projectPath);
    
        // Execute ast-grep command
        const result = await executeAstGrep({
          pattern,
          ruleFilePath,
          projectPath,
          language: languageToUse,
          filePattern: args.filePattern,
          maxMatches: args.maxMatches || 100,
          includeContext: args.includeContext !== false,
          contextLines: args.contextLines || 3,
          respectGitignore: args.respectGitignore !== false,
          excludePatterns: args.excludePatterns,
        });
    
        const executionTime = Date.now() - startTime;
    
        logger.info('āœ… ast-grep search completed', {
          matches: result.matches.length,
          executionTime,
        });
    
        // Clean up temp rule if created
        if (tempRule) {
          try {
            await fs.unlink(tempRule);
          } catch (error) {
            logger.debug('Failed to delete temporary ast-grep rule file', {
              ruleFile: tempRule,
              error: error instanceof Error ? error.message : String(error),
            });
          }
        }
    
        return {
          ...result,
          executionTime,
          pattern: pattern ?? '(rule)',
          language: languageToUse,
        };
      } catch (error) {
        const executionTime = Date.now() - startTime;
        const errorMessage = error instanceof Error ? error.message : String(error);
    
        logger.error('āŒ ast-grep search failed', {
          error: errorMessage,
          pattern: args.pattern,
          executionTime,
        });
    
        return {
          matches: [],
          totalMatches: 0,
          executionTime,
          pattern: args?.pattern,
          language: args.language,
          error: errorMessage,
        };
      }
    }
  • Tool definition including name, detailed description, and comprehensive inputSchema with validation for pattern, rule inputs, projectPath (required), language enum, filePattern for performance, maxMatches, context options, gitignore respect, and exclusions.
    export const astGrepTool: Tool = {
      name: 'ast_grep_search',
      description: `šŸ” AST-Grep structural code search tool
    
    Performs powerful structural code search using ast-grep's pattern matching capabilities.
    Unlike text-based search, this matches syntactical AST node structures.
    
    **Key Features:**
    - Structural pattern matching (not just text)
    - Multi-language support (JS, TS, Python, Go, Rust, etc.)
    - Wildcard variables ($VAR, $FUNC, $ARGS)
    - Precise code location information
    - Fast Rust-based execution with performance optimizations
    - Comprehensive pattern validation with helpful error messages
    - 120-second timeout for very large projects
    - Automatic respect for .gitignore files (no manual exclusions needed)
    
    **Pattern Syntax:**
    - Use $ + UPPERCASE for wildcards: $FUNC, $VAR, $ARGS
    - Patterns look like real code: 'function $NAME($ARGS) { $BODY }'
    - Match specific constructs: 'new $CLASS($ARGS)'
    - Valid characters: (), {}, [], "", '', numbers, operators, keywords
    - NOT regex: do NOT use '|', '.*', '.+', '/pattern/', or escapes like '\\(' or '\\{'.
    
    **Common Mistakes to Avoid:**
    āŒ Don't use: 'function $FUNC' (ambiguous, multiple AST interpretations)
    āŒ Don't use: 'export $TYPE' (ambiguous, multiple AST interpretations)
    āŒ Don't use: '$NAME' (too generic, matches everything)
    āŒ Don't use: /pattern/ (regex syntax not supported)
    
    **āœ… Good Patterns:**
    - 'function $NAME($ARGS) { $BODY }' (complete function structure)
    - 'export const $NAME = $VALUE' (exported constant)
    - 'import $NAME from "$MODULE"' (import statement)
    - 'new $CLASS($ARGS)' (constructor call)
    - 'def ' (Python function definitions)
    - 'class $NAME:' (Python class)
    - 'await $PROMISE' inside 'for ($COND) { $BODY }' (relational patterns)
    
    **Examples:**
    - Find all functions: 'function $NAME($ARGS) { $BODY }'
    - Find all exports: 'export const $NAME = $VALUE'
    - Find imports: 'import $NAME from "$MODULE"'
    - Find class instantiation: 'new $CLASS($ARGS)'
    - Find method calls: '$OBJ.$METHOD($ARGS)'
    - Find async functions: 'async function $NAME($ARGS) { $BODY }'
    - Find arrow functions: 'const $NAME = ($ARGS) => $BODY'
    - Find React components: 'export function $NAME($PROPS) { return $JSX }'
    - Find Python function definitions: 'def '
    - Find Python classes: 'class $NAME:'
    
    **Advanced Usage:**
    - Use $$$ for zero or more arguments: 'console.log($$$ARGS)'
    - Use relational rules: 'await $PROMISE' inside 'for ($COND) { $BODY }'
    - Use multiple searches for OR conditions (alternation not supported)
    
    **Direct CLI Usage (for agents with command line access):**
    Agents with command line access can run ast-grep directly:
    
    # Basic usage
    npx ast-grep --pattern "function $NAME($ARGS) { $BODY }" --lang ts
    
    # Python function definitions
    npx ast-grep --pattern "def " --lang py
    
    # Python classes
    npx ast-grep --pattern "class $NAME:" --lang py
    
    # With file filtering (recommended for large projects)
    npx ast-grep --pattern "def " --lang py src/**/*.py
    
    # JSON output
    npx ast-grep --pattern "class $NAME:" --lang py --json=stream
    
    # Full documentation
    npx ast-grep --help
    
    # Note: ast-grep respects .gitignore files automatically - no --exclude-dir flags needed
    
    **Use Cases:**
    - Code refactoring and migration
    - Finding specific patterns across codebase
    - Security auditing for dangerous patterns
    - Architecture analysis and dependency tracking
    - Finding unused imports or exports
    - API usage analysis
    
    **Performance Optimizations for Large Projects:**
    - 120-second timeout for very large projects
    - Automatically respects .gitignore files for exclusions
    - For additional exclusions, configure .gitignore in your project
    
    **Tips for Large Projects (like D:\\Dev\\SWE-agent):**
    - Use filePattern to search specific directories: "src/**/*.py"
    - Add large directories to .gitignore: node_modules/, tests/, docs/, etc.
    - Consider CLI usage for better performance: npx ast-grep --pattern "import json" --lang py src/**/*.py
    `,
      inputSchema: {
        type: 'object',
        properties: {
          pattern: {
            type: 'string',
            description:
              'AST pattern, not regex. Use $UPPERCASE wildcards. Examples: "$FUNC($ARGS)", "new $CLASS($ARGS)", "import $NAME from "express""',
          },
          rulePath: {
            type: 'string',
            description:
              'Path to an ast-grep rule file (YAML/JSON). When provided, rule mode is used instead of pattern.',
          },
          ruleYaml: {
            type: 'string',
            description:
              'Inline ast-grep rule content in YAML (JSON is also valid YAML). Will be written to a temp file and used with --rule.',
          },
          ruleJson: {
            type: 'object',
            description:
              'Inline ast-grep rule object (JSON). Optionally validated against local schemas and written to a temp file for --rule.',
          },
          projectPath: {
            type: 'string',
            description:
              'Project directory path to search in. Can be absolute or relative to workspace.',
          },
          language: {
            type: 'string',
            description:
              'Programming language (auto-detected if not provided). Supported: js, ts, py, go, rs, java, c, cpp',
            enum: [
              'js',
              'ts',
              'tsx',
              'jsx',
              'py',
              'go',
              'rs',
              'java',
              'c',
              'cpp',
              'php',
              'rb',
              'kt',
              'swift',
            ],
          },
          filePattern: {
            type: 'string',
            description:
              'Specific directory or file path to search within the project (e.g., "src", "lib", "*.py", "src/**/*.ts"). **RECOMMENDED for large projects** - if not provided, searches entire project respecting .gitignore. Use wildcards like "src/**/*.py" to search recursively in specific directories. This can dramatically improve performance on large codebases.',
          },
          maxMatches: {
            type: 'number',
            description: 'Maximum number of matches to return (default: 100)',
            default: 100,
            minimum: 1,
            maximum: 1000,
          },
          includeContext: {
            type: 'boolean',
            description: 'Include surrounding context lines for each match (default: true)',
            default: true,
          },
          contextLines: {
            type: 'number',
            description: 'Number of context lines to include around matches (default: 3)',
            default: 3,
            minimum: 0,
            maximum: 10,
          },
          respectGitignore: {
            type: 'boolean',
            description: 'Respect .gitignore files and other ignore patterns (default: true)',
            default: true,
          },
          excludePatterns: {
            type: 'array',
            description: 'Additional patterns to exclude from search (e.g., ["test/**", "docs/**"])',
            items: { type: 'string' },
          },
        },
        required: ['projectPath'],
      },
    };
  • Registration of the handler in localHandlers object exported from localTools module, mapping 'ast_grep_search' to handleAstGrep. Also includes astGrepTool in localTools array.
    export const localHandlers = {
      ...(allowLocalContext ? { local_context: handleSemanticCompact } : {}),
      local_project_hints: handleProjectHints,
      local_file_summary: handleFileSummary,
      frontend_insights: handleFrontendInsights,
      local_debug_context: handleLocalDebugContext,
      manage_embeddings: handleManageEmbeddings,
      ast_grep_search: handleAstGrep,
    };
  • src/index.ts:135-142 (registration)
    Registration in main MCP server handlers dictionary in src/index.ts, mapping 'ast_grep_search' to handleAstGrep, and astGrepTool added to tools list.
    this.handlers = {
      ...(allowLocalContext ? { local_context: handleSemanticCompact } : {}),
      local_project_hints: handleProjectHints,
      local_file_summary: handleFileSummary,
      frontend_insights: handleFrontendInsights,
      local_debug_context: handleLocalDebugContext,
      ast_grep_search: handleAstGrep,
    };
  • Core helper function that executes the ast-grep CLI command, handles spawning with platform-specific fallbacks, streams and parses JSON output into AstGrepMatch structures, applies filters, and manages timeouts/errors.
    export async function executeAstGrep(options: {
      pattern?: string;
      ruleFilePath?: string;
      projectPath: string;
      language?: string;
      filePattern?: string;
      maxMatches: number;
      includeContext: boolean;
      contextLines: number;
      respectGitignore?: boolean;
      excludePatterns?: string[];
    }): Promise<AstGrepResult> {
      return new Promise((resolve, reject) => {
        const startTime = Date.now();
        const cliArgs: string[] = [];
    
        try {
          // Use rule file if provided; otherwise fall back to pattern
          if (options.ruleFilePath) {
            logger.debug('Using ast-grep rule file', { ruleFilePath: options.ruleFilePath });
            cliArgs.push('--rule', options.ruleFilePath);
          } else {
            logger.debug('Adding pattern to args', {
              pattern: options.pattern,
              patternType: typeof options.pattern,
              patternLength: (options.pattern || '').length,
            });
    
            // Double-check the pattern before adding to args
            if (!options.pattern || options.pattern.trim() === '') {
              throw new Error('Pattern is empty or undefined');
            }
    
            logger.debug('Adding pattern to ast-grep arguments', {
              pattern: options.pattern,
              patternLength: options.pattern.length,
            });
            cliArgs.push('--pattern', options.pattern);
          }
    
          if (options.language) {
            cliArgs.push('--lang', options.language);
          }
    
          cliArgs.push('--json=stream');
    
          if (options.includeContext && options.contextLines > 0) {
            cliArgs.push('--context', options.contextLines.toString());
          }
    
          if (options.respectGitignore === false) {
            cliArgs.push('--no-ignore', 'vcs');
          }
    
          if (options.filePattern) {
            cliArgs.push(options.filePattern);
          } else {
            // Note: ast-grep respects .gitignore files by default, which provides most exclusions
            // For additional exclusions, users should configure .gitignore in their project
            cliArgs.push('.');
            logger.debug(
              'Using default .gitignore-based exclusions (ast-grep does not support --exclude-dir flags)'
            );
          }
    
          logger.info('Executing ast-grep search', {
            pattern: options.pattern,
            ruleFilePath: options.ruleFilePath,
            language: options.language,
            projectPath: options.projectPath,
            maxMatches: options.maxMatches,
            filePattern: options.filePattern || 'all files (with exclusions)',
            cwd: options.projectPath,
          });
    
          // Debug: Check if project path exists and is accessible
          try {
            const fs = require('fs');
            const path = require('path');
            const stats = fs.statSync(options.projectPath);
            logger.debug('Project path exists', {
              path: options.projectPath,
              isDirectory: stats.isDirectory(),
              readable: true,
              size: stats.size,
            });
          } catch (error) {
            logger.error('Project path does not exist or is not accessible', {
              path: options.projectPath,
              error: error instanceof Error ? error.message : String(error),
            });
            throw new Error('Project path does not exist or is not accessible: ' + options.projectPath);
          }
    
          logger.debug('ast-grep command details', {
            command: 'npx ast-grep',
            args: cliArgs,
            cwd: options.projectPath,
            fullCommand: 'npx ast-grep ' + cliArgs.join(' '),
          });
    
          // Log the command being executed
          const fullCommand = 'npx ast-grep ' + cliArgs.join(' ');
          logger.debug('Executing ast-grep command:', { command: fullCommand });
    
          let astGrep: ReturnType<typeof spawn> | null = null;
          let executionMethod = 'unknown';
    
          const localBinary = findLocalAstGrepBinary();
          if (localBinary) {
            try {
              const useShell = process.platform === 'win32' && localBinary.endsWith('.cmd');
              astGrep = spawn(localBinary, cliArgs, {
                cwd: options.projectPath,
                stdio: ['ignore', 'pipe', 'pipe'],
                shell: useShell,
              });
              executionMethod = 'local-binary';
              logger.debug('Using local ast-grep binary', { binary: localBinary, useShell });
            } catch (localBinaryError) {
              logger.debug('Failed to spawn local binary, falling back to npx', {
                error:
                  localBinaryError instanceof Error
                    ? localBinaryError.message
                    : String(localBinaryError),
              });
            }
          }
    
          if (!astGrep) {
            const approaches = [
              {
                name: 'cmd-npx',
                command: 'cmd',
                args: ['/c', 'npx', 'ast-grep', ...cliArgs],
                options: { shell: false },
              },
              {
                name: 'powershell-npx',
                command: 'powershell',
                args: ['-Command', 'npx ast-grep ' + cliArgs.join(' ')],
                options: { shell: false },
              },
              {
                name: 'direct-npx',
                command: 'npx',
                args: ['ast-grep', ...cliArgs],
                options: { shell: false },
              },
            ] as const;
    
            let lastError: Error | null = null;
    
            for (const approach of approaches) {
              try {
                logger.debug('Attempting spawn approach', {
                  approach: approach.name,
                  command: approach.command,
                  args: approach.args,
                  cwd: options.projectPath,
                  pattern: options.pattern,
                  language: options.language,
                });
    
                astGrep = spawn(approach.command, approach.args, {
                  cwd: options.projectPath,
                  stdio: ['pipe', 'pipe', 'pipe'], // Change to pipe all streams for debugging
                  ...approach.options,
                  env: process.env,
                });
    
                executionMethod = approach.name;
                logger.debug('Spawn succeeded', { approach: approach.name });
                break;
              } catch (approachError) {
                lastError =
                  approachError instanceof Error ? approachError : new Error(String(approachError));
                logger.debug('Spawn approach failed', {
                  approach: approach.name,
                  error: lastError.message,
                  code: (lastError as any).code,
                  shell: approach.options.shell,
                });
              }
            }
    
            if (!astGrep) {
              logger.error('All spawn approaches failed', {
                approaches: approaches.map(a => a.name),
                lastError: lastError?.message,
                cwd: options.projectPath,
                nodeVersion: process.version,
              });
              throw new Error(
                'All spawn approaches failed: ' + (lastError?.message || 'Unknown error')
              );
            }
          }
    
          if (!astGrep) {
            throw new Error('Failed to spawn ast-grep: no process handle available.');
          }
    
          let stdout = '';
          let stderr = '';
    
          astGrep.stdout?.on('data', data => {
            stdout += data.toString();
          });
    
          astGrep.stderr?.on('data', data => {
            const chunk = data.toString();
            stderr += chunk;
            // Only log stderr chunks that contain actual error information
            if (chunk.includes('SyntaxError') || chunk.includes('Error') || chunk.includes('error')) {
              logger.debug('ast-grep stderr:', chunk.substring(0, 200));
            }
          });
    
          astGrep.on('close', code => {
            clearInterval(activityInterval);
            logger.debug('ast-grep process closed', {
              code,
              executionMethod,
              success: code === 0,
              executionTime: Date.now() - startTime,
            });
    
            if (code !== 0 && code !== null) {
              reject(
                new Error(
                  'ast-grep exited with code ' + code + ': ' + (stderr || 'No error message provided')
                )
              );
              return;
            }
    
            try {
              let matches = parseAstGrepOutput(stdout, options.maxMatches);
    
              if (options.excludePatterns && options.excludePatterns.length > 0) {
                matches = filterMatchesByExcludePatterns(
                  matches,
                  options.excludePatterns,
                  options.projectPath
                );
              }
    
              resolve({
                ...matches,
                executionTime: Date.now() - startTime,
                pattern: options.pattern || (options.ruleFilePath ? '(rule)' : ''),
                language: options.language,
              });
            } catch (parseError) {
              reject(
                new Error(
                  'Failed to parse ast-grep output: ' +
                    (parseError instanceof Error ? parseError.message : String(parseError))
                )
              );
            }
          });
    
          // Add debugging for process activity
          let lastActivity = Date.now();
          const activityInterval = setInterval(() => {
            const now = Date.now();
            const timeSinceLastActivity = now - lastActivity;
            if (timeSinceLastActivity > 5000) {
              // Log every 5 seconds of inactivity
              logger.debug('ast-grep process still running', {
                executionTime: now - startTime,
                timeSinceLastActivity,
                executionMethod,
                cwd: options.projectPath,
              });
            }
          }, 5000);
    
          astGrep.stdout?.on('data', (data: Buffer) => {
            lastActivity = Date.now();
            const chunk = data.toString();
            stdout += chunk;
            if (chunk.includes('\n')) {
              logger.debug('ast-grep stdout chunk', {
                chunkLength: chunk.length,
                totalStdoutLength: stdout.length,
                executionTime: Date.now() - startTime,
              });
            }
          });
    
          astGrep.stderr?.on('data', (data: Buffer) => {
            lastActivity = Date.now();
            const chunk = data.toString();
            stderr += chunk;
            if (chunk.includes('\n')) {
              logger.debug('ast-grep stderr chunk', {
                chunkLength: chunk.length,
                totalStderrLength: stderr.length,
                executionTime: Date.now() - startTime,
              });
            }
          });
    
          astGrep.on('error', processError => {
            clearInterval(activityInterval);
            logger.error('ast-grep process error', {
              error: processError.message,
              code: (processError as any).code,
              stack: processError.stack?.substring(0, 300),
              executionMethod,
              cwd: options.projectPath,
              platform: process.platform,
              nodeVersion: process.version,
              path: process.env.PATH?.substring(0, 200),
            });
            reject(
              new Error(
                'Failed to spawn ast-grep (' +
                  executionMethod +
                  '): ' +
                  processError.message +
                  ' (code: ' +
                  (processError as any).code +
                  '). Make sure @ast-grep/cli is installed and accessible.'
              )
            );
          });
    
          setTimeout(() => {
            clearInterval(activityInterval);
            if (astGrep) {
              astGrep.kill();
            }
            reject(
              new Error(
                'ast-grep search timed out after 30 seconds. For large projects, consider using filePattern to search specific directories (e.g., "src/**/*.py") or excludePatterns to exclude large directories.'
              )
            );
          }, 30000);
        } catch (error) {
          logger.error('Failed to execute ast-grep', {
            error: error instanceof Error ? error.message : String(error),
            cwd: options.projectPath,
            platform: process.platform,
          });
          reject(
            new Error(
              'Failed to execute ast-grep: ' + (error instanceof Error ? error.message : String(error))
            )
          );
        }
      });
    }
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden and excels at disclosing behavioral traits. It specifies performance characteristics (120-second timeout, fast Rust-based execution), automatic behaviors (respects .gitignore files), validation behavior (comprehensive pattern validation with helpful error messages), and operational constraints (multi-language support, pattern syntax rules).

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness2/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is excessively long (over 1000 words) with redundant sections. While well-structured with headings, it includes unnecessary content like CLI usage instructions, extensive pattern examples, and performance tips that could be condensed. Many sentences don't earn their place for a tool description aimed at AI agents.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (12 parameters, no output schema, no annotations), the description provides comprehensive context. It covers purpose, usage, behavioral traits, parameter guidance, examples, and edge cases. The only gap is output format details, but with no output schema, the description adequately compensates for all other aspects.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 100% schema description coverage, the baseline is 3, but the description adds significant value by explaining parameter relationships and practical usage. It clarifies that 'filePattern' is 'RECOMMENDED for large projects' and provides examples of how parameters work together (e.g., pattern syntax with wildcards, language selection). However, it doesn't fully explain all 12 parameters' interactions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool performs 'structural code search using ast-grep's pattern matching capabilities' and distinguishes it from text-based search. It specifies the verb ('search'), resource ('code'), and unique approach ('structural pattern matching'), making it distinct from any sibling tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides clear context for when to use this tool (structural code search vs. text-based search) and includes 'Use Cases' section with specific applications. However, it doesn't explicitly state when NOT to use it or mention alternatives among sibling tools, though the structural vs. text distinction is implied.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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/sbarron/AmbianceMCP'

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