filesystem: file operations and recovery

I rebuilt the local-LLM MCP server shelpa as filesystem. I removed the pipeline engine and added undo/redo, trash, and file tracking.

The previous design is in Virtual Pipeline Architecture and Sandbox Design and Lessons Learned.

Why I removed pipeline execution

shelpa offered shelpa_pipe for pipelines and shelpa_write for direct writes. Its grammar allowed only approved commands, and writes left mirror copies in .shelpa/.

Pipeline execution had limited compatibility with LLMs. I redesigned filesystem around file operations and recovery from mistakes.

Reused implementation

I kept path validation and workspace restrictions, added missing features, and assembled a prototype.

I removed parser.rs, executor.rs, types.rs, and their tests, along with the regex, os_pipe, shell-words, and thiserror dependencies.

A Rust MCP filesystem API

I replaced the pipeline engine with standard file operations based on src/filesystem in the official modelcontextprotocol/servers repository.

I reimplemented the TypeScript reference in Rust. The old mirror-recording approach became a .filesystem/ history with trash, version records, and file tracking.

8 basic, 4 delete/restore, and 2 history tools

There are 14 tools in total.

Basic File Operations

ToolFunction
read_text_fileRead files with optional head/tail for partial reads
create_fileCreate new file; errors if file already exists
write_fileCreate or overwrite
edit_fileSearch-and-replace via oldText/newText; diff output; dryRun support
create_directoryRecursive directory creation
list_directoryListing with [FILE]/[DIR] prefixes
directory_treeRecursive JSON tree with exclude pattern support
move_fileMove/rename; errors if destination exists

Safe Delete and Restore

ToolFunction
delete_fileMoves file to .filesystem/.trash/ (never permanently deletes)
list_trashList trash contents
restore_fileRestore from trash to original path
search_fileSearch for file status; tracks moves and deletes

Version History (--backup mode only)

ToolFunction
undo_fileRestore to n versions back
redo_fileAdvance n versions forward after undo

create_file, write_file, edit_file, and move_file record to .filesystem/. Moves append to .filesystem/.moves; deletions log to .filesystem/.deletes.

Two startup modes

filesystem supports --mcp for a single project and --server for a multi-project daemon. The CLI follows the same pattern as ctree and pathfinder.

  filesystem --mcp [--root <ROOT>]    # single project
filesystem --server                  # global daemon
  
Aspect--mcp--server
--root at startupOptional (default: cwd)Not allowed
root in tool callsIgnoredRequired
Multi-projectNoYes
Use caseCLI / standaloneagent-gateway daemon

In --server mode, agent-gateway is expected to launch the persistent process and inject root into every call.

  ServerConfig{
    Name:    "filesystem",
    Command: "filesystem",
    Args:    []string{"--server"},
    Scope:   "global",
}
  

The first message determines the protocol. A leading { selects Line JSON; Content-Length: selects HTTP-style framing compatible with LSP.

.filesystem/ history

The simple mirror became a history system with recovery operations and tracking logs.

Structure in --backup Mode

  .filesystem/
  src/
    main.rs/            # history for src/main.rs
      00001             # version 1 (full content snapshot)
      00002             # version 2
      .head             # current version pointer
  .moves                # append-only rename log (tab-separated)
  .trash/
    00001               # deleted file content
    00001.history/      # deleted file's version history
  .deletes              # append-only delete/restore log
  

Each write appends a full snapshot. This is simpler and faster than storing diffs. .head tracks the current version: undo decrements it and redo increments it. A write after undo clears the redo stack.

move_file relocates the history directory and logs to .moves. delete_file moves the file and history to .trash/ and logs to .deletes. Trash IDs increase monotonically and are never reused.

Without --backup

Only .moves, .trash/, and .deletes are active. Version snapshots, .head, and undo/redo are disabled.

Path Safety and Sandboxing

I retained and strengthened shelpa’s workspace restrictions.

  • Paths are normalized without filesystem access (collapsing .. and .).
  • Absolute paths must remain inside the workspace root or return PathEscape.
  • Workspace-root symlinks resolve at startup, including macOS /tmp → /private/tmp.
  • No shell execution, removing that command-injection path.

list_directory and directory_tree exclude .filesystem, .git, node_modules, target, __pycache__, and common build/cache directories by default. They also apply the root .gitignore.

Error Model

Errors return as MCP tool results with isError: true.

Error KindCondition
PathEscapePath escapes the workspace root
NotFoundFile/directory not found; oldText not found in edit_file
AlreadyExistscreate_file target exists; move_file/restore_file destination exists
IoErrorFilesystem I/O failure
InvalidArgumentInvalid parameter (e.g., both head and tail specified, undo exceeds history)

Recovery for agent-gateway workers

The redesign followed the move of agent-gateway toward a custom agent system. AI system development needed recovery and verification when local-LLM workers deleted or damaged files. I reused shelpa’s workspace boundaries and mirror records in the trash and version-history design.