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