shelpaからfilesystemへ: ファイル操作と復旧の再設計
Rust製MCPサーバーshelpaを14ツールのfilesystemへ再設計しました。AIシステム開発に使うワーカーの誤操作に備え、ゴミ箱・undo/redo・履歴追跡を実装した記録です。
ファイル操作と復旧に絞った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_file | oldText/newTextによる検索置換。diff出力、dryRun対応 |
create_directory | 再帰的ディレクトリ作成 |
list_directory | [FILE]/[DIR]プレフィックス付き一覧 |
directory_tree | JSON形式の再帰ツリー(除外パターン対応) |
move_file | 移動・リネーム。移動先既存の場合はエラー |
安全な削除と復元
| ツール | 機能 |
|---|---|
delete_file | ファイルを.filesystem/.trash/に移動(永久削除しない) |
list_trash | ゴミ箱の内容一覧 |
restore_file | ゴミ箱から元のパスに復元 |
search_file | ファイルの状態検索。移動・削除の追跡結果を返す |
バージョン履歴(--backupモード限定)
| ツール | 機能 |
|---|---|
undo_file | nバージョン前の状態に復元 |
redo_file | undo後に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不一致 |
AlreadyExists | create_fileの対象が既存、move_file/restore_fileの移動先が既存 |
IoError | ファイルシステムI/Oエラー |
InvalidArgument | 不正なパラメータ(headとtailの同時指定、undoの超過など) |
agent-gatewayで必要だった復旧
agent-gatewayを自作エージェントへ改良したことが、再設計の直接の動機です。AIシステム開発では、ローカルLLMのワーカーがファイルを削除・破壊した際に、復旧と検証を行える仕組みが必要でした。shelpaのワークスペース制限とミラー記録を、ゴミ箱とバージョン履歴へ引き継いでいます。
