Skip to main content
Glama

find_function

Locate function or method definitions in Python or JS/JSX files using AST parsing to quickly find specific code implementations.

Instructions

Find a function or method definition in a Python or JS/JSX file. Uses AST parsers.

Args: function_name (str): Name of the function or method to find

Returns: dict: function lines with their line numbers, start_line, and end_line

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
function_nameYes

Implementation Reference

  • The primary handler for the 'find_function' MCP tool. Parses Python files using ast module to locate function/method definitions by name, including handling classes, decorators, and nested functions. For JS/JSX, delegates to _find_js_function helper. Returns formatted lines with start/end line numbers.
    async def find_function( function_name: str, ) -> Dict[str, Any]: """ Find a function or method definition in a Python or JS/JSX file. Uses AST parsers. Args: function_name (str): Name of the function or method to find Returns: dict: function lines with their line numbers, start_line, and end_line """ if self.current_file_path is None: return {"error": "No file path is set. Use set_file first."} # Check if the file is a supported type (Python or JavaScript/JSX) is_python = self.current_file_path.endswith(".py") is_javascript = self.current_file_path.endswith((".js", ".jsx")) if not (is_python or is_javascript): return { "error": "This tool only works with Python (.py) or JavaScript/JSX (.js, .jsx) files." } try: with open(self.current_file_path, "r", encoding="utf-8") as file: source_code = file.read() lines = source_code.splitlines(True) # Keep line endings # Process JavaScript/JSX files if is_javascript: return self._find_js_function(function_name, source_code, lines) # For Python files, parse the source code to AST tree = ast.parse(source_code) # Find the function in the AST function_node = None class_node = None parent_function = None # Helper function to find a function or method node def find_node(node): nonlocal function_node, class_node, parent_function if isinstance(node, ast.FunctionDef) and node.name == function_name: function_node = node return True # Check for methods in classes elif isinstance(node, ast.ClassDef): for item in node.body: if ( isinstance(item, ast.FunctionDef) and item.name == function_name ): function_node = item class_node = node return True # Check for nested functions elif isinstance(node, ast.FunctionDef): for item in node.body: # Find directly nested function definitions if ( isinstance(item, ast.FunctionDef) and item.name == function_name ): function_node = item # Store parent function information parent_function = node return True # Recursively search for nested functions/methods for child in ast.iter_child_nodes(node): if find_node(child): return True return False # Search for the function in the AST find_node(tree) if not function_node: return { "error": f"Function or method '{function_name}' not found in the file." } # Get the line range for the function start_line = function_node.lineno end_line = 0 # Find the end line by looking at tokens with open(self.current_file_path, "rb") as file: tokens = list(tokenize.tokenize(file.readline)) # Find the function definition token function_def_index = -1 for i, token in enumerate(tokens): if token.type == tokenize.NAME and token.string == function_name: if ( i > 0 and tokens[i - 1].type == tokenize.NAME and tokens[i - 1].string == "def" ): function_def_index = i break if function_def_index == -1: # Fallback - use AST to determine the end # First, get the end_lineno from the function node itself end_line = function_node.end_lineno or start_line # Then walk through all nodes inside the function to find the deepest end_lineno # This handles nested functions and statements properly # Walk through all nodes inside the function to find the deepest end_lineno # This handles nested functions and statements properly for node in ast.walk(function_node): if hasattr(node, "end_lineno") and node.end_lineno: end_line = max(end_line, node.end_lineno) # Specifically look for nested function definitions # by checking for FunctionDef nodes within the function body for node in ast.walk(function_node): if ( isinstance(node, ast.FunctionDef) and node is not function_node ): if hasattr(node, "end_lineno") and node.end_lineno: end_line = max(end_line, node.end_lineno) else: # Find the closing token of the function (either the next function/class at the same level or the end of file) indent_level = tokens[function_def_index].start[ 1 ] # Get the indentation of the function in_function = False nested_level = 0 for token in tokens[function_def_index + 1 :]: current_line = token.start[0] if current_line > start_line: # Start tracking when we're inside the function body if not in_function and token.string == ":": in_function = True continue # Track nested blocks by indentation if in_function: current_indent = token.start[1] # Find a token at the same indentation level as the function definition # but only if we're not in a nested block if ( current_indent <= indent_level and token.type == tokenize.NAME and token.string in ("def", "class") and nested_level == 0 ): end_line = current_line - 1 break # Track nested blocks elif ( current_indent > indent_level and token.type == tokenize.NAME ): if token.string in ("def", "class"): nested_level += 1 # Look for the end of nested blocks elif ( nested_level > 0 and current_indent <= indent_level ): nested_level -= 1 # If we couldn't find the end, use the last line of the file if end_line == 0: end_line = len(lines) # Include decorators if present for decorator in function_node.decorator_list: start_line = min(start_line, decorator.lineno) # Adjust for methods inside classes if class_node: class_body_start = min( item.lineno for item in class_node.body if hasattr(item, "lineno") ) if function_node.lineno == class_body_start: # If this is the first method, include the class definition start_line = class_node.lineno # Normalize line numbers (1-based for API consistency) function_lines = lines[start_line - 1 : end_line] # Format the results similar to the read tool formatted_lines = [] for i, line in enumerate(function_lines, start_line): formatted_lines.append((i, line.rstrip())) result = { "status": "success", "lines": formatted_lines, "start_line": start_line, "end_line": end_line, } # Add parent function information if this is a nested function if parent_function: result["is_nested"] = True result["parent_function"] = parent_function.name return result except Exception as e: return {"error": f"Error finding function: {str(e)}"}
  • Helper for JS/JSX function location. Attempts Babel AST parsing first (via _find_js_function_babel), falls back to regex matching for function declarations, arrow functions, methods, and hooks. Extracts precise line range by brace counting.
    def _find_js_function( self, function_name: str, source_code: str, lines: list ) -> Dict[str, Any]: """ Helper method to find JavaScript/JSX function definitions using Babel AST parsing. Args: function_name (str): Name of the function to find source_code (str): Source code content lines (list): Source code split by lines with line endings preserved Returns: dict: Dictionary with function information """ try: # First try using Babel for accurate parsing if it's available if self.enable_js_syntax_check: babel_result = self._find_js_function_babel( function_name, source_code, lines ) if babel_result and not babel_result.get("error"): return babel_result # Fallback to regex approach if Babel parsing fails or is disabled # Pattern for named function declaration # Matches: function functionName(args) { body } # Also matches: async function functionName(args) { body } function_pattern = re.compile( r"(?:async\s+)?function\s+(?P<functionName>\w+)\s*\((?P<functionArguments>[^()]*)\)\s*{", re.MULTILINE, ) # Pattern for arrow functions with explicit name # Matches: const functionName = (args) => { body } or const functionName = args => { body } # Also matches async variants: const functionName = async (args) => { body } # Also matches component inner functions: const innerFunction = async () => { ... } arrow_pattern = re.compile( r"(?:(?:const|let|var)\s+)?(?P<functionName>\w+)\s*=\s*(?:async\s+)?(?:\((?P<functionArguments>[^()]*)\)|(?P<singleArg>\w+))\s*=>\s*{", re.MULTILINE, ) # Pattern for object method definitions # Matches: functionName(args) { body } in object literals or classes # Also matches: async functionName(args) { body } method_pattern = re.compile( r"(?:^|,|{)\s*(?:async\s+)?(?P<functionName>\w+)\s*\((?P<functionArguments>[^()]*)\)\s*{", re.MULTILINE, ) # Pattern for React hooks like useCallback, useEffect, etc. # Matches: const functionName = useCallback(async () => { ... }, [deps]) hook_pattern = re.compile( r"const\s+(?P<functionName>\w+)\s*=\s*use\w+\((?:async\s+)?\(?[^{]*\)?\s*=>[^{]*{", re.MULTILINE, ) # Search for the function matches = [] # Check all patterns for pattern in [ function_pattern, arrow_pattern, method_pattern, hook_pattern, ]: for match in pattern.finditer(source_code): if match.groupdict().get("functionName") == function_name: matches.append(match) if not matches: return {"error": f"Function '{function_name}' not found in the file."} # Use the first match match = matches[0] start_pos = match.start() # Find the line number for the start start_line = 1 pos = 0 for i, line in enumerate(lines, 1): next_pos = pos + len(line) if pos <= start_pos < next_pos: start_line = i break pos = next_pos # Find the closing brace that matches the opening brace of the function # Count the number of opening and closing braces brace_count = 0 end_pos = start_pos in_string = False string_delimiter = None escaped = False for i in range(start_pos, len(source_code)): char = source_code[i] # Handle strings to avoid counting braces inside strings if not escaped and char in ['"', "'", "`"]: if not in_string: in_string = True string_delimiter = char elif char == string_delimiter: in_string = False # Check for escape character if char == "\\" and not escaped: escaped = True continue escaped = False # Only count braces outside of strings if not in_string: if char == "{": brace_count += 1 elif char == "}": brace_count -= 1 if brace_count == 0: end_pos = i + 1 # Include the closing brace break # Find the end line number end_line = 1 pos = 0 for i, line in enumerate(lines, 1): next_pos = pos + len(line) if pos <= end_pos < next_pos: end_line = i break pos = next_pos # Extract the function lines function_lines = lines[start_line - 1 : end_line] # Format results like the read tool formatted_lines = [] for i, line in enumerate(function_lines, start_line): formatted_lines.append((i, line.rstrip())) result = { "status": "success", "lines": formatted_lines, "start_line": start_line, "end_line": end_line, } return result except Exception as e: return {"error": f"Error finding JavaScript function: {str(e)}"}
  • Advanced JS/JSX helper using Babel CLI to parse AST and extract function locations precisely from metadata output. Falls back gracefully if unavailable.
    def _find_js_function_babel( self, function_name: str, source_code: str, lines: list ) -> Dict[str, Any]: """ Use Babel to parse JavaScript/JSX code and find function definitions. This provides more accurate function location by using proper AST parsing rather than regex pattern matching. Args: function_name (str): Name of the function to find source_code (str): Source code content lines (list): Source code split by lines with line endings preserved Returns: dict: Dictionary with function information or None if Babel fails """ try: # Create a temporary file with the source code with tempfile.NamedTemporaryFile( mode="w", suffix=".jsx", delete=False ) as temp: temp_path = temp.name temp.write(source_code) # Determine the appropriate Babel preset is_jsx = self.current_file_path.endswith(".jsx") presets = ["@babel/preset-react"] if is_jsx else ["@babel/preset-env"] # Use Babel to output the AST as JSON cmd = [ "npx", "babel", "--presets", ",".join(presets), "--plugins", # Add the AST plugin that outputs function locations "babel-plugin-ast-function-metadata", "--no-babelrc", temp_path, "--out-file", "/dev/null", # Output to nowhere, we just want the AST metadata ] # Execute Babel to get the AST with function locations process = subprocess.run(cmd, capture_output=True, text=True) # Clean up the temporary file try: os.unlink(temp_path) except: pass # If Babel execution failed, return None to fall back to regex if process.returncode != 0: return None # Parse the output to find location data output = process.stdout # Look for the JSON that has our function location data location_data = None import json try: # Extract the JSON output from Babel plugin # Format is typically like: FUNCTION_LOCATIONS: {... json data ...} match = re.search(r"FUNCTION_LOCATIONS:\s*({.*})", output, re.DOTALL) if match: json_str = match.group(1) locations = json.loads(json_str) # Find our specific function location_data = locations.get(function_name) except (json.JSONDecodeError, AttributeError) as e: return None if not location_data: return None # Get the line information from the location data start_line = location_data.get("start", {}).get("line", 0) end_line = location_data.get("end", {}).get("line", 0) if start_line <= 0 or end_line <= 0: return None # Extract the function lines function_lines = lines[start_line - 1 : end_line] # Format results like the read tool formatted_lines = [] for i, line in enumerate(function_lines, start_line): formatted_lines.append((i, line.rstrip())) result = { "status": "success", "lines": formatted_lines, "start_line": start_line, "end_line": end_line, "parser": "babel", # Flag that this was parsed with Babel } return result except Exception as e: # If anything goes wrong, return None to fall back to regex approach return None
  • The @self.mcp.tool() decorator registers the find_function handler with the FastMCP server instance.
    async def find_function(

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/danielpodrazka/editor-mcp'

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