Skip to main content
Glama
utils.py7.65 kB
""" HiveMind MCP Server - Utilities Helper functions for path manipulation, formatting, and common operations. """ import re from pathlib import Path from typing import List, Optional, Any from datetime import datetime from config import ANCHOR_PREFIX, IGNORE_PATTERNS def sanitize_filename(name: str) -> str: """ Clean filename for use in Mermaid IDs. Removes special characters and replaces spaces/dots with underscores. Args: name: Original filename or identifier Returns: Sanitized string safe for Mermaid node IDs """ # Remove file extension if present name = Path(name).stem if '.' in name else name # Replace special characters with underscores sanitized = re.sub(r'[^a-zA-Z0-9_]', '_', name) # Remove consecutive underscores sanitized = re.sub(r'_+', '_', sanitized) # Remove leading/trailing underscores sanitized = sanitized.strip('_') # Ensure it starts with a letter (Mermaid requirement) if sanitized and not sanitized[0].isalpha(): sanitized = 'n_' + sanitized return sanitized or 'unnamed' def normalize_path(path: str) -> Path: """ Normalize file path to absolute Path object. Args: path: Relative or absolute path string Returns: Normalized absolute Path object """ p = Path(path) if not p.is_absolute(): p = Path.cwd() / p return p.resolve() def parse_anchor(anchor: str) -> Path: """ Convert anchor string to file path. Args: anchor: Anchor in format "anchor://path/to/directory" Returns: Path object pointing to the directory Raises: ValueError: If anchor format is invalid """ if not anchor.startswith(ANCHOR_PREFIX): raise ValueError(f"Invalid anchor format. Expected '{ANCHOR_PREFIX}...'") path_str = anchor[len(ANCHOR_PREFIX):] return normalize_path(path_str) def create_anchor(path: Path, root: Optional[Path] = None) -> str: """ Create anchor string from path. Args: path: Path to create anchor for root: Optional root path to make anchor relative to Returns: Anchor string in format "anchor://path/to/directory" """ if root: try: relative = path.relative_to(root) return f"{ANCHOR_PREFIX}{relative.as_posix()}" except ValueError: pass return f"{ANCHOR_PREFIX}{path.as_posix()}" def format_list(items: List[Any], prefix: str = "- ") -> str: """ Format list items for markdown. Args: items: List of items to format prefix: Prefix for each line (default "- ") Returns: Formatted markdown list string """ if not items: return "_None_" return '\n'.join(f"{prefix}{item}" for item in items) def format_code_block(code: str, language: str = "") -> str: """ Format code as markdown code block. Args: code: Code content language: Language for syntax highlighting Returns: Formatted code block string """ return f"```{language}\n{code}\n```" def get_relative_path(from_path: Path, to_path: Path) -> str: """ Get relative path from one path to another. Args: from_path: Starting path to_path: Target path Returns: Relative path string """ try: # Find common ancestor from_parts = from_path.resolve().parts to_parts = to_path.resolve().parts # Find where paths diverge common_length = 0 for i, (f, t) in enumerate(zip(from_parts, to_parts)): if f != t: break common_length = i + 1 # Build relative path ups = len(from_parts) - common_length downs = to_parts[common_length:] if ups == 0 and not downs: return '.' parts = ['..'] * ups + list(downs) return '/'.join(parts) except Exception: return str(to_path) def should_ignore(path: Path) -> bool: """ Check if path should be ignored. Args: path: Path to check Returns: True if path should be ignored """ for pattern in IGNORE_PATTERNS: if pattern in path.parts: return True # Check for glob patterns if '*' in pattern: if path.match(pattern): return True return False def calculate_complexity(node_count: int, branch_count: int = 0, loop_count: int = 0) -> int: """ Calculate simplified cyclomatic complexity. Cyclomatic complexity = E - N + 2P where: - E = edges - N = nodes - P = connected components (usually 1) Simplified: 1 + number of decision points (branches, loops) Args: node_count: Number of nodes/statements branch_count: Number of if/switch/ternary loop_count: Number of for/while/do-while Returns: Complexity score """ return 1 + branch_count + loop_count def get_file_info(file_path: Path) -> dict: """ Get basic file information. Args: file_path: Path to file Returns: Dict with name, path, lines, size, modified """ try: content = file_path.read_text(encoding='utf-8', errors='ignore') lines = len(content.splitlines()) size = file_path.stat().st_size modified = datetime.fromtimestamp(file_path.stat().st_mtime) return { 'name': file_path.name, 'path': str(file_path), 'lines': lines, 'size': size, 'modified': modified.isoformat(), } except Exception as e: return { 'name': file_path.name, 'path': str(file_path), 'lines': 0, 'size': 0, 'error': str(e), } def truncate_string(s: str, max_length: int = 100, suffix: str = "...") -> str: """ Truncate string to maximum length. Args: s: String to truncate max_length: Maximum length suffix: Suffix to add if truncated Returns: Truncated string """ if len(s) <= max_length: return s return s[:max_length - len(suffix)] + suffix def format_size(size_bytes: int) -> str: """ Format file size in human-readable format. Args: size_bytes: Size in bytes Returns: Formatted size string (e.g., "1.5 KB") """ for unit in ['B', 'KB', 'MB', 'GB']: if size_bytes < 1024: return f"{size_bytes:.1f} {unit}" if unit != 'B' else f"{size_bytes} {unit}" size_bytes /= 1024 return f"{size_bytes:.1f} TB" def extract_first_paragraph(markdown: str) -> str: """ Extract the first paragraph from markdown content. Useful for generating summaries. Args: markdown: Markdown content Returns: First paragraph text """ lines = markdown.strip().split('\n') paragraph = [] started = False for line in lines: # Skip headers and empty lines at start if not started: if line.startswith('#') or not line.strip(): continue started = True # End at empty line or new header if started: if not line.strip() or line.startswith('#'): break paragraph.append(line.strip()) return ' '.join(paragraph)

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/Jahanzaib-Kaleem/hive-mind-mcp'

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