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

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

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(
Behavior3/5

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

With no annotations provided, the description carries full burden. It discloses the tool's behavior: it finds function/method definitions using AST parsers and returns line numbers and ranges. However, it doesn't mention error handling (e.g., if file doesn't exist or function isn't found), performance characteristics, or whether it searches recursively in directories. It adds value beyond the minimal schema but lacks comprehensive behavioral details.

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

Conciseness5/5

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

The description is efficiently structured with a clear purpose statement followed by Args and Returns sections. Every sentence earns its place: the first sentence defines the tool's core functionality, and the subsequent lines provide essential parameter and return value information without redundancy.

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

Completeness4/5

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

Given the tool's moderate complexity (AST parsing across two languages), no annotations, and an output schema that likely defines the return dict structure, the description is reasonably complete. It covers what the tool does, the parameter meaning, and return format. However, it could better address error cases or language-specific quirks (e.g., JSX vs JS differences) for full completeness.

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?

The schema has 0% description coverage, so the description must compensate. It clearly explains the single parameter 'function_name' as 'Name of the function or method to find', which adds essential semantic meaning not present in the bare schema. However, it doesn't specify case-sensitivity, partial matching, or handling of nested methods (e.g., 'ClassName.method_name'), leaving some ambiguity.

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 specific action ('Find a function or method definition'), the target resources ('in a Python or JS/JSX file'), and the implementation method ('Uses AST parsers'). It distinguishes itself from siblings like 'find_line' (general line search) and 'skim' (likely file overview) by focusing specifically on function/method definitions with AST parsing.

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

Usage Guidelines3/5

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

The description implies usage context by specifying 'Python or JS/JSX file' and 'function or method definition', suggesting it should be used when searching for function definitions in those file types. However, it doesn't explicitly state when NOT to use it or name alternatives like 'find_line' for non-function searches or 'skim' for file overviews.

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

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