Skip to main content
Glama

MCP Claude Code

by SDGLBL
permissions.py8.04 kB
"""Permission system for the MCP Claude Code server.""" import json import os import sys import tempfile from collections.abc import Awaitable, Callable from pathlib import Path from typing import Any, TypeVar, final # Define type variables for better type annotations T = TypeVar("T") P = TypeVar("P") @final class PermissionManager: """Manages permissions for file and command operations.""" def __init__(self) -> None: """Initialize the permission manager.""" # Allowed paths self.allowed_paths: set[Path] = set() # Allowed paths based on platform if sys.platform == "win32": # Windows self.allowed_paths.add(Path(tempfile.gettempdir()).resolve()) else: # Unix/Linux/Mac self.allowed_paths.add(Path("/tmp").resolve()) self.allowed_paths.add(Path("/var").resolve()) # Excluded paths self.excluded_paths: set[Path] = set() self.excluded_patterns: list[str] = [] # Default excluded patterns self._add_default_exclusions() def _add_default_exclusions(self) -> None: """Add default exclusions for sensitive files and directories.""" # Sensitive directories sensitive_dirs: list[str] = [ ".ssh", ".gnupg", "node_modules", "__pycache__", ".venv", "venv", "env", ".idea", ".DS_Store", ".pytest_cache", ".vs", ".vscode", "dist", "build", "target", ".ruff_cache", ".llm-context", ] self.excluded_patterns.extend(sensitive_dirs) # Sensitive file patterns sensitive_patterns: list[str] = [ ".env", "*.key", "*.pem", "*.crt", "*password*", "*secret*", "*.sqlite", "*.db", "*.sqlite3", "*.log", ] self.excluded_patterns.extend(sensitive_patterns) def add_allowed_path(self, path: str) -> None: """Add a path to the allowed paths. Args: path: The path to allow """ resolved_path: Path = Path(path).resolve() self.allowed_paths.add(resolved_path) def remove_allowed_path(self, path: str) -> None: """Remove a path from the allowed paths. Args: path: The path to remove """ resolved_path: Path = Path(path).resolve() if resolved_path in self.allowed_paths: self.allowed_paths.remove(resolved_path) def exclude_path(self, path: str) -> None: """Exclude a path from allowed operations. Args: path: The path to exclude """ resolved_path: Path = Path(path).resolve() self.excluded_paths.add(resolved_path) def add_exclusion_pattern(self, pattern: str) -> None: """Add an exclusion pattern. Args: pattern: The pattern to exclude """ self.excluded_patterns.append(pattern) def remove_exclusion_pattern(self, pattern: str) -> None: """Remove an exclusion pattern. Args: pattern: The pattern to remove from exclusions """ if pattern in self.excluded_patterns: self.excluded_patterns.remove(pattern) def is_path_allowed(self, path: str) -> bool: """Check if a path is allowed. Args: path: The path to check Returns: True if the path is allowed, False otherwise """ resolved_path: Path = Path(path).resolve() # Check exclusions first if self._is_path_excluded(resolved_path): return False # Check if the path is within any allowed path for allowed_path in self.allowed_paths: try: resolved_path.relative_to(allowed_path) return True except ValueError: continue return False def _is_path_excluded(self, path: Path) -> bool: """Check if a path is excluded. Args: path: The path to check Returns: True if the path is excluded, False otherwise """ # Check exact excluded paths if path in self.excluded_paths: return True # Check excluded patterns path_str: str = str(path) # Get path parts to check for exact directory/file name matches path_parts = path_str.split(os.sep) for pattern in self.excluded_patterns: # Handle wildcard patterns (e.g., "*.log") if pattern.startswith("*"): if path_str.endswith(pattern[1:]): return True else: # For non-wildcard patterns, check if any path component matches exactly if pattern in path_parts: return True return False def to_json(self) -> str: """Convert the permission manager to a JSON string. Returns: A JSON string representation of the permission manager """ data: dict[str, Any] = { "allowed_paths": [str(p) for p in self.allowed_paths], "excluded_paths": [str(p) for p in self.excluded_paths], "excluded_patterns": self.excluded_patterns, } return json.dumps(data) @classmethod def from_json(cls, json_str: str) -> "PermissionManager": """Create a permission manager from a JSON string. Args: json_str: The JSON string Returns: A new PermissionManager instance """ data: dict[str, Any] = json.loads(json_str) manager = cls() for path in data.get("allowed_paths", []): manager.add_allowed_path(path) for path in data.get("excluded_paths", []): manager.exclude_path(path) manager.excluded_patterns = data.get("excluded_patterns", []) return manager class PermissibleOperation: """A decorator for operations that require permission.""" def __init__( self, permission_manager: PermissionManager, operation: str, get_path_fn: Callable[[list[Any], dict[str, Any]], str] | None = None, ) -> None: """Initialize the permissible operation. Args: permission_manager: The permission manager operation: The operation type (read, write, execute, etc.) get_path_fn: Optional function to extract the path from args and kwargs """ self.permission_manager: PermissionManager = permission_manager self.operation: str = operation self.get_path_fn: Callable[[list[Any], dict[str, Any]], str] | None = ( get_path_fn ) def __call__( self, func: Callable[..., Awaitable[T]] ) -> Callable[..., Awaitable[T]]: """Decorate the function. Args: func: The function to decorate Returns: The decorated function """ async def wrapper(*args: Any, **kwargs: Any) -> T: # Extract the path if self.get_path_fn: # Pass args as a list and kwargs as a dict to the path function path = self.get_path_fn(list(args), kwargs) else: # Default to first argument path = args[0] if args else next(iter(kwargs.values()), None) if not isinstance(path, str): raise ValueError(f"Invalid path type: {type(path)}") # Check permission if not self.permission_manager.is_path_allowed(path): raise PermissionError( f"Operation '{self.operation}' not allowed for path: {path}" ) # Call the function return await func(*args, **kwargs) return wrapper

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/SDGLBL/mcp-claude-code'

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