ファイル操作と復旧に絞ったfilesystem

ローカルLLM向けMCPサーバーshelpaを、ファイル操作用のfilesystemへ作り直しました。パイプライン実行エンジンを撤去し、undo/redo、ゴミ箱、ファイル追跡を追加しています。

旧設計は仮想パイプラインの設計記録とサンドボックス設計と教訓にあります。

パイプライン実行をやめた理由

shelpaはshelpa_pipeでパイプラインを実行し、shelpa_writeで直接書き込みました。許可コマンドだけを扱う文法を設け、書き込み時は.shelpa/にミラーコピーを残していました。

パイプライン実行はLLMとの相性に限界がありました。filesystemでは、ファイル操作と誤操作からの復旧を中心に設計を変えています。

再利用した実装

パス検証とワークスペース制限を残し、不足していた機能を加えてプロトタイプを作りました。

parser.rs、executor.rs、types.rsと関連テストを削除しました。依存クレートからもregex、os_pipe、shell-words、thiserrorを外しています。

RustによるMCPファイルシステムAPI

パイプライン実行を撤去し、MCP公式サーバーのmodelcontextprotocol/serversにあるsrc/filesystemを参考に、標準的なファイル操作へ置き換えました。

TypeScriptのリファレンス実装をRustで再実装しました。shelpaのミラー記録を.filesystem/の履歴へ発展させ、ゴミ箱、バージョン履歴、ファイル追跡を加えています。

基本操作8種・削除復元4種・履歴2種

合計14ツールです。

基本ファイル操作

ツール機能
read_text_fileファイル読み取り。head/tailで部分読み取り対応
create_file新規作成。既存ファイルがある場合はエラー
write_file作成・上書きの両方に対応
edit_fileoldText/newTextによる検索置換。diff出力、dryRun対応
create_directory再帰的ディレクトリ作成
list_directory[FILE]/[DIR]プレフィックス付き一覧
directory_treeJSON形式の再帰ツリー(除外パターン対応)
move_file移動・リネーム。移動先既存の場合はエラー

安全な削除と復元

ツール機能
delete_fileファイルを.filesystem/.trash/に移動(永久削除しない)
list_trashゴミ箱の内容一覧
restore_fileゴミ箱から元のパスに復元
search_fileファイルの状態検索。移動・削除の追跡結果を返す

バージョン履歴(--backupモード限定)

ツール機能
undo_filenバージョン前の状態に復元
redo_fileundo後にnバージョン先に進める

create_file、write_file、edit_file、move_fileは.filesystem/へ記録します。移動ログは.filesystem/.moves、削除ログは.filesystem/.deletesに残ります。

2つの起動モード

filesystemは--mcpと--serverを持ちます。ctree、pathfinderとCLIを揃え、単一プロジェクトと複数プロジェクトのデーモンで使い分けます。

  filesystem --mcp [--root <ROOT>]    # シングルプロジェクト
filesystem --server                  # グローバルデーモン
  
観点--mcp--server
--root(起動時)任意(デフォルト: cwd)受け付けない
ツール呼び出しのroot無視必須
マルチプロジェクト不可可
用途CLI / スタンドアロンagent-gatewayデーモン

--serverではagent-gatewayから常駐プロセスとして起動し、ゲートウェイが各呼び出しにrootを注入する想定です。

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

先頭メッセージでプロトコルを判定します。先頭バイトが{ならLine JSON、Content-Length:ならLSP互換のHTTPスタイルフレーミングです。

.filesystem/の履歴

ミラーコピーを、復旧操作と追跡ログを持つ履歴に変えました。

--backupモード時の構造

  .filesystem/
  src/
    main.rs/            # src/main.rsの履歴
      00001             # バージョン1(全文スナップショット)
      00002             # バージョン2
      .head             # 現在のバージョンポインタ
  .moves                # append-onlyのリネームログ(タブ区切り)
  .trash/
    00001               # 削除されたファイル内容
    00001.history/      # 削除されたファイルのバージョン履歴
  .deletes              # append-onlyの削除・復元ログ
  

書き込みごとに全文スナップショットを追記します。diffより単純で、処理も速い方式です。.headが現在のバージョンを示し、undoで減らし、redoで増やします。undo後に書き込むとredoスタックを消去します。

move_fileは履歴ディレクトリも移し、.movesへ追記します。delete_fileはファイルと履歴を.trash/へ移し、.deletesへ記録します。ゴミ箱IDは単調増加し、再利用しません。

--backupなしの場合

.moves、.trash/、.deletesだけを使います。バージョンスナップショット、.head、undo/redoは無効です。

パス安全性とサンドボックス

shelpaのワークスペース制限を引き継いで強化しました。

  • パスはファイルシステムアクセスなしで正規化(..と.を折りたたむ)
  • 絶対パスもワークスペースルート内でなければPathEscapeエラー
  • シンボリンクは起動時に解決(macOSの/tmp → /private/tmpに対応)
  • シェル実行なし。コマンドインジェクションの攻撃面がない

list_directoryとdirectory_treeは.filesystem、.git、node_modules、target、__pycache__などを既定で除外します。ルートの.gitignoreも適用します。

エラーモデル

エラーはisError: trueのMCPツール結果で返します。

エラー種別発生条件
PathEscapeパスがワークスペースルートを逸脱
NotFoundファイル/ディレクトリが見つからない、edit_fileのoldText不一致
AlreadyExistscreate_fileの対象が既存、move_file/restore_fileの移動先が既存
IoErrorファイルシステムI/Oエラー
InvalidArgument不正なパラメータ(headとtailの同時指定、undoの超過など)

agent-gatewayで必要だった復旧

agent-gatewayを自作エージェントへ改良したことが、再設計の直接の動機です。AIシステム開発では、ローカルLLMのワーカーがファイルを削除・破壊した際に、復旧と検証を行える仕組みが必要でした。shelpaのワークスペース制限とミラー記録を、ゴミ箱とバージョン履歴へ引き継いでいます。