Moondream MCP Server

  • src
  • mcp_server_everything_search
"""Platform-agnostic search interface for MCP.""" import abc import platform import subprocess import os from datetime import datetime from typing import Optional, List from dataclasses import dataclass from pathlib import Path @dataclass class SearchResult: """Universal search result structure.""" path: str filename: str extension: Optional[str] = None size: Optional[int] = None created: Optional[datetime] = None modified: Optional[datetime] = None accessed: Optional[datetime] = None attributes: Optional[str] = None class SearchProvider(abc.ABC): """Abstract base class for platform-specific search implementations.""" @abc.abstractmethod def search_files( self, query: str, max_results: int = 100, match_path: bool = False, match_case: bool = False, match_whole_word: bool = False, match_regex: bool = False, sort_by: Optional[int] = None ) -> List[SearchResult]: """Execute a file search using platform-specific methods.""" pass @classmethod def get_provider(cls) -> 'SearchProvider': """Factory method to get the appropriate search provider for the current platform.""" system = platform.system().lower() if system == 'darwin': return MacSearchProvider() elif system == 'linux': return LinuxSearchProvider() elif system == 'windows': return WindowsSearchProvider() else: raise NotImplementedError(f"No search provider available for {system}") def _convert_path_to_result(self, path: str) -> SearchResult: """Convert a path to a SearchResult with file information.""" try: path_obj = Path(path) stat = path_obj.stat() return SearchResult( path=str(path_obj), filename=path_obj.name, extension=path_obj.suffix[1:] if path_obj.suffix else None, size=stat.st_size, created=datetime.fromtimestamp(stat.st_ctime), modified=datetime.fromtimestamp(stat.st_mtime), accessed=datetime.fromtimestamp(stat.st_atime) ) except (OSError, ValueError) as e: # If we can't access the file, return basic info return SearchResult( path=str(path), filename=os.path.basename(path) ) class MacSearchProvider(SearchProvider): """macOS search implementation using mdfind.""" def search_files( self, query: str, max_results: int = 100, match_path: bool = False, match_case: bool = False, match_whole_word: bool = False, match_regex: bool = False, sort_by: Optional[int] = None ) -> List[SearchResult]: try: # Build mdfind command cmd = ['mdfind'] if match_path: # When matching path, don't use -name cmd.append(query) else: cmd.extend(['-name', query]) # Execute search result = subprocess.run(cmd, capture_output=True, text=True) if result.returncode != 0: raise RuntimeError(f"mdfind failed: {result.stderr}") # Process results paths = result.stdout.splitlines()[:max_results] return [self._convert_path_to_result(path) for path in paths] except subprocess.CalledProcessError as e: raise RuntimeError(f"Search failed: {e}") class LinuxSearchProvider(SearchProvider): """Linux search implementation using locate/plocate.""" def __init__(self): """Check if locate/plocate is installed and the database is ready.""" self.locate_cmd = None self.locate_type = None # Check for plocate first (newer version) plocate_check = subprocess.run(['which', 'plocate'], capture_output=True) if plocate_check.returncode == 0: self.locate_cmd = 'plocate' self.locate_type = 'plocate' else: # Check for mlocate mlocate_check = subprocess.run(['which', 'locate'], capture_output=True) if mlocate_check.returncode == 0: self.locate_cmd = 'locate' self.locate_type = 'mlocate' else: raise RuntimeError( "Neither 'locate' nor 'plocate' is installed. Please install one:\n" "Ubuntu/Debian: sudo apt-get install plocate\n" " or\n" " sudo apt-get install mlocate\n" "Fedora: sudo dnf install mlocate\n" "After installation, the database will be updated automatically, or run:\n" "For plocate: sudo updatedb\n" "For mlocate: sudo /etc/cron.daily/mlocate" ) def _update_database(self): """Update the locate database.""" if self.locate_type == 'plocate': subprocess.run(['sudo', 'updatedb'], check=True) else: # mlocate subprocess.run(['sudo', '/etc/cron.daily/mlocate'], check=True) def search_files( self, query: str, max_results: int = 100, match_path: bool = False, match_case: bool = False, match_whole_word: bool = False, match_regex: bool = False, sort_by: Optional[int] = None ) -> List[SearchResult]: try: # Build locate command cmd = [self.locate_cmd] if not match_case: cmd.append('-i') if match_regex: cmd.append('--regex' if self.locate_type == 'mlocate' else '-r') cmd.append(query) # Execute search result = subprocess.run(cmd, capture_output=True, text=True) if result.returncode != 0: error_msg = result.stderr.lower() if "no such file or directory" in error_msg or "database" in error_msg: raise RuntimeError( f"The {self.locate_type} database needs to be created. " f"Please run: sudo updatedb" ) raise RuntimeError(f"{self.locate_cmd} failed: {result.stderr}") # Process results paths = result.stdout.splitlines()[:max_results] return [self._convert_path_to_result(path) for path in paths] except FileNotFoundError: raise RuntimeError( f"The {self.locate_cmd} command disappeared. Please reinstall:\n" "Ubuntu/Debian: sudo apt-get install plocate\n" " or\n" " sudo apt-get install mlocate\n" "Fedora: sudo dnf install mlocate" ) except subprocess.CalledProcessError as e: raise RuntimeError(f"Search failed: {e}") class WindowsSearchProvider(SearchProvider): """Windows search implementation using Everything SDK.""" def __init__(self): """Initialize Everything SDK.""" import os from .everything_sdk import EverythingSDK dll_path = os.getenv('EVERYTHING_SDK_PATH', 'D:\\dev\\tools\\Everything-SDK\\dll\\Everything64.dll') self.everything_sdk = EverythingSDK(dll_path) def search_files( self, query: str, max_results: int = 100, match_path: bool = False, match_case: bool = False, match_whole_word: bool = False, match_regex: bool = False, sort_by: Optional[int] = None ) -> List[SearchResult]: # Replace double backslashes with single backslashes query = query.replace("\\\\", "\\") # If the query.query contains forward slashes, replace them with backslashes query = query.replace("/", "\\") return self.everything_sdk.search_files( query=query, max_results=max_results, match_path=match_path, match_case=match_case, match_whole_word=match_whole_word, match_regex=match_regex, sort_by=sort_by )