Python版 MCP-server-filesystem

### MCP filesystemとは？

　 素のままではClaudeのプロンプトから、PCのファイルシステムに対して、何も手が出ませんし、情報を得ることもできません。
  MCPサーバーによって、ファイルをリストしたり、探したり、コピー、削除などの
  操作が可能になります。

### なぜPython版か?


mcp-installerというMCPサーバーによって自動的にインストールできるMCPファイルシステムは、Node.jsで動作するが、MacOSでインストールできないケースが頻発したのと、実行速度の点を考慮して、uv版のMCP-server-filesystemをインストールした。その手順を示す。（uvは最新のpython実行環境）

### MCPライブラリをインストール
(/Users/＜ユーザー名＞  ディレクトリで)

```
# uvを使って直接MCPライブラリをインストールしてプロジェクトを作成
uv init MCP-server-filesystem
cd MCP-server-filesystem
uv add "mcp[cli]"
```

### server.py

以下のコードを server.pyにコピペして　フォルダ MCP-server-filesystem
にセーブする。
（以下のserver.py用のコードは、macOS用）



<summary>macOS版のserver.pyのコード</summary>
（詳細をクリックすると展開してコードが表示されます）
<details>

```python

from mcp.server.fastmcp import FastMCP
import os
import json
import shutil
from pathlib import Path
from datetime import datetime
from typing import List, Dict, Optional

# MCPサーバーを初期化
mcp = FastMCP("FileOperationsServer")

# ファイル読み取り
@mcp.tool()
def read_file(file_path: str, encoding: str = "utf-8") -> str:
    """Read the contents of a text file"""
    try:
        with open(file_path, 'r', encoding=encoding) as file:
            return file.read()
    except FileNotFoundError:
        return f"Error: File '{file_path}' not found"
    except Exception as e:
        return f"Error reading file: {str(e)}"

# ファイル書き込み
@mcp.tool()
def write_file(file_path: str, content: str, encoding: str = "utf-8", mode: str = "w") -> str:
    """Write content to a file. Mode can be 'w' (write) or 'a' (append)"""
    try:
        # ディレクトリが存在しない場合は作成
        directory = os.path.dirname(file_path)
        if directory and not os.path.exists(directory):
            os.makedirs(directory)
        
        with open(file_path, mode, encoding=encoding) as file:
            file.write(content)
        return f"Successfully {'wrote to' if mode == 'w' else 'appended to'} '{file_path}'"
    except Exception as e:
        return f"Error writing to file: {str(e)}"

# ファイル削除
@mcp.tool()
def delete_file(file_path: str) -> str:
    """Delete a file"""
    try:
        if os.path.exists(file_path):
            os.remove(file_path)
            return f"Successfully deleted '{file_path}'"
        else:
            return f"Error: File '{file_path}' not found"
    except Exception as e:
        return f"Error deleting file: {str(e)}"

# ファイル移動/リネーム
@mcp.tool()
def move_file(source_path: str, destination_path: str) -> str:
    """Move or rename a file"""
    try:
        # 移動先のディレクトリが存在しない場合は作成
        destination_dir = os.path.dirname(destination_path)
        if destination_dir and not os.path.exists(destination_dir):
            os.makedirs(destination_dir)
        
        shutil.move(source_path, destination_path)
        return f"Successfully moved '{source_path}' to '{destination_path}'"
    except FileNotFoundError:
        return f"Error: Source file '{source_path}' not found"
    except Exception as e:
        return f"Error moving file: {str(e)}"

# ファイルコピー
@mcp.tool()
def copy_file(source_path: str, destination_path: str) -> str:
    """Copy a file to a new location"""
    try:
        # コピー先のディレクトリが存在しない場合は作成
        destination_dir = os.path.dirname(destination_path)
        if destination_dir and not os.path.exists(destination_dir):
            os.makedirs(destination_dir)
        
        shutil.copy2(source_path, destination_path)
        return f"Successfully copied '{source_path}' to '{destination_path}'"
    except FileNotFoundError:
        return f"Error: Source file '{source_path}' not found"
    except Exception as e:
        return f"Error copying file: {str(e)}"

# ディレクトリ作成
@mcp.tool()
def create_directory(directory_path: str) -> str:
    """Create a new directory (including parent directories if needed)"""
    try:
        os.makedirs(directory_path, exist_ok=True)
        return f"Successfully created directory '{directory_path}'"
    except Exception as e:
        return f"Error creating directory: {str(e)}"

# ディレクトリ削除
@mcp.tool()
def delete_directory(directory_path: str, recursive: bool = False) -> str:
    """Delete a directory. Set recursive=True to delete non-empty directories"""
    try:
        if not os.path.exists(directory_path):
            return f"Error: Directory '{directory_path}' not found"
        
        if recursive:
            shutil.rmtree(directory_path)
        else:
            os.rmdir(directory_path)
        
        return f"Successfully deleted directory '{directory_path}'"
    except OSError as e:
        if "not empty" in str(e):
            return f"Error: Directory '{directory_path}' is not empty. Use recursive=True to force deletion"
        return f"Error deleting directory: {str(e)}"
    except Exception as e:
        return f"Error deleting directory: {str(e)}"

# ファイル・ディレクトリ一覧
@mcp.tool()
def list_directory(directory_path: str = ".", show_hidden: bool = False) -> List[Dict]:
    """List files and directories in the specified path"""
    try:
        items = []
        for item in os.listdir(directory_path):
            if not show_hidden and item.startswith('.'):
                continue
            
            item_path = os.path.join(directory_path, item)
            stat_info = os.stat(item_path)
            
            items.append({
                "name": item,
                "type": "directory" if os.path.isdir(item_path) else "file",
                "size": stat_info.st_size,
                "modified": datetime.fromtimestamp(stat_info.st_mtime).strftime("%Y-%m-%d %H:%M:%S"),
                "path": item_path
            })
        
        return sorted(items, key=lambda x: (x["type"] == "file", x["name"]))
    except FileNotFoundError:
        return [{"error": f"Directory '{directory_path}' not found"}]
    except Exception as e:
        return [{"error": f"Error listing directory: {str(e)}"}]

# ファイル情報取得
@mcp.tool()
def get_file_info(file_path: str) -> Dict:
    """Get detailed information about a file or directory"""
    try:
        if not os.path.exists(file_path):
            return {"error": f"Path '{file_path}' not found"}
        
        stat_info = os.stat(file_path)
        path_obj = Path(file_path)
        
        info = {
            "path": file_path,
            "name": path_obj.name,
            "type": "directory" if os.path.isdir(file_path) else "file",
            "size": stat_info.st_size,
            "created": datetime.fromtimestamp(stat_info.st_ctime).strftime("%Y-%m-%d %H:%M:%S"),
            "modified": datetime.fromtimestamp(stat_info.st_mtime).strftime("%Y-%m-%d %H:%M:%S"),
            "accessed": datetime.fromtimestamp(stat_info.st_atime).strftime("%Y-%m-%d %H:%M:%S"),
            "permissions": oct(stat_info.st_mode)[-3:],
            "absolute_path": os.path.abspath(file_path)
        }
        
        if os.path.isfile(file_path):
            info["extension"] = path_obj.suffix
        
        return info
    except Exception as e:
        return {"error": f"Error getting file info: {str(e)}"}

# ファイル検索
@mcp.tool()
def search_files(directory_path: str, pattern: str, case_sensitive: bool = False) -> List[str]:
    """Search for files matching a pattern in the specified directory and subdirectories"""
    try:
        import fnmatch
        matches = []
        
        if not case_sensitive:
            pattern = pattern.lower()
        
        for root, dirs, files in os.walk(directory_path):
            for file in files:
                file_to_check = file if case_sensitive else file.lower()
                if fnmatch.fnmatch(file_to_check, pattern):
                    matches.append(os.path.join(root, file))
        
        return matches
    except Exception as e:
        return [f"Error searching files: {str(e)}"]

# ファイルの存在確認
@mcp.tool()
def file_exists(file_path: str) -> bool:
    """Check if a file or directory exists"""
    return os.path.exists(file_path)

# 現在の作業ディレクトリ取得
@mcp.tool()
def get_current_directory() -> str:
    """Get the current working directory"""
    return os.getcwd()

# 作業ディレクトリ変更
@mcp.tool()
def change_directory(directory_path: str) -> str:
    """Change the current working directory"""
    try:
        os.chdir(directory_path)
        return f"Successfully changed directory to '{os.getcwd()}'"
    except FileNotFoundError:
        return f"Error: Directory '{directory_path}' not found"
    except Exception as e:
        return f"Error changing directory: {str(e)}"

# リソースの追加
@mcp.resource("info://server")
def get_server_info() -> str:
    """Get information about this file operations MCP server"""
    return json.dumps({
        "name": "FileOperationsServer",
        "version": "1.0.0",
        "description": "A comprehensive file operations MCP server for file and directory management",
        "tools": [
            "read_file", "write_file", "delete_file", "move_file", "copy_file",
            "create_directory", "delete_directory", "list_directory", 
            "get_file_info", "search_files", "file_exists", 
            "get_current_directory", "change_directory"
        ],
        "features": [
            "File reading and writing with encoding support",
            "File and directory operations (create, delete, move, copy)",
            "Directory listing and navigation",
            "File search with pattern matching",
            "Detailed file information retrieval",
            "Safe error handling"
        ]
    }, indent=2)

if __name__ == "__main__":
    mcp.run(transport="stdio")
```
</details>


次に、claude_desktop_config.json　を編集する。
以下を追加します。
(claude_desktop_config.jsonはClaude Desktopの　Claude > 設定 > 開発者　> 構成を編集で開きます。）

```
"mcp-server-filesystem": {
      "command": "uv",
      "args": [
        "--directory",
        "/Users/#ユーザー名/MCP-server-filesystem",
        "run",
        "server.py"
      ]
    }
```

----------------------
この後、 Claude Desktopを完全に終了し、再起動する。
これでエラーが出なければOK.

Claude Desktopのプロンプト入力欄に

```
デスクトップ上にあるファイルを列挙して
```
として、そのファイルのリストが表示されれば無事動作しています。