Skip to main content
Glama
SDGLBL
by SDGLBL

directory_tree

Visualize file and directory structures with customizable depth and filtering options to analyze project layouts.

Instructions

Get a recursive tree view of files and directories with customizable depth and filtering.

Returns a structured view of the directory tree with files and subdirectories. Directories are marked with trailing slashes. The output is formatted as an indented list for readability. By default, common development directories like .git, node_modules, and venv are noted but not traversed unless explicitly requested. Only works within allowed directories.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
pathYesThe path to the directory to view
depthNoThe maximum depth to traverse (0 for unlimited)
include_filteredNoInclude directories that are normally filtered

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

Implementation Reference

  • The `call` method in DirectoryTreeTool class containing the core implementation of the `directory_tree` tool: validates inputs, checks permissions, recursively builds directory tree with depth and filter controls, formats as indented text with stats.
    async def call(
        self,
        ctx: MCPContext,
        **params: Unpack[DirectoryTreeToolParams],
    ) -> str:
        """Execute the tool with the given parameters.
    
        Args:
            ctx: MCP context
            **params: Tool parameters
    
        Returns:
            Tool result
        """
        tool_ctx = self.create_tool_context(ctx)
    
        # Extract parameters
        path: DirectoryPath = params["path"]
        depth = params.get("depth", 3)  # Default depth is 3
        include_filtered = params.get("include_filtered", False)  # Default to False
    
        # Validate path parameter
        path_validation = self.validate_path(path)
        if path_validation.is_error:
            await tool_ctx.error(path_validation.error_message)
            return f"Error: {path_validation.error_message}"
    
        await tool_ctx.info(
            f"Getting directory tree: {path} (depth: {depth}, include_filtered: {include_filtered})"
        )
    
        # Check if path is allowed
        allowed, error_msg = await self.check_path_allowed(path, tool_ctx)
        if not allowed:
            return error_msg
    
        try:
            dir_path = Path(path)
    
            # Check if path exists
            exists, error_msg = await self.check_path_exists(path, tool_ctx)
            if not exists:
                return error_msg
    
            # Check if path is a directory
            is_dir, error_msg = await self.check_is_directory(path, tool_ctx)
            if not is_dir:
                return error_msg
    
            # Get filtered directories from permission manager
            filtered_patterns = set(self.permission_manager.excluded_patterns)
    
            # Log filtering settings
            await tool_ctx.info(
                f"Directory tree filtering: include_filtered={include_filtered}"
            )
    
            # Check if a directory should be filtered
            def should_filter(current_path: Path) -> bool:
                # Don't filter if it's the explicitly requested path
                if str(current_path.absolute()) == str(dir_path.absolute()):
                    # Don't filter explicitly requested paths
                    return False
    
                # Filter based on directory name if filtering is enabled
                return current_path.name in filtered_patterns and not include_filtered
    
            # Track stats for summary
            stats = {
                "directories": 0,
                "files": 0,
                "skipped_depth": 0,
                "skipped_filtered": 0,
            }
    
            # Build the tree recursively
            async def build_tree(
                current_path: Path, current_depth: int = 0
            ) -> list[dict[str, Any]]:
                result: list[dict[str, Any]] = []
    
                # Skip processing if path isn't allowed, unless we're including filtered dirs
                # and this path is only excluded due to filtering patterns
                if not include_filtered and not self.is_path_allowed(str(current_path)):
                    return result
                elif include_filtered:
                    # When including filtered directories, we need to check if the path
                    # would be allowed if we ignore pattern-based exclusions
                    # For now, we'll be more permissive and only check the basic allowed paths
                    path_in_allowed = False
                    resolved_path = Path(str(current_path)).resolve()
                    for allowed_path in self.permission_manager.allowed_paths:
                        try:
                            resolved_path.relative_to(allowed_path)
                            path_in_allowed = True
                            break
                        except ValueError:
                            continue
                    if not path_in_allowed:
                        return result
    
                try:
                    # Sort entries: directories first, then files alphabetically
                    entries = sorted(
                        current_path.iterdir(), key=lambda x: (not x.is_dir(), x.name)
                    )
    
                    for entry in entries:
                        if entry.is_dir():
                            stats["directories"] += 1
                            entry_data: dict[str, Any] = {
                                "name": entry.name,
                                "type": "directory",
                            }
    
                            # Check if we should filter this directory
                            if should_filter(entry):
                                entry_data["skipped"] = "filtered-directory"
                                stats["skipped_filtered"] += 1
                                result.append(entry_data)
                                continue
    
                            # Check depth limit (if enabled)
                            if depth > 0 and current_depth >= depth:
                                entry_data["skipped"] = "depth-limit"
                                stats["skipped_depth"] += 1
                                result.append(entry_data)
                                continue
    
                            # Process children recursively with depth increment
                            entry_data["children"] = await build_tree(
                                entry, current_depth + 1
                            )
                            result.append(entry_data)
                        else:
                            # Skip files that aren't allowed (with same logic as directories)
                            if not include_filtered and not self.is_path_allowed(
                                str(entry)
                            ):
                                continue
                            elif include_filtered:
                                # When including filtered directories, check basic path allowance
                                path_in_allowed = False
                                resolved_path = Path(str(entry)).resolve()
                                for (
                                    allowed_path
                                ) in self.permission_manager.allowed_paths:
                                    try:
                                        resolved_path.relative_to(allowed_path)
                                        path_in_allowed = True
                                        break
                                    except ValueError:
                                        continue
                                if not path_in_allowed:
                                    continue
    
                            # Files should be at the same level check as directories
                            if depth <= 0 or current_depth < depth:
                                stats["files"] += 1
                                # Add file entry
                                result.append({"name": entry.name, "type": "file"})
    
                except Exception as e:
                    await tool_ctx.warning(f"Error processing {current_path}: {str(e)}")
    
                return result
    
            # Format the tree as a simple indented structure
            def format_tree(
                tree_data: list[dict[str, Any]], level: int = 0
            ) -> list[str]:
                lines = []
    
                for item in tree_data:
                    # Indentation based on level
                    indent = "  " * level
    
                    # Format based on type
                    if item["type"] == "directory":
                        if "skipped" in item:
                            lines.append(
                                f"{indent}{item['name']}/ [skipped - {item['skipped']}]"
                            )
                        else:
                            lines.append(f"{indent}{item['name']}/")
                            # Add children with increased indentation if present
                            if "children" in item:
                                lines.extend(format_tree(item["children"], level + 1))
                    else:
                        # File
                        lines.append(f"{indent}{item['name']}")
    
                return lines
    
            # Build tree starting from the requested directory
            tree_data = await build_tree(dir_path)
    
            # Format as simple text
            formatted_output = "\n".join(format_tree(tree_data))
    
            # Add stats summary
            summary = (
                f"\nDirectory Stats: {stats['directories']} directories, {stats['files']} files "
                f"({stats['skipped_depth']} skipped due to depth limit, "
                f"{stats['skipped_filtered']} filtered directories skipped)"
            )
    
            await tool_ctx.info(
                f"Generated directory tree for {path} (depth: {depth}, include_filtered: {include_filtered})"
            )
    
            return formatted_output + summary
        except Exception as e:
            await tool_ctx.error(f"Error generating directory tree: {str(e)}")
            return f"Error generating directory tree: {str(e)}"
  • Input schema definitions using Annotated Pydantic Fields for path, depth, and include_filtered parameters, plus TypedDict DirectoryTreeToolParams.
    DirectoryPath = Annotated[
        str,
        Field(
            description="The path to the directory to view",
            title="Path",
        ),
    ]
    
    Depth = Annotated[
        int,
        Field(
            default=3,
            description="The maximum depth to traverse (0 for unlimited)",
            title="Depth",
        ),
    ]
    
    IncludeFiltered = Annotated[
        bool,
        Field(
            default=False,
            description="Include directories that are normally filtered",
            title="Include Filtered",
        ),
    ]
    
    
    class DirectoryTreeToolParams(TypedDict):
        """Parameters for the DirectoryTreeTool.
    
        Attributes:
            path: The path to the directory to view
            depth: The maximum depth to traverse (0 for unlimited)
            include_filtered: Include directories that are normally filtered
        """
    
        path: DirectoryPath
        depth: Depth
        include_filtered: IncludeFiltered
  • The `register` method that wraps the tool call in a decorated function `directory_tree` registered to the MCP server with name 'directory_tree' and description.
    def register(self, mcp_server: FastMCP) -> None:
        """Register this directory tree tool with the MCP server.
    
        Creates a wrapper function with explicitly defined parameters that match
        the tool's parameter schema and registers it with the MCP server.
    
        Args:
            mcp_server: The FastMCP server instance
        """
        tool_self = self  # Create a reference to self for use in the closure
    
        @mcp_server.tool(name=self.name, description=self.description)
        async def directory_tree(
            ctx: MCPContext,
            path: DirectoryPath,
            depth: Depth,
            include_filtered: IncludeFiltered,
        ) -> str:
            ctx = get_context()
            return await tool_self.call(
                ctx, path=path, depth=depth, include_filtered=include_filtered
            )
  • Instantiation of DirectoryTreeTool in `get_filesystem_tools`, which is called by `register_filesystem_tools` to create and register all filesystem tools including directory_tree.
    def get_filesystem_tools(permission_manager: PermissionManager) -> list[BaseTool]:
        """Create instances of all filesystem tools.
    
        Args:
            permission_manager: Permission manager for access control
    
        Returns:
            List of filesystem tool instances
        """
        return [
            ReadTool(permission_manager),
            Write(permission_manager),
            Edit(permission_manager),
            MultiEdit(permission_manager),
            DirectoryTreeTool(permission_manager),
            Grep(permission_manager),
            ContentReplaceTool(permission_manager),
            GrepAstTool(permission_manager),
        ]
  • Top-level registration in `register_all_tools` that calls `register_filesystem_tools`, triggering instantiation and registration of the directory_tree tool.
    # Register all filesystem tools
    filesystem_tools = register_filesystem_tools(mcp_server, permission_manager)
    for tool in filesystem_tools:
        all_tools[tool.name] = tool
Behavior4/5

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

With no annotations provided, the description carries the full burden and does well by disclosing key behaviors: output format (indented list with trailing slashes), default exclusions for development directories, and the constraint of working only within allowed directories. It doesn't mention performance implications or error handling, but covers essential operational context.

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 four sentences that each add value: purpose statement, output format details, behavioral constraints, and operational limits. No redundant information, and it's front-loaded with the core functionality.

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 moderate complexity, 100% schema coverage, and the presence of an output schema, the description is complete enough. It covers purpose, output format, behavioral traits, and constraints without needing to explain return values (handled by output schema) or parameter details (handled by schema).

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

Parameters3/5

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

Schema description coverage is 100%, so the schema already documents all three parameters thoroughly. The description adds marginal value by mentioning 'customizable depth and filtering' and explaining the effect of 'include_filtered' on development directories, but doesn't provide additional syntax or format details beyond what the schema provides.

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 verb 'Get' and resource 'recursive tree view of files and directories', specifying it's a structured view with directories marked by trailing slashes. It distinguishes from sibling tools like 'read' or 'grep' by focusing on hierarchical directory structure rather than file content or pattern matching.

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: 'Only works within allowed directories' and mentions default exclusions for development directories. However, it doesn't explicitly state when to use this tool versus alternatives like 'read' for file content or 'grep' for searching within files, leaving some ambiguity about tool selection.

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/SDGLBL/mcp-claude-code'

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