Skip to content

Directory Tool

directory_tool inspects and manipulates directories: list a tree view, create, copy, move, delete. For operating on a single file, see the File Tool.

from langchain_mero_tools import make_directory_tool

directory_tool = make_directory_tool(ctx)   # ctx=None for unrestricted access

Input schema

{
    "operation": "list" | "create" | "copy" | "move" | "delete",
    "path": str,
    "destination_path": str | None,  # required for copy/move
    "glob": list[str] | None,       # list only — filters files shown by "list"
    "depth": int,                   # default 1; -1 = all (capped, see below)
    "ignore": list[str] | None,     # regex or plain substrings to skip
    "include_hidden": bool           # default False — dotfiles/dotdirs skipped otherwise
}

glob/depth/ignore/include_hidden only apply to list. copy, move, and delete are always fully recursive — there's no way to partially copy or delete a directory tree with this tool (use File Tool for single-file granularity within a directory).

Operations

Operation Required permission What it does
list READ Returns an ASCII tree view of the directory, respecting depth, glob, ignore, include_hidden.
create WRITE Creates the directory (and any missing parents), exist_ok=True.
copy WRITE (both source and destination_path) Recursively copies the directory tree (shutil.copytree, dirs_exist_ok=True).
move WRITE (both source and destination_path) Recursively moves/renames the directory.
delete DELETE Recursively deletes the directory and everything in it (shutil.rmtree).

Examples

List a tree (default: one level deep, no hidden files)

directory_tool.invoke({"operation": "list", "path": "./workspace"})
workspace/
├── README.md
├── src/
└── tests/

List deeper, with hidden files, ignoring build artifacts

directory_tool.invoke({
    "operation": "list",
    "path": "./workspace",
    "depth": 3,
    "include_hidden": True,
    "ignore": ["__pycache__", "node_modules", r"\.egg-info$"],
})

ignore entries are checked as both a regex (re.search) and a glob (fnmatch) against the entry name — whichever matches first wins, so you can freely mix plain substrings, glob patterns, and regex without prefixing anything (unlike the stricter, explicitly-typed matching used for allowed_commands/denied_commands — see Security & Sandboxing for why command matching is stricter than directory-listing matching).

List only certain file types

directory_tool.invoke({
    "operation": "list",
    "path": "./src",
    "depth": -1,          # all levels (capped — see below)
    "glob": ["*.py"],     # only show files matching one of these globs
})

Note: glob only filters files in the listing, not directories — directories are always shown so you can still see the structure.

Create a directory

directory_tool.invoke({"operation": "create", "path": "./workspace/output/logs"})
# "Created directory './workspace/output/logs'."

Recursively copy, move, delete

directory_tool.invoke({
    "operation": "copy",
    "path": "./workspace/draft",
    "destination_path": "./workspace/archive/draft",
})

directory_tool.invoke({
    "operation": "move",
    "path": "./workspace/draft",
    "destination_path": "./workspace/final",
})

directory_tool.invoke({"operation": "delete", "path": "./workspace/temp"})
# "Deleted directory './workspace/temp' (recursive)."

delete requires Permission.DELETE specifically, same as File Tool's delete.

Depth and output-size caps

Two hard caps protect against a huge/deep tree blowing up the agent's context window, regardless of what depth is requested:

Cap Value Behavior when hit
Max depth 12 levels depth=-1 (unlimited) or any depth > 12 is silently capped to 12, with a [depth capped to 12 to avoid excessive output] notice appended to the output.
Max entries 2000 total Once 2000 entries have been listed across the whole tree, listing stops with a [entry list truncated at 2000 entries] notice.

Both notices appear directly in the tool's string output, so an agent (or you, reading the result) always knows if what it's looking at is partial.

Error responses

directory_tool.invoke({"operation": "list", "path": "not-a-directory.txt"})
# "Error: 'not-a-directory.txt' is not a directory or does not exist."

directory_tool.invoke({"operation": "delete", "path": "/"})
# "Denied: Path '/' is outside the allowed scope [...] for context '...'."

Notes and edge cases

  • An empty directory listing returns the literal string "(empty directory)" rather than an empty string.
  • Permission errors while walking a subdirectory (e.g. restrictive OS permissions on a nested folder) are caught per-directory and shown as [permission denied] in that branch of the tree, rather than failing the whole listing.
  • create is idempotent — creating a directory that already exists succeeds silently (exist_ok=True), it doesn't error.

See also