Skip to main content
Glama

mcp-plots

models.py10.6 kB
""" Domain models for chart generation and configuration. This module defines strongly-typed data models that replace generic Dict[str, Any] types throughout the codebase. These models provide validation, type safety, and clear contracts between components. """ from dataclasses import dataclass, field from typing import Optional, Dict, Any, List, Union from enum import Enum from ..visualization.constants import ChartConstants class ChartRequestMode(Enum): """Special modes for chart requests""" RENDER = "render" HELP = "help" SUGGEST = "suggest" @dataclass class UserPreferences: """ User configuration preferences with validation. Represents user's saved preferences for chart generation, including output format, styling, and dimensions. """ output_format: str = ChartConstants.ConfigDefaults.OUTPUT_FORMAT theme: str = ChartConstants.ConfigDefaults.THEME chart_width: int = ChartConstants.ConfigDefaults.WIDTH chart_height: int = ChartConstants.ConfigDefaults.HEIGHT def __post_init__(self): """Validate preferences after initialization""" self.validate() def validate(self) -> None: """Validate all preference values""" if not ChartConstants.OutputFormats.validate(self.output_format): raise ValueError(f"Invalid output format: {self.output_format}") if not ChartConstants.Themes.validate(self.theme): raise ValueError(f"Invalid theme: {self.theme}") ChartConstants.validate_config_values( self.chart_width, self.chart_height, ChartConstants.ConfigDefaults.DPI ) def to_dict(self) -> Dict[str, Any]: """Convert preferences to dictionary for serialization""" return { "output_format": self.output_format, "theme": self.theme, "chart_width": self.chart_width, "chart_height": self.chart_height } @classmethod def from_dict(cls, data: Dict[str, Any]) -> 'UserPreferences': """Create preferences from dictionary with defaults""" return cls( output_format=data.get("output_format", ChartConstants.ConfigDefaults.OUTPUT_FORMAT), theme=data.get("theme", ChartConstants.ConfigDefaults.THEME), chart_width=data.get("chart_width", ChartConstants.ConfigDefaults.WIDTH), chart_height=data.get("chart_height", ChartConstants.ConfigDefaults.HEIGHT) ) def merge_with_overrides(self, overrides: Dict[str, Any]) -> 'UserPreferences': """Create new preferences by merging with overrides""" merged_data = self.to_dict() # Map override keys to preference keys key_mapping = { "width": "chart_width", "height": "chart_height", "output_format": "output_format", "theme": "theme" } for override_key, value in overrides.items(): pref_key = key_mapping.get(override_key, override_key) if pref_key in merged_data: merged_data[pref_key] = value return UserPreferences.from_dict(merged_data) @dataclass class FieldMapping: """ Chart field mappings with validation. Maps chart concepts (x-axis, y-axis, etc.) to actual data column names. Provides validation that required fields are present. """ x_field: Optional[str] = None y_field: Optional[str] = None category_field: Optional[str] = None value_field: Optional[str] = None group_field: Optional[str] = None size_field: Optional[str] = None source_field: Optional[str] = None target_field: Optional[str] = None # Future implementations: # name_field: Optional[str] = None # TODO: implement entity naming # time_field: Optional[str] = None # TODO: implement temporal data @classmethod def from_dict(cls, field_map: Dict[str, str]) -> 'FieldMapping': """Create field mapping from dictionary""" return cls( x_field=field_map.get(ChartConstants.FieldNames.X_FIELD), y_field=field_map.get(ChartConstants.FieldNames.Y_FIELD), category_field=field_map.get(ChartConstants.FieldNames.CATEGORY_FIELD), value_field=field_map.get(ChartConstants.FieldNames.VALUE_FIELD), group_field=field_map.get(ChartConstants.FieldNames.GROUP_FIELD), size_field=field_map.get(ChartConstants.FieldNames.SIZE_FIELD), source_field=field_map.get(ChartConstants.FieldNames.SOURCE_FIELD), target_field=field_map.get(ChartConstants.FieldNames.TARGET_FIELD) # Future: name_field, time_field will be added when implemented ) def to_dict(self) -> Dict[str, str]: """Convert to dictionary, excluding None values""" result = {} for field_name in ChartConstants.FieldNames.all(): value = getattr(self, field_name, None) if value is not None: result[field_name] = value return result def get_non_none_fields(self) -> Dict[str, str]: """Get only fields that have values""" return {k: v for k, v in self.to_dict().items() if v is not None} @dataclass class ChartRequest: """ Complete chart generation request with validation. Encapsulates all information needed to generate a chart, including data, configuration, and output preferences. """ chart_type: str data: List[Dict[str, Any]] field_mapping: FieldMapping = field(default_factory=FieldMapping) config_overrides: Dict[str, Any] = field(default_factory=dict) options: Dict[str, Any] = field(default_factory=dict) output_format: Optional[str] = None mode: ChartRequestMode = ChartRequestMode.RENDER def __post_init__(self): """Validate request after initialization""" # Only validate for render mode, not for help/suggest if self.mode == ChartRequestMode.RENDER: self.validate() @classmethod def from_tool_params( cls, chart_type: str, data: Optional[List[Dict[str, Any]]] = None, field_map: Optional[Dict[str, str]] = None, config_overrides: Optional[Dict[str, Any]] = None, options: Optional[Dict[str, Any]] = None, output_format: Optional[str] = None ) -> 'ChartRequest': """Create request from MCP tool parameters""" # Determine mode based on chart_type if chart_type == "help": mode = ChartRequestMode.HELP elif chart_type == "suggest": mode = ChartRequestMode.SUGGEST else: mode = ChartRequestMode.RENDER return cls( chart_type=chart_type, data=data or [], field_mapping=FieldMapping.from_dict(field_map or {}), config_overrides=config_overrides or {}, options=options or {}, output_format=output_format, mode=mode ) def validate(self) -> None: """Validate request for chart generation""" if self.mode == ChartRequestMode.RENDER: if not self.data: raise ValueError(ChartConstants.ErrorMessages.EMPTY_DATA) if not isinstance(self.data, list): raise ValueError(ChartConstants.ErrorMessages.INVALID_DATA_TYPE) if len(self.data) > ChartConstants.ConfigDefaults.MAX_DATA_POINTS: raise ValueError(f"Data exceeds maximum allowed points: {ChartConstants.ConfigDefaults.MAX_DATA_POINTS}") elif self.mode == ChartRequestMode.SUGGEST: if not self.data: raise ValueError("Data is required for field suggestions") def get_field_kwargs(self) -> Dict[str, Any]: """Get field mappings as kwargs for ChartGenerator""" return self.field_mapping.get_non_none_fields() def is_special_mode(self) -> bool: """Check if this is a special mode request (help/suggest)""" return self.mode in (ChartRequestMode.HELP, ChartRequestMode.SUGGEST) @dataclass class ChartResponse: """ Standardized chart generation response. Provides consistent response format for all chart generation operations, including success and error cases. """ success: bool content: Optional[List[Dict[str, Any]]] = None error: Optional[str] = None metadata: Dict[str, Any] = field(default_factory=dict) @classmethod def success_response( cls, content: List[Dict[str, Any]], metadata: Optional[Dict[str, Any]] = None ) -> 'ChartResponse': """Create successful response""" return cls( success=True, content=content, metadata=metadata or {} ) @classmethod def error_response(cls, error: str, metadata: Optional[Dict[str, Any]] = None) -> 'ChartResponse': """Create error response""" return cls( success=False, error=error, metadata=metadata or {} ) def to_mcp_format(self) -> Dict[str, Any]: """Convert to MCP tool response format""" if self.success: result = {"status": "success"} if self.content: result["content"] = self.content return result else: return { "status": "error", "error": self.error or "Unknown error" } def to_dict(self) -> Dict[str, Any]: """Convert to dictionary for serialization""" return { "success": self.success, "content": self.content, "error": self.error, "metadata": self.metadata } @dataclass class ConfigurationSnapshot: """ Snapshot of current configuration state. Used for debugging and monitoring configuration changes. """ user_preferences: UserPreferences effective_config: Dict[str, Any] timestamp: str source: str = "system" def to_dict(self) -> Dict[str, Any]: """Convert to dictionary for logging/debugging""" return { "user_preferences": self.user_preferences.to_dict(), "effective_config": self.effective_config, "timestamp": self.timestamp, "source": self.source }

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/MR901/mcp-plots'

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