list_directory
List directory contents with file metadata, respecting configured path and depth restrictions to enforce policy compliance.
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)The actual implementation of the list_directory tool handler. Lists directory entries with metadata (name, kind, size, modified time), enforces path policy (check_path_policy) and depth policy (max_directory_depth), and logs results via append_log_entry.
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)Registers list_directory as an MCP tool on the FastMCP server via mcp.tool()(list_directory) at line 29.
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:2-14 (registration)Re-exports list_directory from tools.file_tools and includes it in __all__.
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)Lists 'list_directory' as one of the configured MCP tools in AIRG_MCP_TOOLS for config management.
AIRG_MCP_TOOLS = [ "server_info", "restore_backup", "execute_command", "read_file", "write_file", "edit_file", "delete_file", "list_directory", ] - tests/test_attacker_suite.py:210-225 (helper)Test that exercises list_directory to verify it returns directory contents (e.g., 'demo.txt').
def test_file_tools_read_write_list_delete_flow(self): (self.workspace / "nested").mkdir(parents=True, exist_ok=True) write_result = write_file("nested/demo.txt", "hello world") self.assertIn("Successfully wrote", write_result) read_result = read_file("nested/demo.txt") self.assertEqual(read_result, "hello world") listing = list_directory("nested") self.assertIn("demo.txt", listing) delete_result = delete_file("nested/demo.txt") self.assertIn("Successfully deleted", delete_result) def test_edit_file_flow(self): self._write("nested/edit.txt", "alpha beta gamma")