Skip to content

File Tool

file_tool reads and modifies individual files: read, write, edit (find/replace), copy, move, delete. For directory-level operations (listing a tree, recursively copying/deleting a whole folder), see the Directory Tool instead.

from langchain_mero_tools import make_file_tool

file_tool = make_file_tool(ctx)   # ctx=None for unrestricted access

Input schema

{
    "operation": "read" | "write" | "edit" | "copy" | "move" | "delete",
    "path": str | list[str],       # list only valid for "edit"
    "destination_path": str | None, # required for copy/move
    "content": str | None,         # required for write
    "find": str | None,            # required for edit
    "replace": str | None,         # required for edit
    "mode": "overwrite" | "append" # only used by write, default "overwrite"
}

Operations

Operation Required permission What it does
read READ Returns the full text content of the file.
write WRITE Writes content to the file. mode="overwrite" (default) replaces the file; mode="append" appends. Creates parent directories automatically.
edit WRITE Find/replace: replaces every occurrence of find with replace in the file. Accepts a list of paths to apply the same find/replace across multiple files in one call.
copy WRITE (checked on both source and destination_path) Copies the file, preserving metadata (shutil.copy2).
move WRITE (checked on both source and destination_path) Moves/renames the file.
delete DELETE Deletes the file.

Examples

Read a file

file_tool.invoke({"operation": "read", "path": "config.yaml"})

Write a new file (creates parent directories automatically)

file_tool.invoke({
    "operation": "write",
    "path": "output/report.md",
    "content": "# Report\n\nGenerated automatically.",
})
# "Wrote 39 chars to 'output/report.md' (mode=overwrite)."

Append to a log file

file_tool.invoke({
    "operation": "write",
    "path": "run.log",
    "content": "job finished at 12:00\n",
    "mode": "append",
})

Find/replace in one file

file_tool.invoke({
    "operation": "edit",
    "path": "src/config.py",
    "find": "DEBUG = True",
    "replace": "DEBUG = False",
})
# "Replaced 1 occurrence(s) in 'src/config.py'."

Find/replace across multiple files in one call

This is the one operation that accepts a list of paths — useful for a rename or a config change that spans several files:

file_tool.invoke({
    "operation": "edit",
    "path": ["src/a.py", "src/b.py", "src/c.py"],
    "find": "old_function_name",
    "replace": "new_function_name",
})

The result is a per-file breakdown:

[src/a.py] Replaced 2 occurrence(s) in 'src/a.py'.
[src/b.py] No occurrences of the given 'find' string in 'src/b.py'.
[src/c.py] Replaced 1 occurrence(s) in 'src/c.py'.

If any individual file is denied by the security context (e.g. it's not covered by any PathEntry), only that file's line shows "Denied: ..." — the others still proceed. This lets an agent see exactly which files succeeded.

Copy and move

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

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

Both the source and destination_path are checked against the SecurityContext independently — a scoped agent can't use copy/move to smuggle a file outside its sandbox, and can't overwrite something outside its scope either.

Delete

file_tool.invoke({"operation": "delete", "path": "temp.txt"})

Requires Permission.DELETE specifically — a context with only READ | WRITE will be denied, even though it can otherwise touch the file freely.

Error responses

file_tool never raises for expected failures — it always returns a string:

file_tool.invoke({"operation": "read", "path": "does-not-exist.txt"})
# "Error: 'does-not-exist.txt' is not a file or does not exist."

file_tool.invoke({"operation": "write", "path": "/etc/passwd", "content": "x"})
# "Denied: Path '/etc/passwd' is an absolute path that isn't covered by any
#  configured PathEntry, and its leading segment doesn't match a mounted slug (...)."

See Error Handling for the full pattern.

Notes and edge cases

  • write and edit both require content/find+replace respectively — omitting them returns a plain "Error: ..." string rather than raising.
  • write reads files with errors="replace" on read, so non-UTF-8 bytes don't crash the tool (they're shown as replacement characters instead).
  • copy/move create the destination's parent directories automatically, same as write.
  • Multiple paths are only accepted for edit — passing a list for any other operation returns "Error: multiple paths are only supported for the 'edit' operation."

See also