Skip to main content
Glama
ASXRND

MCP Weather & Accruals Server

by ASXRND
file_api.py14.7 kB
#!/usr/bin/env python3 """ Модуль для работы с файлами. Содержит функции для чтения, создания и изменения файлов по определённому пути. """ import logging import os from pathlib import Path from typing import Optional logger = logging.getLogger("FileAPI") # Разрешённая базовая директория для операций с файлами. # Значение теперь читается из переменной окружения `ALLOWED_BASE_DIR`. # По умолчанию сохранено прежнее поведение для обратной совместимости. ALLOWED_BASE_DIR = os.getenv( "ALLOWED_BASE_DIR", "/Users/aleksandrhohon/Downloads/Скриншоты" ) # Проверяет, является ли путь безопасным (не выходит за пределы base_dir). def is_path_safe(file_path: str, base_dir: str = ALLOWED_BASE_DIR) -> bool: """ Проверяет, является ли путь безопасным (не выходит за пределы base_dir). Предотвращает path traversal атаки. Args: file_path: Проверяемый путь base_dir: Базовая директория (по умолчанию текущая директория) Returns: bool: True если путь безопасен, False иначе """ try: base = Path(base_dir).resolve() target = (base / file_path).resolve() # Проверяем, что целевой путь находится в базовой директории return str(target).startswith(str(base)) except Exception as e: logger.error(f"Ошибка при проверке пути: {e}") return False # Читает содержимое файла. async def read_file(file_path: str, base_dir: str = ALLOWED_BASE_DIR) -> str: """ Читает содержимое файла. Args: file_path: Путь к файлу (относительно base_dir) base_dir: Базовая директория для безопасности Returns: Содержимое файла или сообщение об ошибке """ logger.info(f"Чтение файла: {file_path}") # Проверяем безопасность пути if not is_path_safe(file_path, base_dir): error_msg = f"❌ Доступ запрещен: путь '{file_path}' выходит за пределы разрешённой директории" logger.warning(error_msg) return error_msg try: full_path = Path(base_dir) / file_path # Проверяем, существует ли файл if not full_path.exists(): error_msg = f"❌ Файл не найден: {file_path}" logger.warning(error_msg) return error_msg # Проверяем, что это файл (не директория) if not full_path.is_file(): error_msg = f"❌ Путь '{file_path}' не является файлом" logger.warning(error_msg) return error_msg # Читаем файл with open(full_path, 'r', encoding='utf-8') as f: content = f.read() logger.info(f"✅ Файл успешно прочитан: {file_path} ({len(content)} символов)") return content except Exception as e: error_msg = f"❌ Ошибка при чтении файла: {str(e)}" logger.error(error_msg) return error_msg # Создаёт новый файл с указанным содержимым. async def create_file(file_path: str, content: str, base_dir: str = ALLOWED_BASE_DIR, overwrite: bool = False) -> str: """ Создаёт новый файл с указанным содержимым. Args: file_path: Путь к файлу (относительно base_dir) content: Содержимое файла base_dir: Базовая директория для безопасности overwrite: Перезаписать ли файл, если он существует Returns: Сообщение об успехе или ошибке """ logger.info(f"Создание файла: {file_path}") # Проверяем безопасность пути if not is_path_safe(file_path, base_dir): error_msg = f"❌ Доступ запрещен: путь '{file_path}' выходит за пределы разрешённой директории" logger.warning(error_msg) return error_msg try: full_path = Path(base_dir) / file_path # Проверяем, существует ли файл if full_path.exists() and not overwrite: error_msg = f"❌ Файл уже существует: {file_path}. Используйте overwrite=True для перезаписи" logger.warning(error_msg) return error_msg # Создаём директории, если их нет full_path.parent.mkdir(parents=True, exist_ok=True) # Записываем файл with open(full_path, 'w', encoding='utf-8') as f: f.write(content) success_msg = f"✅ Файл успешно создан: {file_path} ({len(content)} символов)" logger.info(success_msg) return success_msg except Exception as e: error_msg = f"❌ Ошибка при создании файла: {str(e)}" logger.error(error_msg) return error_msg # Обновляет содержимое существующего файла. async def update_file(file_path: str, content: str, base_dir: str = ALLOWED_BASE_DIR) -> str: """ Обновляет содержимое существующего файла. Args: file_path: Путь к файлу (относительно base_dir) content: Новое содержимое файла base_dir: Базовая директория для безопасности Returns: Сообщение об успехе или ошибке """ logger.info(f"Обновление файла: {file_path}") # Проверяем безопасность пути if not is_path_safe(file_path, base_dir): error_msg = f"❌ Доступ запрещен: путь '{file_path}' выходит за пределы разрешённой директории" logger.warning(error_msg) return error_msg try: full_path = Path(base_dir) / file_path # Проверяем, существует ли файл if not full_path.exists(): error_msg = f"❌ Файл не найден: {file_path}. Используйте create_file для создания нового файла" logger.warning(error_msg) return error_msg # Проверяем, что это файл (не директория) if not full_path.is_file(): error_msg = f"❌ Путь '{file_path}' не является файлом" logger.warning(error_msg) return error_msg # Читаем старое содержимое (для логирования) with open(full_path, 'r', encoding='utf-8') as f: old_size = len(f.read()) # Записываем новое содержимое with open(full_path, 'w', encoding='utf-8') as f: f.write(content) success_msg = f"✅ Файл успешно обновлен: {file_path} ({old_size} → {len(content)} символов)" logger.info(success_msg) return success_msg except Exception as e: error_msg = f"❌ Ошибка при обновлении файла: {str(e)}" logger.error(error_msg) return error_msg # Добавляет содержимое в конец существующего файла. async def append_to_file(file_path: str, content: str, base_dir: str = ALLOWED_BASE_DIR) -> str: """ Добавляет содержимое в конец существующего файла. Args: file_path: Путь к файлу (относительно base_dir) content: Содержимое для добавления base_dir: Базовая директория для безопасности Returns: Сообщение об успехе или ошибке """ logger.info(f"Добавление содержимого в файл: {file_path}") # Проверяем безопасность пути if not is_path_safe(file_path, base_dir): error_msg = f"❌ Доступ запрещен: путь '{file_path}' выходит за пределы разрешённой директории" logger.warning(error_msg) return error_msg try: full_path = Path(base_dir) / file_path # Проверяем, существует ли файл if not full_path.exists(): error_msg = f"❌ Файл не найден: {file_path}" logger.warning(error_msg) return error_msg # Добавляем содержимое в конец файла with open(full_path, 'a', encoding='utf-8') as f: f.write(content) success_msg = f"✅ Содержимое добавлено в файл: {file_path} ({len(content)} символов)" logger.info(success_msg) return success_msg except Exception as e: error_msg = f"❌ Ошибка при добавлении в файл: {str(e)}" logger.error(error_msg) return error_msg # Удаляет файл. async def delete_file(file_path: str, base_dir: str = ALLOWED_BASE_DIR) -> str: """ Удаляет файл. Args: file_path: Путь к файлу (относительно base_dir) base_dir: Базовая директория для безопасности Returns: Сообщение об успехе или ошибке """ logger.info(f"Удаление файла: {file_path}") # Проверяем безопасность пути if not is_path_safe(file_path, base_dir): error_msg = f"❌ Доступ запрещен: путь '{file_path}' выходит за пределы разрешённой директории" logger.warning(error_msg) return error_msg try: full_path = Path(base_dir) / file_path # Проверяем, существует ли файл if not full_path.exists(): error_msg = f"❌ Файл не найден: {file_path}" logger.warning(error_msg) return error_msg # Проверяем, что это файл (не директория) if not full_path.is_file(): error_msg = f"❌ Путь '{file_path}' не является файлом" logger.warning(error_msg) return error_msg # Удаляем файл full_path.unlink() success_msg = f"✅ Файл успешно удалён: {file_path}" logger.info(success_msg) return success_msg except Exception as e: error_msg = f"❌ Ошибка при удалении файла: {str(e)}" logger.error(error_msg) return error_msg # Выводит список файлов в директории. async def list_files(directory: str = ".", base_dir: str = ALLOWED_BASE_DIR) -> str: """ Выводит список файлов в директории. Args: directory: Директория для просмотра (относительно base_dir) base_dir: Базовая директория для безопасности Returns: Отформатированный список файлов или сообщение об ошибке """ logger.info(f"Получение списка файлов: {directory}") # Проверяем безопасность пути if not is_path_safe(directory, base_dir): error_msg = f"❌ Доступ запрещен: путь '{directory}' выходит за пределы разрешённой директории" logger.warning(error_msg) return error_msg try: full_path = Path(base_dir) / directory # Проверяем, существует ли директория if not full_path.exists(): error_msg = f"❌ Директория не найдена: {directory}" logger.warning(error_msg) return error_msg # Проверяем, что это директория (не файл) if not full_path.is_dir(): error_msg = f"❌ Путь '{directory}' не является директорией" logger.warning(error_msg) return error_msg # Получаем список файлов files = [] dirs = [] for item in sorted(full_path.iterdir()): if item.is_file(): size = item.stat().st_size files.append(f" 📄 {item.name} ({size} байт)") elif item.is_dir(): dirs.append(f" 📁 {item.name}/") result = f"📂 Содержимое директории: {directory}\n\n" if dirs: result += "Директории:\n" + "\n".join(dirs) + "\n\n" if files: result += "Файлы:\n" + "\n".join(files) if not dirs and not files: result += "(пусто)" logger.info(f"Успешно получен список файлов: {directory}") return result except Exception as e: error_msg = f"❌ Ошибка при получении списка файлов: {str(e)}" logger.error(error_msg) return error_msg

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ASXRND/MCP_deepseek'

If you have feedback or need assistance with the MCP directory API, please join our Discord server