Skip to main content
Glama

Quarkdown MCP Server

by Lillard01
config.py13.1 kB
"""Configuration module for Quarkdown MCP server. This module provides configuration management for the Quarkdown MCP server, including JAR path, temporary directory settings, and execution options. """ import logging import os import tempfile from pathlib import Path from typing import Optional logger = logging.getLogger(__name__) class QuarkdownConfig: """Configuration class for Quarkdown MCP server. This class manages all configuration settings needed to run Quarkdown operations, including JAR file location, temporary directories, and execution parameters. """ def __init__(self, jar_path: Optional[str] = None, temp_dir: Optional[str] = None, log_level: str = "INFO"): """Initialize Quarkdown configuration. Args: jar_path: Path to the Quarkdown JAR file. If None, uses default location. temp_dir: Temporary directory for file operations. If None, uses system temp. log_level: Logging level for operations. Defaults to INFO. """ # Set JAR path - default to the built JAR in the project if jar_path is None: project_root = Path(__file__).parent.parent.parent.parent jar_path_obj = project_root / "quarkdown" / "build" / "libs" / "quarkdown.jar" else: jar_path_obj = Path(jar_path) # Validate JAR file exists if not jar_path_obj.exists(): raise FileNotFoundError(f"JAR file not found at: {jar_path_obj}") # Store as string for API consistency self.jar_path = str(jar_path_obj) # Set temporary directory if temp_dir is None: temp_dir_obj = Path(tempfile.gettempdir()) / "quarkdown_mcp" # Store as string for API consistency self.temp_dir = str(temp_dir_obj) # Ensure temp directory exists when using default Path(self.temp_dir).mkdir(parents=True, exist_ok=True) else: temp_dir_obj = Path(temp_dir) # Validate that explicitly provided temp directory exists if not temp_dir_obj.exists(): raise FileNotFoundError(f"Temporary directory not found: {temp_dir_obj}") # Store as string for API consistency self.temp_dir = str(temp_dir_obj) # Set log level with validation valid_log_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] if log_level not in valid_log_levels: raise ValueError(f"Invalid log level: {log_level}. Must be one of {valid_log_levels}") self.log_level = log_level # Java executable path self.java_executable = "java" # Default timeout for operations (in seconds) self.timeout = 300 # Default encoding self.encoding = "utf-8" # Track temporary files for cleanup self._temp_files = [] def create_temp_file(self, content: str | bytes = "", suffix: str = ".qmd") -> Path: """Create a temporary file with the given content. Args: content: Content to write to the file (str or bytes) suffix: File extension/suffix Returns: Path to the created temporary file """ import uuid # Use shorter UUID to avoid "File name too long" error short_uuid = str(uuid.uuid4())[:8] temp_file = Path(self.temp_dir) / f"temp_{short_uuid}{suffix}" if content: if isinstance(content, bytes): temp_file.write_bytes(content) else: temp_file.write_text(content, encoding=self.encoding) # Track the file for cleanup self._temp_files.append(temp_file) return temp_file def create_temp_dir(self, prefix: str = "quarkdown_") -> Path: """Create a temporary directory. Args: prefix: Prefix for the temporary directory name Returns: Path to the created temporary directory """ import uuid # Use shorter UUID to avoid "Directory name too long" error short_uuid = str(uuid.uuid4())[:8] temp_dir = Path(self.temp_dir) / f"{prefix}{short_uuid}" temp_dir.mkdir(parents=True, exist_ok=True) return temp_dir def cleanup_temp_file(self, file_path: Path) -> None: """Clean up a temporary file. Args: file_path: Path to the file to remove """ try: if file_path.exists() and file_path.is_relative_to(Path(self.temp_dir)): file_path.unlink() except Exception: # Ignore cleanup errors pass def get_output_formats(self) -> list[str]: """Get list of supported output formats. Returns: List of supported output format extensions """ return ["html", "pdf", "tex", "md"] def validate_output_format(self, format_name: str) -> bool: """Validate if the given output format is supported. Args: format_name: Output format to validate Returns: True if format is supported, False otherwise """ return format_name.lower() in self.get_output_formats() def validate(self) -> None: """Validate the configuration settings. Raises: FileNotFoundError: If JAR file or temp directory doesn't exist """ # Validate JAR file exists jar_path_obj = Path(self.jar_path) if not jar_path_obj.exists(): raise FileNotFoundError(f"JAR file not found: {jar_path_obj}") # Validate temp directory exists temp_dir_obj = Path(self.temp_dir) if not temp_dir_obj.exists(): raise FileNotFoundError(f"Temporary directory not found: {temp_dir_obj}") @classmethod def from_env(cls) -> "QuarkdownConfig": """Create configuration from environment variables. Environment variables: QUARKDOWN_JAR_PATH: Path to the Quarkdown JAR file (required) QUARKDOWN_TEMP_DIR: Temporary directory path (optional) QUARKDOWN_LOG_LEVEL: Log level (optional, defaults to INFO) Returns: QuarkdownConfig instance Raises: ValueError: If required environment variables are missing """ jar_path = os.environ.get("QUARKDOWN_JAR_PATH") if jar_path is None: raise ValueError("QUARKDOWN_JAR_PATH environment variable is required") temp_dir = os.environ.get("QUARKDOWN_TEMP_DIR") if temp_dir is None: # When no temp_dir is specified in env, use system temp directly temp_dir = tempfile.gettempdir() log_level = os.environ.get("QUARKDOWN_LOG_LEVEL", "INFO") return cls(jar_path=jar_path, temp_dir=temp_dir, log_level=log_level) @classmethod def from_dict(cls, config_data: dict) -> "QuarkdownConfig": """Create configuration from dictionary. Args: config_data: Dictionary containing configuration data Returns: QuarkdownConfig instance Raises: ValueError: If required fields are missing TypeError: If field types are invalid """ jar_path = config_data.get("jar_path") if jar_path is None: raise ValueError("jar_path is required") temp_dir = config_data.get("temp_dir") if temp_dir is not None and not isinstance(temp_dir, str): raise TypeError("temp_dir must be a string") log_level = config_data.get("log_level", "INFO") return cls(jar_path=jar_path, temp_dir=temp_dir, log_level=log_level) def to_dict(self) -> dict: """Convert configuration to dictionary. Returns: Dictionary containing configuration values """ return { "jar_path": self.jar_path, "temp_dir": self.temp_dir, "log_level": self.log_level } def __repr__(self) -> str: """String representation of configuration. Returns: String representation """ return f"QuarkdownConfig(jar_path='{self.jar_path}', temp_dir='{self.temp_dir}', log_level='{self.log_level}')" def __eq__(self, other) -> bool: """Check equality with another configuration. Args: other: Another QuarkdownConfig instance Returns: True if configurations are equal """ if not isinstance(other, QuarkdownConfig): return False return ( self.jar_path == other.jar_path and self.temp_dir == other.temp_dir and self.log_level == other.log_level ) def __hash__(self) -> int: """Hash function for configuration. Returns: Hash value based on configuration attributes """ return hash((self.jar_path, self.temp_dir, self.log_level)) def copy(self) -> "QuarkdownConfig": """Create a copy of the configuration. Returns: New QuarkdownConfig instance with same values """ return QuarkdownConfig( jar_path=self.jar_path, temp_dir=self.temp_dir, log_level=self.log_level ) def update(self, **kwargs) -> None: """Update configuration attributes. Args: **kwargs: Configuration attributes to update Raises: AttributeError: If invalid field is provided ValueError: If invalid log level is provided """ valid_fields = {"jar_path", "temp_dir", "log_level"} for key, value in kwargs.items(): if key not in valid_fields: raise AttributeError(f"Invalid configuration field: {key}") if key == "log_level": valid_levels = {"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"} if value not in valid_levels: raise ValueError(f"Invalid log level: {value}. Must be one of {valid_levels}") setattr(self, key, value) def is_valid(self) -> bool: """Check if configuration is valid. Returns: True if configuration is valid, False otherwise """ try: self.validate() return True except (FileNotFoundError, ValueError): return False def get_java_command(self, jvm_options: list = None) -> list: """Get Java command for executing Quarkdown. Args: jvm_options: Optional JVM options to include Returns: List of command arguments """ command = ["java"] if jvm_options: command.extend(jvm_options) command.extend(["-jar", self.jar_path]) return command def cleanup_temp_files(self) -> None: """Clean up all temporary files created by this config instance.""" for temp_file in self._temp_files: try: if temp_file.exists(): temp_file.unlink() logger.debug(f"Cleaned up temporary file: {temp_file}") except Exception as e: logger.warning(f"Failed to clean up temporary file {temp_file}: {e}") self._temp_files.clear() def cleanup_temp_file(self, file_path: Path) -> None: """Clean up a specific temporary file. Args: file_path: Path to the temporary file to clean up """ try: if file_path.exists(): file_path.unlink() logger.debug(f"Cleaned up temporary file: {file_path}") if file_path in self._temp_files: self._temp_files.remove(file_path) except Exception as e: logger.warning(f"Failed to clean up temporary file {file_path}: {e}") def get_log_config(self) -> dict: """Get logging configuration dictionary. Returns: Dictionary containing logging configuration """ return { "level": self.log_level, "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s", "handlers": [ { "class": "logging.StreamHandler", "level": self.log_level, "formatter": "default" } ] }

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/Lillard01/quarkdown-mcp'

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