How to Perform Structural Search in Code-Graph-RAG: AST Pattern Matching Guide
Code-Graph-RAG enables structural search through AST pattern matching using the ast-grep library, exposing the functionality via an agentic Python Tool, an MCP command, or a direct service API to find code patterns beyond simple text matching.
Structural search in Code-Graph-RAG allows you to query codebases using Abstract Syntax Tree (AST) patterns rather than plain-text regular expressions. This capability, implemented in the vitali87/code-graph-rag repository, leverages the ast-grep engine to match syntactic structures across supported languages. Whether you need to find all function definitions with specific signatures or locate print statements with particular arguments, the structural search tool provides precise, context-aware code discovery.
Understanding AST Pattern Matching
Unlike traditional regex searches that match raw text, structural search operates on the parsed syntax tree of your code. This approach understands language constructs like function definitions, class hierarchies, and control flow statements. The implementation relies on the ast-grep library, which parses source files into ASTs and matches patterns using metavariables.
The core implementation resides in codebase_rag/tools/structural_search.py, with the underlying service logic in codebase_rag/tools/ast_grep_service.py. All three access methods share this common foundation, ensuring consistent behavior regardless of how you invoke the search.
Three Methods to Execute Structural Search
1. Agentic Tool (Async Python API)
The primary interface for RAG agents is the create_structural_search_tool function in codebase_rag/tools/structural_search.py. This creates an async-compatible Tool that can be invoked from any asynchronous code or agent framework. The async wrapper implementation spans lines 33-55, utilizing asyncio.to_thread to run the I/O-heavy directory traversal and AST parsing in a background thread.
import asyncio
from codebase_rag.tools.structural_search import create_structural_search_tool
from codebase_rag.tools.ast_grep_service import AstGrepService
async def demo():
# Point the service at the root of the repository you want to search
service = AstGrepService(root_path="/path/to/your/repo")
# Build the structural_search tool
tool = create_structural_search_tool(service)
# Perform a search – the function stored in `tool.function` is async
pattern = "print($A)" # ast-grep pattern
language = "python" # optional language filter
result = await tool.function(pattern, language=language)
print("Matches returned by structural_search:")
print(result)
# Run the demo
asyncio.run(demo())
2. MCP Command (CLI and Remote Clients)
For command-line usage or remote clients, Code-Graph-RAG exposes structural search through the Memgraph Control Protocol (MCP). The command registration appears in codebase_rag/tools/tool_descriptions.py at line 20 (MCP_STRUCTURAL_SEARCH), defining the JSON RPC interface for the tool.
from codebase_rag.mcp.client import MCPClient
# Connect to the running MCP server (adjust host/port as needed)
client = MCPClient(host="localhost", port=7687)
# Perform a structural search
pattern = "def $F($$$ARGS): $$$BODY"
response = client.structural_search(
pattern=pattern,
language="python", # optional
project="my_project" # optional – limits search to a project
)
print("MCP structural_search result:")
print(response)
3. Direct Service API (Low-Level Synchronous)
If you need raw match data without the tool wrapper overhead, instantiate AstGrepService directly from codebase_rag/tools/ast_grep_service.py. The search() method performs synchronous pattern matching and returns structured dictionaries conforming to the StructuralSearchMatch interface.
from codebase_rag.tools.ast_grep_service import AstGrepService
service = AstGrepService(root_path="/path/to/repo")
matches = service.search("print($A)", language="python")
for m in matches:
print(f"{m['file']}:{m['line']}:{m['column']} {m['text']}")
AST Pattern Syntax and Metavariables
Code-Graph-RAG uses ast-grep syntax for pattern definitions, supporting two types of metavariables:
$NAME– Matches a single AST node (e.g., a variable name, expression, or statement)$$$NAME– Matches a repeating subtree (e.g., multiple arguments or a function body)
Common pattern examples include:
print($A)– Matches anyprintcall and captures its argument as$Adef $F($$$ARGS): $$$BODY– Matches function definitions, capturing the name ($F), arguments ($$$ARGS), and body ($$$BODY)
The optional language parameter filters files by programming language (e.g., "python", "typescript", "csharp"), ensuring patterns match only against files the parser can handle.
Result Format and System Requirements
Search results follow a standardized string format where each line represents one match:
file_path:line:column matched_code
If the result set exceeds the internal AST_GREP_MAX_RESULTS limit, the output includes an extra line indicating truncation has occurred.
Before executing, the system verifies the ast-grep binary is available via has_ast_grep(). If the binary is missing, the tool returns AST_GREP_NOT_AVAILABLE rather than failing silently. All search operations run in background threads using asyncio.to_thread to prevent blocking the event loop during filesystem traversal and AST parsing.
Summary
- Three access methods: Agentic Tool (async), MCP command (remote/CLI), and direct Service API (synchronous) all leverage the same core implementation in
codebase_rag/tools/structural_search.py - Pattern syntax: Use
$for single nodes and$$$for repeating subtrees in ast-grep patterns - Source locations: Tool wrapper at lines 33-55 of
structural_search.py, MCP registration at line 20 oftool_descriptions.py, service logic inast_grep_service.py - Requirements: Requires the ast-grep binary installed on the system; results capped at
AST_GREP_MAX_RESULTSwith truncation indicators
Frequently Asked Questions
What is the difference between structural search and regular text search in Code-Graph-RAG?
Structural search matches the Abstract Syntax Tree representation of code using patterns like def $F($$$ARGS), while text search matches raw character sequences. AST matching understands language syntax, preventing false positives when variable names happen to contain keywords, and can capture entire code blocks as single matches using $$$ metavariables.
How do I limit structural search to a specific programming language?
Pass the language parameter when calling any of the three interfaces. Valid values include "python", "typescript", and "csharp". When specified, the search restricts file traversal to extensions associated with that language and ensures the ast-grep parser uses the correct grammar for pattern matching.
What happens if the ast-grep binary is not installed on the system?
The system calls has_ast_grep() to check for binary availability before execution. If ast-grep is missing, all structural search interfaces return the constant AST_GREP_NOT_AVAILABLE immediately, providing a clear error message rather than attempting execution and failing with a system error.
Can I use structural search synchronously or only asynchronously?
Both options exist. The AstGrepService.search() method provides synchronous execution suitable for scripts and blocking contexts. For agent workflows and web servers, the create_structural_search_tool function returns an async wrapper (lines 33-55 of structural_search.py) that offloads work to a background thread while maintaining an async-compatible interface.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →