list_directory
List directory entries with metadata while enforcing path and depth restrictions to prevent unauthorized file access.
Instructions
List directory entries with metadata, honoring path and depth policy.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | ||
| ctx | No |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| result | Yes |
Implementation Reference
- src/tools/file_tools.py:287-344 (handler)Main handler for list_directory tool. Resolves path, checks policy (path whitelist/blocklist, depth limit), reads directory entries with metadata (name, type, size, modification time), and returns formatted listing.
def list_directory(path: str, ctx: Context | None = None) -> str: """List directory entries with metadata, honoring path and depth policy.""" context_tokens = activate_runtime_context(ctx) path = str(pathlib.Path(WORKSPACE_ROOT) / path) if not os.path.isabs(path) else path try: refresh_policy_if_changed() path_check = check_path_policy(path, tool="list_directory") if path_check: result = PolicyResult(allowed=False, reason=path_check[0], decision_tier="blocked", matched_rule=path_check[1]) else: result = PolicyResult(allowed=True, reason="allowed", decision_tier="allowed", matched_rule=None) if result.allowed: if not os.path.exists(path): append_log_entry(build_log_entry("list_directory", result, path=path, error="path not found")) return f"Error: path not found: {path}" if not os.path.isdir(path): append_log_entry(build_log_entry("list_directory", result, path=path, error="not a directory")) return f"Error: '{path}' is a file, not a directory" depth = relative_depth(path) max_depth = POLICY.get("allowed", {}).get("max_directory_depth", 5) if depth > max_depth: result = PolicyResult( allowed=False, reason=f"Directory depth {depth} exceeds the policy limit of {max_depth} (allowed.max_directory_depth): '{path}'", decision_tier="blocked", matched_rule="allowed.max_directory_depth", ) append_log_entry(build_log_entry("list_directory", result, path=path)) if not result.allowed: return f"[POLICY BLOCK] {result.reason}" lines = [f"Contents of {path}:"] try: entries = sorted(os.scandir(path), key=lambda e: (e.is_file(), e.name)) except OSError as e: return f"Error reading directory: {e}" for entry in entries: try: stat = entry.stat(follow_symlinks=False) mtime = datetime.datetime.fromtimestamp(stat.st_mtime, datetime.UTC).isoformat().replace("+00:00", "Z") kind = "file" if entry.is_file(follow_symlinks=False) else "directory" size = f"{stat.st_size} bytes" if kind == "file" else "-" lines.append(f" {entry.name} [{kind}] size={size} modified={mtime}") except OSError: lines.append(f" {entry.name} [unreadable]") if len(lines) == 1: lines.append(" (empty)") return "\n".join(lines) finally: reset_runtime_context(context_tokens) - src/server.py:21-31 (registration)list_directory is registered as an MCP tool via mcp.tool()(tool) decoration loop in the FastMCP server.
for tool in [ server_info, restore_backup, execute_command, read_file, write_file, edit_file, delete_file, list_directory, ]: mcp.tool()(tool) - src/tools/__init__.py:1-14 (registration)list_directory is exported from the tools package via __init__.py.
from .command_tools import execute_command, server_info from .file_tools import delete_file, edit_file, list_directory, read_file, write_file from .restore_tools import restore_backup __all__ = [ "server_info", "execute_command", "read_file", "write_file", "edit_file", "delete_file", "list_directory", "restore_backup", ] - src/mcp_config_manager.py:30-39 (registration)list_directory is listed as one of the AIRG MCP tools for configuration management (used for generating MCP config for agent platforms).
AIRG_MCP_TOOLS = [ "server_info", "restore_backup", "execute_command", "read_file", "write_file", "edit_file", "delete_file", "list_directory", ]