diff --git a/mcp-servers/viz-platform/mcp_server/component_registry.py b/mcp-servers/viz-platform/mcp_server/component_registry.py new file mode 100644 index 0000000..77641f2 --- /dev/null +++ b/mcp-servers/viz-platform/mcp_server/component_registry.py @@ -0,0 +1,301 @@ +""" +DMC Component Registry for viz-platform. + +Provides version-locked component definitions to prevent Claude from +hallucinating invalid props. Uses static JSON registries pre-generated +from DMC source. +""" +import json +import logging +from pathlib import Path +from typing import Dict, List, Optional, Any + +logger = logging.getLogger(__name__) + + +class ComponentRegistry: + """ + Version-locked registry of Dash Mantine Components. + + Loads component definitions from static JSON files and provides + lookup methods for validation tools. + """ + + def __init__(self, dmc_version: Optional[str] = None): + """ + Initialize the component registry. + + Args: + dmc_version: Installed DMC version (e.g., "0.14.7"). + If None, will try to detect or use fallback. + """ + self.dmc_version = dmc_version + self.registry_dir = Path(__file__).parent.parent / 'registry' + self.components: Dict[str, Dict[str, Any]] = {} + self.categories: Dict[str, List[str]] = {} + self.loaded_version: Optional[str] = None + + def load(self) -> bool: + """ + Load the component registry for the configured DMC version. + + Returns: + True if registry loaded successfully, False otherwise + """ + registry_file = self._find_registry_file() + + if not registry_file: + logger.warning( + f"No registry found for DMC {self.dmc_version}. " + "Component validation will be limited." + ) + return False + + try: + with open(registry_file, 'r') as f: + data = json.load(f) + + self.loaded_version = data.get('version') + self.components = data.get('components', {}) + self.categories = data.get('categories', {}) + + logger.info( + f"Loaded component registry v{self.loaded_version} " + f"with {len(self.components)} components" + ) + return True + + except Exception as e: + logger.error(f"Failed to load registry: {e}") + return False + + def _find_registry_file(self) -> Optional[Path]: + """ + Find the best matching registry file for the DMC version. + + Strategy: + 1. Exact major.minor match (e.g., dmc_0_14.json for 0.14.7) + 2. Fallback to latest available registry + + Returns: + Path to registry file, or None if not found + """ + if not self.registry_dir.exists(): + logger.warning(f"Registry directory not found: {self.registry_dir}") + return None + + # Try exact major.minor match + if self.dmc_version: + parts = self.dmc_version.split('.') + if len(parts) >= 2: + major_minor = f"{parts[0]}_{parts[1]}" + exact_match = self.registry_dir / f"dmc_{major_minor}.json" + if exact_match.exists(): + return exact_match + + # Fallback: find latest registry + registry_files = list(self.registry_dir.glob("dmc_*.json")) + if registry_files: + # Sort by version and return latest + registry_files.sort(reverse=True) + fallback = registry_files[0] + if self.dmc_version: + logger.warning( + f"No exact match for DMC {self.dmc_version}, " + f"using fallback: {fallback.name}" + ) + return fallback + + return None + + def get_component(self, name: str) -> Optional[Dict[str, Any]]: + """ + Get component definition by name. + + Args: + name: Component name (e.g., "Button", "TextInput") + + Returns: + Component definition dict, or None if not found + """ + return self.components.get(name) + + def get_component_props(self, name: str) -> Optional[Dict[str, Any]]: + """ + Get props schema for a component. + + Args: + name: Component name + + Returns: + Props dict with type info, or None if component not found + """ + component = self.get_component(name) + if component: + return component.get('props', {}) + return None + + def list_components(self, category: Optional[str] = None) -> Dict[str, List[str]]: + """ + List available components, optionally filtered by category. + + Args: + category: Optional category filter (e.g., "inputs", "buttons") + + Returns: + Dict of category -> component names + """ + if category: + if category in self.categories: + return {category: self.categories[category]} + return {} + return self.categories + + def get_categories(self) -> List[str]: + """ + Get list of available component categories. + + Returns: + List of category names + """ + return list(self.categories.keys()) + + def validate_prop( + self, + component: str, + prop_name: str, + prop_value: Any + ) -> Dict[str, Any]: + """ + Validate a single prop value against the registry. + + Args: + component: Component name + prop_name: Prop name + prop_value: Value to validate + + Returns: + Dict with valid: bool, error: Optional[str] + """ + props = self.get_component_props(component) + if props is None: + return { + 'valid': False, + 'error': f"Unknown component: {component}" + } + + if prop_name not in props: + # Check for similar prop names (typo detection) + similar = self._find_similar_props(prop_name, props.keys()) + if similar: + return { + 'valid': False, + 'error': f"Unknown prop '{prop_name}' for {component}. Did you mean '{similar}'?" + } + return { + 'valid': False, + 'error': f"Unknown prop '{prop_name}' for {component}" + } + + prop_schema = props[prop_name] + return self._validate_value(prop_value, prop_schema, prop_name) + + def _validate_value( + self, + value: Any, + schema: Dict[str, Any], + prop_name: str + ) -> Dict[str, Any]: + """ + Validate a value against a prop schema. + + Args: + value: Value to validate + schema: Prop schema from registry + prop_name: Prop name (for error messages) + + Returns: + Dict with valid: bool, error: Optional[str] + """ + prop_type = schema.get('type', 'any') + + # Any type always valid + if prop_type == 'any': + return {'valid': True} + + # Check enum values + if 'enum' in schema: + if value not in schema['enum']: + return { + 'valid': False, + 'error': f"Prop '{prop_name}' expects one of {schema['enum']}, got '{value}'" + } + return {'valid': True} + + # Type checking + type_checks = { + 'string': lambda v: isinstance(v, str), + 'number': lambda v: isinstance(v, (int, float)), + 'integer': lambda v: isinstance(v, int), + 'boolean': lambda v: isinstance(v, bool), + 'array': lambda v: isinstance(v, list), + 'object': lambda v: isinstance(v, dict), + } + + checker = type_checks.get(prop_type) + if checker and not checker(value): + return { + 'valid': False, + 'error': f"Prop '{prop_name}' expects type '{prop_type}', got '{type(value).__name__}'" + } + + return {'valid': True} + + def _find_similar_props( + self, + prop_name: str, + available_props: List[str] + ) -> Optional[str]: + """ + Find a similar prop name for typo suggestions. + + Uses simple edit distance heuristic. + + Args: + prop_name: The (possibly misspelled) prop name + available_props: List of valid prop names + + Returns: + Most similar prop name, or None if no close match + """ + prop_lower = prop_name.lower() + + for prop in available_props: + # Exact match after lowercase + if prop.lower() == prop_lower: + return prop + # Common typos: extra/missing letter + if abs(len(prop) - len(prop_name)) == 1: + if prop_lower.startswith(prop.lower()[:3]): + return prop + + return None + + def is_loaded(self) -> bool: + """Check if registry is loaded.""" + return len(self.components) > 0 + + +def load_registry(dmc_version: Optional[str] = None) -> ComponentRegistry: + """ + Convenience function to load and return a component registry. + + Args: + dmc_version: Optional DMC version string + + Returns: + Loaded ComponentRegistry instance + """ + registry = ComponentRegistry(dmc_version) + registry.load() + return registry diff --git a/mcp-servers/viz-platform/pyproject.toml b/mcp-servers/viz-platform/pyproject.toml index 32b670f..f85b01b 100644 --- a/mcp-servers/viz-platform/pyproject.toml +++ b/mcp-servers/viz-platform/pyproject.toml @@ -25,7 +25,7 @@ dependencies = [ "mcp>=0.9.0", "plotly>=5.18.0", "dash>=2.14.0", - "dash-mantine-components>=0.14.0", + "dash-mantine-components>=2.0.0", "python-dotenv>=1.0.0", "pydantic>=2.5.0", ] diff --git a/mcp-servers/viz-platform/registry/dmc_2_5.json b/mcp-servers/viz-platform/registry/dmc_2_5.json new file mode 100644 index 0000000..b8fed35 --- /dev/null +++ b/mcp-servers/viz-platform/registry/dmc_2_5.json @@ -0,0 +1,668 @@ +{ + "version": "2.5.1", + "generated": "2026-01-26", + "mantine_version": "7.x", + "categories": { + "buttons": ["Button", "ButtonGroup", "ActionIcon", "ActionIconGroup", "CopyButton", "CloseButton", "UnstyledButton"], + "inputs": [ + "TextInput", "PasswordInput", "NumberInput", "Textarea", "Select", "MultiSelect", + "Checkbox", "CheckboxGroup", "CheckboxCard", "Switch", "Radio", "RadioGroup", "RadioCard", + "Slider", "RangeSlider", "ColorInput", "ColorPicker", "Autocomplete", "TagsInput", + "PinInput", "Rating", "SegmentedControl", "Chip", "ChipGroup", "JsonInput", + "NativeSelect", "FileInput", "Combobox" + ], + "navigation": ["Anchor", "Breadcrumbs", "Burger", "NavLink", "Pagination", "Stepper", "Tabs", "TabsList", "TabsTab", "TabsPanel"], + "feedback": ["Alert", "Loader", "Notification", "NotificationContainer", "Progress", "RingProgress", "Skeleton"], + "overlays": ["Modal", "Drawer", "DrawerStack", "Popover", "HoverCard", "Tooltip", "FloatingTooltip", "Menu", "MenuTarget", "MenuDropdown", "MenuItem", "Affix"], + "typography": ["Text", "Title", "Highlight", "Mark", "Code", "CodeHighlight", "Blockquote", "List", "ListItem", "Kbd"], + "layout": [ + "AppShell", "AppShellHeader", "AppShellNavbar", "AppShellAside", "AppShellFooter", "AppShellMain", "AppShellSection", + "Container", "Center", "Stack", "Group", "Flex", "Grid", "GridCol", "SimpleGrid", + "Paper", "Card", "CardSection", "Box", "Space", "Divider", "AspectRatio", "ScrollArea" + ], + "data_display": [ + "Accordion", "AccordionItem", "AccordionControl", "AccordionPanel", + "Avatar", "AvatarGroup", "Badge", "Image", "BackgroundImage", + "Indicator", "Spoiler", "Table", "ThemeIcon", "Timeline", "TimelineItem", "Tree" + ], + "charts": ["AreaChart", "BarChart", "LineChart", "PieChart", "DonutChart", "RadarChart", "ScatterChart", "BubbleChart", "CompositeChart", "Sparkline"], + "dates": ["DatePicker", "DateTimePicker", "DateInput", "DatePickerInput", "MonthPicker", "YearPicker", "TimePicker", "TimeInput", "Calendar", "MiniCalendar", "DatesProvider"] + }, + "components": { + "Button": { + "description": "Button component for user interactions", + "props": { + "children": {"type": "any", "description": "Button content"}, + "variant": {"type": "string", "enum": ["filled", "light", "outline", "transparent", "white", "subtle", "default", "gradient"], "default": "filled"}, + "color": {"type": "string", "default": "blue", "description": "Key of theme.colors or CSS color"}, + "size": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl", "compact-xs", "compact-sm", "compact-md", "compact-lg", "compact-xl"], "default": "sm"}, + "radius": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "sm"}, + "disabled": {"type": "boolean", "default": false}, + "loading": {"type": "boolean", "default": false}, + "loaderProps": {"type": "object"}, + "leftSection": {"type": "any", "description": "Content on the left side of label"}, + "rightSection": {"type": "any", "description": "Content on the right side of label"}, + "fullWidth": {"type": "boolean", "default": false}, + "gradient": {"type": "object", "description": "Gradient for gradient variant"}, + "justify": {"type": "string", "enum": ["center", "start", "end", "space-between"], "default": "center"}, + "autoContrast": {"type": "boolean", "default": false}, + "n_clicks": {"type": "integer", "default": 0, "description": "Dash callback trigger"} + } + }, + "ActionIcon": { + "description": "Icon button without text label", + "props": { + "children": {"type": "any", "required": true, "description": "Icon element"}, + "variant": {"type": "string", "enum": ["filled", "light", "outline", "transparent", "white", "subtle", "default", "gradient"], "default": "subtle"}, + "color": {"type": "string", "default": "gray"}, + "size": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "md"}, + "radius": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "sm"}, + "disabled": {"type": "boolean", "default": false}, + "loading": {"type": "boolean", "default": false}, + "autoContrast": {"type": "boolean", "default": false}, + "n_clicks": {"type": "integer", "default": 0} + } + }, + "TextInput": { + "description": "Text input field", + "props": { + "value": {"type": "string", "default": ""}, + "placeholder": {"type": "string"}, + "label": {"type": "any"}, + "description": {"type": "any"}, + "error": {"type": "any"}, + "disabled": {"type": "boolean", "default": false}, + "required": {"type": "boolean", "default": false}, + "size": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "sm"}, + "radius": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "sm"}, + "variant": {"type": "string", "enum": ["default", "filled", "unstyled"], "default": "default"}, + "leftSection": {"type": "any"}, + "rightSection": {"type": "any"}, + "withAsterisk": {"type": "boolean", "default": false}, + "debounce": {"type": "integer", "description": "Debounce delay in ms"}, + "leftSectionPointerEvents": {"type": "string", "enum": ["none", "all"], "default": "none"}, + "rightSectionPointerEvents": {"type": "string", "enum": ["none", "all"], "default": "none"} + } + }, + "NumberInput": { + "description": "Numeric input with optional controls", + "props": { + "value": {"type": "number"}, + "placeholder": {"type": "string"}, + "label": {"type": "any"}, + "description": {"type": "any"}, + "error": {"type": "any"}, + "disabled": {"type": "boolean", "default": false}, + "required": {"type": "boolean", "default": false}, + "min": {"type": "number"}, + "max": {"type": "number"}, + "step": {"type": "number", "default": 1}, + "hideControls": {"type": "boolean", "default": false}, + "size": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "sm"}, + "radius": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "sm"}, + "allowNegative": {"type": "boolean", "default": true}, + "allowDecimal": {"type": "boolean", "default": true}, + "clampBehavior": {"type": "string", "enum": ["strict", "blur", "none"], "default": "blur"}, + "decimalScale": {"type": "integer"}, + "fixedDecimalScale": {"type": "boolean", "default": false}, + "thousandSeparator": {"type": "string"}, + "decimalSeparator": {"type": "string"}, + "prefix": {"type": "string"}, + "suffix": {"type": "string"} + } + }, + "Select": { + "description": "Dropdown select input", + "props": { + "value": {"type": "string"}, + "data": {"type": "array", "required": true, "description": "Array of options: strings or {value, label} objects"}, + "placeholder": {"type": "string"}, + "label": {"type": "any"}, + "description": {"type": "any"}, + "error": {"type": "any"}, + "disabled": {"type": "boolean", "default": false}, + "required": {"type": "boolean", "default": false}, + "searchable": {"type": "boolean", "default": false}, + "clearable": {"type": "boolean", "default": false}, + "nothingFoundMessage": {"type": "string"}, + "size": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "sm"}, + "radius": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "sm"}, + "maxDropdownHeight": {"type": "number", "default": 250}, + "allowDeselect": {"type": "boolean", "default": true}, + "checkIconPosition": {"type": "string", "enum": ["left", "right"], "default": "left"}, + "comboboxProps": {"type": "object"}, + "withScrollArea": {"type": "boolean", "default": true} + } + }, + "MultiSelect": { + "description": "Multiple selection dropdown", + "props": { + "value": {"type": "array", "default": []}, + "data": {"type": "array", "required": true}, + "placeholder": {"type": "string"}, + "label": {"type": "any"}, + "description": {"type": "any"}, + "error": {"type": "any"}, + "disabled": {"type": "boolean", "default": false}, + "required": {"type": "boolean", "default": false}, + "searchable": {"type": "boolean", "default": false}, + "clearable": {"type": "boolean", "default": false}, + "maxValues": {"type": "integer"}, + "hidePickedOptions": {"type": "boolean", "default": false}, + "size": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "sm"}, + "radius": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "sm"}, + "maxDropdownHeight": {"type": "number", "default": 250}, + "withCheckIcon": {"type": "boolean", "default": true} + } + }, + "Checkbox": { + "description": "Checkbox input", + "props": { + "checked": {"type": "boolean", "default": false}, + "label": {"type": "any"}, + "description": {"type": "any"}, + "error": {"type": "any"}, + "disabled": {"type": "boolean", "default": false}, + "indeterminate": {"type": "boolean", "default": false}, + "size": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "sm"}, + "radius": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "sm"}, + "color": {"type": "string", "default": "blue"}, + "labelPosition": {"type": "string", "enum": ["left", "right"], "default": "right"}, + "autoContrast": {"type": "boolean", "default": false}, + "icon": {"type": "any"}, + "iconColor": {"type": "string"} + } + }, + "Switch": { + "description": "Toggle switch input", + "props": { + "checked": {"type": "boolean", "default": false}, + "label": {"type": "any"}, + "description": {"type": "any"}, + "error": {"type": "any"}, + "disabled": {"type": "boolean", "default": false}, + "size": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "sm"}, + "radius": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "xl"}, + "color": {"type": "string", "default": "blue"}, + "onLabel": {"type": "any"}, + "offLabel": {"type": "any"}, + "thumbIcon": {"type": "any"}, + "labelPosition": {"type": "string", "enum": ["left", "right"], "default": "right"} + } + }, + "Slider": { + "description": "Slider input for numeric values", + "props": { + "value": {"type": "number"}, + "min": {"type": "number", "default": 0}, + "max": {"type": "number", "default": 100}, + "step": {"type": "number", "default": 1}, + "label": {"type": "any"}, + "disabled": {"type": "boolean", "default": false}, + "marks": {"type": "array"}, + "size": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "md"}, + "radius": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "xl"}, + "color": {"type": "string", "default": "blue"}, + "showLabelOnHover": {"type": "boolean", "default": true}, + "labelAlwaysOn": {"type": "boolean", "default": false}, + "thumbLabel": {"type": "string"}, + "precision": {"type": "integer", "default": 0}, + "inverted": {"type": "boolean", "default": false}, + "thumbSize": {"type": "number"}, + "restrictToMarks": {"type": "boolean", "default": false} + } + }, + "Alert": { + "description": "Alert component for feedback messages", + "props": { + "children": {"type": "any"}, + "title": {"type": "any"}, + "color": {"type": "string", "default": "blue"}, + "variant": {"type": "string", "enum": ["filled", "light", "outline", "default", "transparent", "white"], "default": "light"}, + "radius": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "sm"}, + "icon": {"type": "any"}, + "withCloseButton": {"type": "boolean", "default": false}, + "closeButtonLabel": {"type": "string"}, + "autoContrast": {"type": "boolean", "default": false} + } + }, + "Loader": { + "description": "Loading indicator", + "props": { + "color": {"type": "string", "default": "blue"}, + "size": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "md"}, + "type": {"type": "string", "enum": ["oval", "bars", "dots"], "default": "oval"} + } + }, + "Progress": { + "description": "Progress bar", + "props": { + "value": {"type": "number", "required": true}, + "color": {"type": "string", "default": "blue"}, + "size": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "md"}, + "radius": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "sm"}, + "striped": {"type": "boolean", "default": false}, + "animated": {"type": "boolean", "default": false}, + "autoContrast": {"type": "boolean", "default": false}, + "transitionDuration": {"type": "number", "default": 100} + } + }, + "Modal": { + "description": "Modal dialog overlay", + "props": { + "children": {"type": "any"}, + "opened": {"type": "boolean", "required": true}, + "title": {"type": "any"}, + "size": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl", "auto"], "default": "md"}, + "radius": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "sm"}, + "centered": {"type": "boolean", "default": false}, + "fullScreen": {"type": "boolean", "default": false}, + "withCloseButton": {"type": "boolean", "default": true}, + "closeOnClickOutside": {"type": "boolean", "default": true}, + "closeOnEscape": {"type": "boolean", "default": true}, + "overlayProps": {"type": "object"}, + "padding": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "md"}, + "transitionProps": {"type": "object"}, + "zIndex": {"type": "number", "default": 200}, + "trapFocus": {"type": "boolean", "default": true}, + "returnFocus": {"type": "boolean", "default": true}, + "lockScroll": {"type": "boolean", "default": true} + } + }, + "Drawer": { + "description": "Sliding panel drawer", + "props": { + "children": {"type": "any"}, + "opened": {"type": "boolean", "required": true}, + "title": {"type": "any"}, + "position": {"type": "string", "enum": ["left", "right", "top", "bottom"], "default": "left"}, + "size": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "md"}, + "radius": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"]}, + "withCloseButton": {"type": "boolean", "default": true}, + "closeOnClickOutside": {"type": "boolean", "default": true}, + "closeOnEscape": {"type": "boolean", "default": true}, + "overlayProps": {"type": "object"}, + "padding": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "md"}, + "zIndex": {"type": "number", "default": 200}, + "offset": {"type": "number", "default": 0}, + "trapFocus": {"type": "boolean", "default": true}, + "returnFocus": {"type": "boolean", "default": true}, + "lockScroll": {"type": "boolean", "default": true} + } + }, + "Tooltip": { + "description": "Tooltip on hover", + "props": { + "children": {"type": "any", "required": true}, + "label": {"type": "any", "required": true}, + "position": {"type": "string", "enum": ["top", "right", "bottom", "left", "top-start", "top-end", "right-start", "right-end", "bottom-start", "bottom-end", "left-start", "left-end"], "default": "top"}, + "color": {"type": "string"}, + "radius": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "sm"}, + "withArrow": {"type": "boolean", "default": false}, + "arrowSize": {"type": "number", "default": 4}, + "arrowOffset": {"type": "number", "default": 5}, + "offset": {"type": "number", "default": 5}, + "multiline": {"type": "boolean", "default": false}, + "disabled": {"type": "boolean", "default": false}, + "openDelay": {"type": "number", "default": 0}, + "closeDelay": {"type": "number", "default": 0}, + "transitionProps": {"type": "object"}, + "zIndex": {"type": "number", "default": 300} + } + }, + "Text": { + "description": "Text component with styling", + "props": { + "children": {"type": "any"}, + "size": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"]}, + "c": {"type": "string", "description": "Color"}, + "fw": {"type": "number", "description": "Font weight"}, + "fs": {"type": "string", "enum": ["normal", "italic"], "description": "Font style"}, + "td": {"type": "string", "enum": ["none", "underline", "line-through"], "description": "Text decoration"}, + "tt": {"type": "string", "enum": ["none", "capitalize", "uppercase", "lowercase"], "description": "Text transform"}, + "ta": {"type": "string", "enum": ["left", "center", "right", "justify"], "description": "Text align"}, + "lineClamp": {"type": "integer"}, + "truncate": {"type": "boolean", "default": false}, + "inherit": {"type": "boolean", "default": false}, + "gradient": {"type": "object"}, + "span": {"type": "boolean", "default": false}, + "lh": {"type": "string", "description": "Line height"} + } + }, + "Title": { + "description": "Heading component", + "props": { + "children": {"type": "any"}, + "order": {"type": "integer", "enum": [1, 2, 3, 4, 5, 6], "default": 1}, + "size": {"type": "string"}, + "c": {"type": "string", "description": "Color"}, + "ta": {"type": "string", "enum": ["left", "center", "right", "justify"]}, + "td": {"type": "string", "enum": ["none", "underline", "line-through"]}, + "tt": {"type": "string", "enum": ["none", "capitalize", "uppercase", "lowercase"]}, + "lineClamp": {"type": "integer"}, + "truncate": {"type": "boolean", "default": false} + } + }, + "Stack": { + "description": "Vertical stack layout", + "props": { + "children": {"type": "any"}, + "gap": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "md"}, + "align": {"type": "string", "enum": ["stretch", "center", "flex-start", "flex-end"], "default": "stretch"}, + "justify": {"type": "string", "enum": ["flex-start", "flex-end", "center", "space-between", "space-around", "space-evenly"], "default": "flex-start"} + } + }, + "Group": { + "description": "Horizontal group layout", + "props": { + "children": {"type": "any"}, + "gap": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "md"}, + "align": {"type": "string", "enum": ["stretch", "center", "flex-start", "flex-end"], "default": "center"}, + "justify": {"type": "string", "enum": ["flex-start", "flex-end", "center", "space-between", "space-around"], "default": "flex-start"}, + "grow": {"type": "boolean", "default": false}, + "wrap": {"type": "string", "enum": ["wrap", "nowrap", "wrap-reverse"], "default": "wrap"}, + "preventGrowOverflow": {"type": "boolean", "default": true} + } + }, + "Flex": { + "description": "Flexbox container", + "props": { + "children": {"type": "any"}, + "gap": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"]}, + "rowGap": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"]}, + "columnGap": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"]}, + "align": {"type": "string", "enum": ["stretch", "center", "flex-start", "flex-end", "baseline"]}, + "justify": {"type": "string", "enum": ["flex-start", "flex-end", "center", "space-between", "space-around", "space-evenly"]}, + "wrap": {"type": "string", "enum": ["wrap", "nowrap", "wrap-reverse"], "default": "nowrap"}, + "direction": {"type": "string", "enum": ["row", "column", "row-reverse", "column-reverse"], "default": "row"} + } + }, + "Grid": { + "description": "Grid layout component", + "props": { + "children": {"type": "any"}, + "columns": {"type": "integer", "default": 12}, + "gutter": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "md"}, + "grow": {"type": "boolean", "default": false}, + "justify": {"type": "string", "enum": ["flex-start", "flex-end", "center", "space-between", "space-around"], "default": "flex-start"}, + "align": {"type": "string", "enum": ["stretch", "center", "flex-start", "flex-end"], "default": "stretch"}, + "overflow": {"type": "string", "enum": ["visible", "hidden"], "default": "visible"} + } + }, + "SimpleGrid": { + "description": "Simple grid with equal columns", + "props": { + "children": {"type": "any"}, + "cols": {"type": "integer", "default": 1}, + "spacing": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "md"}, + "verticalSpacing": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"]} + } + }, + "Container": { + "description": "Centered container with max-width", + "props": { + "children": {"type": "any"}, + "size": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "md"}, + "fluid": {"type": "boolean", "default": false} + } + }, + "Paper": { + "description": "Paper surface component", + "props": { + "children": {"type": "any"}, + "shadow": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"]}, + "radius": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "sm"}, + "p": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "description": "Padding"}, + "withBorder": {"type": "boolean", "default": false} + } + }, + "Card": { + "description": "Card container", + "props": { + "children": {"type": "any"}, + "shadow": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "sm"}, + "radius": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "sm"}, + "padding": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "md"}, + "withBorder": {"type": "boolean", "default": false} + } + }, + "Tabs": { + "description": "Tabbed interface", + "props": { + "children": {"type": "any"}, + "value": {"type": "string"}, + "defaultValue": {"type": "string"}, + "orientation": {"type": "string", "enum": ["horizontal", "vertical"], "default": "horizontal"}, + "variant": {"type": "string", "enum": ["default", "outline", "pills"], "default": "default"}, + "color": {"type": "string", "default": "blue"}, + "radius": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "sm"}, + "placement": {"type": "string", "enum": ["left", "right"], "default": "left"}, + "grow": {"type": "boolean", "default": false}, + "inverted": {"type": "boolean", "default": false}, + "keepMounted": {"type": "boolean", "default": true}, + "activateTabWithKeyboard": {"type": "boolean", "default": true}, + "allowTabDeactivation": {"type": "boolean", "default": false}, + "autoContrast": {"type": "boolean", "default": false} + } + }, + "Accordion": { + "description": "Collapsible content panels", + "props": { + "children": {"type": "any"}, + "value": {"type": "any"}, + "defaultValue": {"type": "any"}, + "multiple": {"type": "boolean", "default": false}, + "variant": {"type": "string", "enum": ["default", "contained", "filled", "separated"], "default": "default"}, + "radius": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "sm"}, + "chevronPosition": {"type": "string", "enum": ["left", "right"], "default": "right"}, + "disableChevronRotation": {"type": "boolean", "default": false}, + "transitionDuration": {"type": "number", "default": 200}, + "chevronSize": {"type": "any"}, + "order": {"type": "integer", "enum": [2, 3, 4, 5, 6]} + } + }, + "Badge": { + "description": "Badge for status or labels", + "props": { + "children": {"type": "any"}, + "color": {"type": "string", "default": "blue"}, + "variant": {"type": "string", "enum": ["filled", "light", "outline", "dot", "gradient", "default", "transparent", "white"], "default": "filled"}, + "size": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "md"}, + "radius": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "xl"}, + "fullWidth": {"type": "boolean", "default": false}, + "leftSection": {"type": "any"}, + "rightSection": {"type": "any"}, + "autoContrast": {"type": "boolean", "default": false}, + "circle": {"type": "boolean", "default": false} + } + }, + "Avatar": { + "description": "User avatar image", + "props": { + "src": {"type": "string"}, + "alt": {"type": "string"}, + "children": {"type": "any", "description": "Fallback content"}, + "color": {"type": "string", "default": "gray"}, + "size": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "md"}, + "radius": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "xl"}, + "variant": {"type": "string", "enum": ["filled", "light", "outline", "gradient", "default", "transparent", "white"], "default": "filled"}, + "autoContrast": {"type": "boolean", "default": false} + } + }, + "Image": { + "description": "Image with fallback", + "props": { + "src": {"type": "string"}, + "alt": {"type": "string"}, + "w": {"type": "any", "description": "Width"}, + "h": {"type": "any", "description": "Height"}, + "radius": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"]}, + "fit": {"type": "string", "enum": ["contain", "cover", "fill", "none", "scale-down"], "default": "cover"}, + "fallbackSrc": {"type": "string"} + } + }, + "Table": { + "description": "Data table component", + "props": { + "children": {"type": "any"}, + "data": {"type": "object", "description": "Table data object with head, body, foot"}, + "striped": {"type": "boolean", "default": false}, + "highlightOnHover": {"type": "boolean", "default": false}, + "withTableBorder": {"type": "boolean", "default": false}, + "withColumnBorders": {"type": "boolean", "default": false}, + "withRowBorders": {"type": "boolean", "default": true}, + "verticalSpacing": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "xs"}, + "horizontalSpacing": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "xs"}, + "captionSide": {"type": "string", "enum": ["top", "bottom"], "default": "bottom"}, + "stickyHeader": {"type": "boolean", "default": false}, + "stickyHeaderOffset": {"type": "number", "default": 0} + } + }, + "AreaChart": { + "description": "Area chart for time series data", + "props": { + "data": {"type": "array", "required": true}, + "dataKey": {"type": "string", "required": true, "description": "X-axis data key"}, + "series": {"type": "array", "required": true, "description": "Array of {name, color} objects"}, + "h": {"type": "any", "description": "Chart height"}, + "w": {"type": "any", "description": "Chart width"}, + "curveType": {"type": "string", "enum": ["bump", "linear", "natural", "monotone", "step", "stepBefore", "stepAfter"], "default": "monotone"}, + "connectNulls": {"type": "boolean", "default": true}, + "withDots": {"type": "boolean", "default": true}, + "withGradient": {"type": "boolean", "default": true}, + "withLegend": {"type": "boolean", "default": false}, + "withTooltip": {"type": "boolean", "default": true}, + "withXAxis": {"type": "boolean", "default": true}, + "withYAxis": {"type": "boolean", "default": true}, + "gridAxis": {"type": "string", "enum": ["x", "y", "xy", "none"], "default": "x"}, + "tickLine": {"type": "string", "enum": ["x", "y", "xy", "none"], "default": "y"}, + "strokeDasharray": {"type": "string"}, + "fillOpacity": {"type": "number", "default": 0.2}, + "splitColors": {"type": "array"}, + "areaChartProps": {"type": "object"}, + "type": {"type": "string", "enum": ["default", "stacked", "percent", "split"], "default": "default"} + } + }, + "BarChart": { + "description": "Bar chart for categorical data", + "props": { + "data": {"type": "array", "required": true}, + "dataKey": {"type": "string", "required": true}, + "series": {"type": "array", "required": true}, + "h": {"type": "any"}, + "w": {"type": "any"}, + "orientation": {"type": "string", "enum": ["horizontal", "vertical"], "default": "vertical"}, + "withLegend": {"type": "boolean", "default": false}, + "withTooltip": {"type": "boolean", "default": true}, + "withXAxis": {"type": "boolean", "default": true}, + "withYAxis": {"type": "boolean", "default": true}, + "gridAxis": {"type": "string", "enum": ["x", "y", "xy", "none"], "default": "x"}, + "tickLine": {"type": "string", "enum": ["x", "y", "xy", "none"], "default": "y"}, + "barProps": {"type": "object"}, + "type": {"type": "string", "enum": ["default", "stacked", "percent", "waterfall"], "default": "default"} + } + }, + "LineChart": { + "description": "Line chart for trends", + "props": { + "data": {"type": "array", "required": true}, + "dataKey": {"type": "string", "required": true}, + "series": {"type": "array", "required": true}, + "h": {"type": "any"}, + "w": {"type": "any"}, + "curveType": {"type": "string", "enum": ["bump", "linear", "natural", "monotone", "step", "stepBefore", "stepAfter"], "default": "monotone"}, + "connectNulls": {"type": "boolean", "default": true}, + "withDots": {"type": "boolean", "default": true}, + "withLegend": {"type": "boolean", "default": false}, + "withTooltip": {"type": "boolean", "default": true}, + "withXAxis": {"type": "boolean", "default": true}, + "withYAxis": {"type": "boolean", "default": true}, + "gridAxis": {"type": "string", "enum": ["x", "y", "xy", "none"], "default": "x"}, + "strokeWidth": {"type": "number", "default": 2} + } + }, + "PieChart": { + "description": "Pie chart for proportions", + "props": { + "data": {"type": "array", "required": true, "description": "Array of {name, value, color} objects"}, + "h": {"type": "any"}, + "w": {"type": "any"}, + "withLabels": {"type": "boolean", "default": false}, + "withLabelsLine": {"type": "boolean", "default": true}, + "withTooltip": {"type": "boolean", "default": true}, + "labelsPosition": {"type": "string", "enum": ["inside", "outside"], "default": "outside"}, + "labelsType": {"type": "string", "enum": ["value", "percent"], "default": "value"}, + "strokeWidth": {"type": "number", "default": 1}, + "strokeColor": {"type": "string"}, + "startAngle": {"type": "number", "default": 0}, + "endAngle": {"type": "number", "default": 360} + } + }, + "DonutChart": { + "description": "Donut chart (pie with hole)", + "props": { + "data": {"type": "array", "required": true}, + "h": {"type": "any"}, + "w": {"type": "any"}, + "withLabels": {"type": "boolean", "default": false}, + "withLabelsLine": {"type": "boolean", "default": true}, + "withTooltip": {"type": "boolean", "default": true}, + "thickness": {"type": "number", "default": 20}, + "chartLabel": {"type": "any"}, + "strokeWidth": {"type": "number", "default": 1}, + "strokeColor": {"type": "string"}, + "startAngle": {"type": "number", "default": 0}, + "endAngle": {"type": "number", "default": 360}, + "paddingAngle": {"type": "number", "default": 0} + } + }, + "DatePicker": { + "description": "Date picker calendar", + "props": { + "value": {"type": "string"}, + "type": {"type": "string", "enum": ["default", "range", "multiple"], "default": "default"}, + "defaultValue": {"type": "any"}, + "allowDeselect": {"type": "boolean", "default": false}, + "allowSingleDateInRange": {"type": "boolean", "default": false}, + "numberOfColumns": {"type": "integer", "default": 1}, + "columnsToScroll": {"type": "integer", "default": 1}, + "ariaLabels": {"type": "object"}, + "hideOutsideDates": {"type": "boolean", "default": false}, + "hideWeekdays": {"type": "boolean", "default": false}, + "weekendDays": {"type": "array", "default": [0, 6]}, + "renderDay": {"type": "any"}, + "minDate": {"type": "string"}, + "maxDate": {"type": "string"}, + "size": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "sm"} + } + }, + "DatePickerInput": { + "description": "Date picker input field", + "props": { + "value": {"type": "string"}, + "label": {"type": "any"}, + "description": {"type": "any"}, + "error": {"type": "any"}, + "placeholder": {"type": "string"}, + "clearable": {"type": "boolean", "default": false}, + "type": {"type": "string", "enum": ["default", "range", "multiple"], "default": "default"}, + "valueFormat": {"type": "string", "default": "MMMM D, YYYY"}, + "size": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "sm"}, + "radius": {"type": "string", "enum": ["xs", "sm", "md", "lg", "xl"], "default": "sm"}, + "disabled": {"type": "boolean", "default": false}, + "required": {"type": "boolean", "default": false}, + "minDate": {"type": "string"}, + "maxDate": {"type": "string"}, + "popoverProps": {"type": "object"}, + "dropdownType": {"type": "string", "enum": ["popover", "modal"], "default": "popover"} + } + }, + "DatesProvider": { + "description": "Provider for date localization settings", + "props": { + "children": {"type": "any", "required": true}, + "settings": {"type": "object", "description": "Locale and formatting settings"} + } + } + } +} diff --git a/mcp-servers/viz-platform/requirements.txt b/mcp-servers/viz-platform/requirements.txt index 93859ca..4b5159e 100644 --- a/mcp-servers/viz-platform/requirements.txt +++ b/mcp-servers/viz-platform/requirements.txt @@ -4,7 +4,7 @@ mcp>=0.9.0 # Visualization plotly>=5.18.0 dash>=2.14.0 -dash-mantine-components>=0.14.0 +dash-mantine-components>=2.0.0 # Utilities python-dotenv>=1.0.0 diff --git a/mcp-servers/viz-platform/scripts/generate-dmc-registry.py b/mcp-servers/viz-platform/scripts/generate-dmc-registry.py new file mode 100644 index 0000000..b500dd9 --- /dev/null +++ b/mcp-servers/viz-platform/scripts/generate-dmc-registry.py @@ -0,0 +1,262 @@ +#!/usr/bin/env python3 +""" +Generate DMC Component Registry from installed dash-mantine-components package. + +This script introspects the installed DMC package and generates a JSON registry +file containing component definitions, props, types, and defaults. + +Usage: + python generate-dmc-registry.py [--output registry/dmc_X_Y.json] + +Requirements: + - dash-mantine-components must be installed + - Run from the mcp-servers/viz-platform directory +""" +import argparse +import inspect +import json +import sys +from datetime import date +from pathlib import Path +from typing import Any, Dict, List, Optional, get_type_hints + + +def get_dmc_version() -> Optional[str]: + """Get installed DMC version.""" + try: + from importlib.metadata import version + return version('dash-mantine-components') + except Exception: + return None + + +def get_component_categories() -> Dict[str, List[str]]: + """Define component categories.""" + return { + "buttons": ["Button", "ActionIcon", "CopyButton", "FileButton", "UnstyledButton"], + "inputs": [ + "TextInput", "PasswordInput", "NumberInput", "Textarea", + "Select", "MultiSelect", "Checkbox", "Switch", "Radio", + "Slider", "RangeSlider", "ColorInput", "ColorPicker", + "DateInput", "DatePicker", "TimeInput" + ], + "navigation": ["Anchor", "Breadcrumbs", "Burger", "NavLink", "Pagination", "Stepper", "Tabs"], + "feedback": ["Alert", "Loader", "Notification", "Progress", "RingProgress", "Skeleton"], + "overlays": ["Dialog", "Drawer", "HoverCard", "Menu", "Modal", "Popover", "Tooltip"], + "typography": ["Blockquote", "Code", "Highlight", "Mark", "Text", "Title"], + "layout": [ + "AppShell", "AspectRatio", "Center", "Container", "Flex", + "Grid", "Group", "Paper", "SimpleGrid", "Space", "Stack" + ], + "data": [ + "Accordion", "Avatar", "Badge", "Card", "Image", + "Indicator", "Kbd", "Spoiler", "Table", "ThemeIcon", "Timeline" + ] + } + + +def extract_prop_type(prop_info: Dict[str, Any]) -> Dict[str, Any]: + """Extract prop type information from Dash component prop.""" + result = {"type": "any"} + + if 'type' not in prop_info: + return result + + prop_type = prop_info['type'] + + if isinstance(prop_type, dict): + type_name = prop_type.get('name', 'any') + + # Map Dash types to JSON schema types + type_mapping = { + 'string': 'string', + 'number': 'number', + 'bool': 'boolean', + 'boolean': 'boolean', + 'array': 'array', + 'object': 'object', + 'node': 'any', + 'element': 'any', + 'any': 'any', + 'func': 'any', + } + + result['type'] = type_mapping.get(type_name, 'any') + + # Handle enums + if type_name == 'enum' and 'value' in prop_type: + values = prop_type['value'] + if isinstance(values, list): + enum_values = [] + for v in values: + if isinstance(v, dict) and 'value' in v: + # Remove quotes from string values + val = v['value'].strip("'\"") + enum_values.append(val) + elif isinstance(v, str): + enum_values.append(v.strip("'\"")) + if enum_values: + result['enum'] = enum_values + result['type'] = 'string' + + # Handle union types + elif type_name == 'union' and 'value' in prop_type: + # For unions, just mark as any for simplicity + result['type'] = 'any' + + elif isinstance(prop_type, str): + result['type'] = prop_type + + return result + + +def extract_component_props(component_class) -> Dict[str, Any]: + """Extract props from a Dash component class.""" + props = {} + + # Try to get _prop_names or similar + if hasattr(component_class, '_prop_names'): + prop_names = component_class._prop_names + else: + prop_names = [] + + # Try to get _type attribute for prop definitions + if hasattr(component_class, '_type'): + prop_types = getattr(component_class, '_type', {}) + else: + prop_types = {} + + # Get default values + if hasattr(component_class, '_default_props'): + defaults = component_class._default_props + else: + defaults = {} + + # Try to extract from _prop_descriptions + if hasattr(component_class, '_prop_descriptions'): + descriptions = component_class._prop_descriptions + else: + descriptions = {} + + for prop_name in prop_names: + if prop_name.startswith('_'): + continue + + prop_info = {} + + # Get type info if available + if prop_name in prop_types: + prop_info = extract_prop_type({'type': prop_types[prop_name]}) + else: + prop_info = {'type': 'any'} + + # Add default if exists + if prop_name in defaults: + prop_info['default'] = defaults[prop_name] + + # Add description if exists + if prop_name in descriptions: + prop_info['description'] = descriptions[prop_name] + + props[prop_name] = prop_info + + return props + + +def generate_registry() -> Dict[str, Any]: + """Generate the component registry from installed DMC.""" + try: + import dash_mantine_components as dmc + except ImportError: + print("ERROR: dash-mantine-components not installed") + print("Install with: pip install dash-mantine-components") + sys.exit(1) + + version = get_dmc_version() + categories = get_component_categories() + + registry = { + "version": version, + "generated": date.today().isoformat(), + "categories": categories, + "components": {} + } + + # Get all components from categories + all_components = set() + for comp_list in categories.values(): + all_components.update(comp_list) + + # Extract props for each component + for comp_name in sorted(all_components): + if hasattr(dmc, comp_name): + comp_class = getattr(dmc, comp_name) + try: + props = extract_component_props(comp_class) + if props: + registry["components"][comp_name] = { + "description": comp_class.__doc__ or f"{comp_name} component", + "props": props + } + print(f" Extracted: {comp_name} ({len(props)} props)") + except Exception as e: + print(f" Warning: Failed to extract {comp_name}: {e}") + else: + print(f" Warning: Component not found: {comp_name}") + + return registry + + +def main(): + parser = argparse.ArgumentParser( + description="Generate DMC component registry from installed package" + ) + parser.add_argument( + '--output', '-o', + type=str, + help='Output file path (default: auto-generated based on version)' + ) + parser.add_argument( + '--dry-run', + action='store_true', + help='Print to stdout instead of writing file' + ) + + args = parser.parse_args() + + print("Generating DMC Component Registry...") + print("=" * 50) + + registry = generate_registry() + + print("=" * 50) + print(f"Generated registry for DMC {registry['version']}") + print(f"Total components: {len(registry['components'])}") + + if args.dry_run: + print(json.dumps(registry, indent=2)) + return + + # Determine output path + if args.output: + output_path = Path(args.output) + else: + version = registry['version'] + if version: + major_minor = '_'.join(version.split('.')[:2]) + output_path = Path(__file__).parent.parent / 'registry' / f'dmc_{major_minor}.json' + else: + output_path = Path(__file__).parent.parent / 'registry' / 'dmc_unknown.json' + + # Create directory if needed + output_path.parent.mkdir(parents=True, exist_ok=True) + + # Write registry + with open(output_path, 'w') as f: + json.dump(registry, indent=2, fp=f) + + print(f"Registry written to: {output_path}") + + +if __name__ == "__main__": + main()