development #184
7
mcp-servers/viz-platform/mcp_server/__init__.py
Normal file
7
mcp-servers/viz-platform/mcp_server/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
"""
|
||||
viz-platform MCP Server package.
|
||||
|
||||
Provides Dash Mantine Components validation and visualization tools to Claude Code.
|
||||
"""
|
||||
|
||||
__version__ = "1.0.0"
|
||||
172
mcp-servers/viz-platform/mcp_server/config.py
Normal file
172
mcp-servers/viz-platform/mcp_server/config.py
Normal file
@@ -0,0 +1,172 @@
|
||||
"""
|
||||
Configuration loader for viz-platform MCP Server.
|
||||
|
||||
Implements hybrid configuration system:
|
||||
- System-level: ~/.config/claude/viz-platform.env (theme preferences)
|
||||
- Project-level: .env (DMC version overrides)
|
||||
- Auto-detection: DMC package version from installed package
|
||||
"""
|
||||
from pathlib import Path
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
import logging
|
||||
from typing import Dict, Optional
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class VizPlatformConfig:
|
||||
"""Hybrid configuration loader for viz-platform tools"""
|
||||
|
||||
def __init__(self):
|
||||
self.dmc_version: Optional[str] = None
|
||||
self.theme_dir_user: Path = Path.home() / '.config' / 'claude' / 'themes'
|
||||
self.theme_dir_project: Optional[Path] = None
|
||||
self.default_theme: Optional[str] = None
|
||||
|
||||
def load(self) -> Dict[str, any]:
|
||||
"""
|
||||
Load configuration from system and project levels.
|
||||
|
||||
Returns:
|
||||
Dict containing dmc_version, theme directories, and availability flags
|
||||
"""
|
||||
# Load system config
|
||||
system_config = Path.home() / '.config' / 'claude' / 'viz-platform.env'
|
||||
if system_config.exists():
|
||||
load_dotenv(system_config)
|
||||
logger.info(f"Loaded system configuration from {system_config}")
|
||||
|
||||
# Find project directory
|
||||
project_dir = self._find_project_directory()
|
||||
|
||||
# Load project config (overrides system)
|
||||
if project_dir:
|
||||
project_config = project_dir / '.env'
|
||||
if project_config.exists():
|
||||
load_dotenv(project_config, override=True)
|
||||
logger.info(f"Loaded project configuration from {project_config}")
|
||||
|
||||
# Set project theme directory
|
||||
self.theme_dir_project = project_dir / '.viz-platform' / 'themes'
|
||||
|
||||
# Get DMC version (from env or auto-detect)
|
||||
self.dmc_version = os.getenv('DMC_VERSION') or self._detect_dmc_version()
|
||||
self.default_theme = os.getenv('VIZ_DEFAULT_THEME')
|
||||
|
||||
# Ensure user theme directory exists
|
||||
self.theme_dir_user.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
return {
|
||||
'dmc_version': self.dmc_version,
|
||||
'dmc_available': self.dmc_version is not None,
|
||||
'theme_dir_user': str(self.theme_dir_user),
|
||||
'theme_dir_project': str(self.theme_dir_project) if self.theme_dir_project else None,
|
||||
'default_theme': self.default_theme,
|
||||
'project_dir': str(project_dir) if project_dir else None
|
||||
}
|
||||
|
||||
def _detect_dmc_version(self) -> Optional[str]:
|
||||
"""
|
||||
Auto-detect installed Dash Mantine Components version.
|
||||
|
||||
Returns:
|
||||
Version string (e.g., "0.14.7") or None if not installed
|
||||
"""
|
||||
try:
|
||||
from importlib.metadata import version
|
||||
dmc_version = version('dash-mantine-components')
|
||||
logger.info(f"Detected DMC version: {dmc_version}")
|
||||
return dmc_version
|
||||
except ImportError:
|
||||
logger.warning("dash-mantine-components not installed - using registry fallback")
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not detect DMC version: {e}")
|
||||
return None
|
||||
|
||||
def _find_project_directory(self) -> Optional[Path]:
|
||||
"""
|
||||
Find the user's project directory.
|
||||
|
||||
Returns:
|
||||
Path to project directory, or None if not found
|
||||
"""
|
||||
# Strategy 1: Check CLAUDE_PROJECT_DIR environment variable
|
||||
project_dir = os.getenv('CLAUDE_PROJECT_DIR')
|
||||
if project_dir:
|
||||
path = Path(project_dir)
|
||||
if path.exists():
|
||||
logger.info(f"Found project directory from CLAUDE_PROJECT_DIR: {path}")
|
||||
return path
|
||||
|
||||
# Strategy 2: Check PWD
|
||||
pwd = os.getenv('PWD')
|
||||
if pwd:
|
||||
path = Path(pwd)
|
||||
if path.exists() and (
|
||||
(path / '.git').exists() or
|
||||
(path / '.env').exists() or
|
||||
(path / '.viz-platform').exists()
|
||||
):
|
||||
logger.info(f"Found project directory from PWD: {path}")
|
||||
return path
|
||||
|
||||
# Strategy 3: Check current working directory
|
||||
cwd = Path.cwd()
|
||||
if (cwd / '.git').exists() or (cwd / '.env').exists() or (cwd / '.viz-platform').exists():
|
||||
logger.info(f"Found project directory from cwd: {cwd}")
|
||||
return cwd
|
||||
|
||||
logger.debug("Could not determine project directory")
|
||||
return None
|
||||
|
||||
|
||||
def load_config() -> Dict[str, any]:
|
||||
"""
|
||||
Convenience function to load configuration.
|
||||
|
||||
Returns:
|
||||
Configuration dictionary
|
||||
"""
|
||||
config = VizPlatformConfig()
|
||||
return config.load()
|
||||
|
||||
|
||||
def check_dmc_version() -> Dict[str, any]:
|
||||
"""
|
||||
Check DMC installation status for SessionStart hook.
|
||||
|
||||
Returns:
|
||||
Dict with installation status and version info
|
||||
"""
|
||||
config = load_config()
|
||||
|
||||
if not config.get('dmc_available'):
|
||||
return {
|
||||
'installed': False,
|
||||
'message': 'dash-mantine-components not installed. Run: pip install dash-mantine-components'
|
||||
}
|
||||
|
||||
version = config.get('dmc_version', 'unknown')
|
||||
|
||||
# Check for registry compatibility
|
||||
registry_path = Path(__file__).parent.parent / 'registry'
|
||||
major_minor = '.'.join(version.split('.')[:2]) if version else None
|
||||
registry_file = registry_path / f'dmc_{major_minor.replace(".", "_")}.json' if major_minor else None
|
||||
|
||||
if registry_file and registry_file.exists():
|
||||
return {
|
||||
'installed': True,
|
||||
'version': version,
|
||||
'registry_available': True,
|
||||
'message': f'DMC {version} ready with component registry'
|
||||
}
|
||||
else:
|
||||
return {
|
||||
'installed': True,
|
||||
'version': version,
|
||||
'registry_available': False,
|
||||
'message': f'DMC {version} installed but no matching registry. Validation may be limited.'
|
||||
}
|
||||
72
mcp-servers/viz-platform/mcp_server/dmc_tools.py
Normal file
72
mcp-servers/viz-platform/mcp_server/dmc_tools.py
Normal file
@@ -0,0 +1,72 @@
|
||||
"""
|
||||
DMC (Dash Mantine Components) validation tools.
|
||||
|
||||
Provides component constraint layer to prevent Claude from hallucinating invalid props.
|
||||
Tools implemented in Issue #172.
|
||||
"""
|
||||
import logging
|
||||
from typing import Dict, List, Optional, Any
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DMCTools:
|
||||
"""
|
||||
DMC component validation tools.
|
||||
|
||||
These tools provide the "constraint layer" that validates component usage
|
||||
against a version-locked registry of DMC components.
|
||||
"""
|
||||
|
||||
def __init__(self, registry=None):
|
||||
"""
|
||||
Initialize DMC tools with component registry.
|
||||
|
||||
Args:
|
||||
registry: ComponentRegistry instance (from Issue #171)
|
||||
"""
|
||||
self.registry = registry
|
||||
|
||||
async def list_components(self, category: Optional[str] = None) -> Dict[str, Any]:
|
||||
"""
|
||||
List available DMC components, optionally filtered by category.
|
||||
|
||||
Args:
|
||||
category: Optional category filter (e.g., "inputs", "buttons", "navigation")
|
||||
|
||||
Returns:
|
||||
Dict with components grouped by category
|
||||
"""
|
||||
# Implementation in Issue #172
|
||||
raise NotImplementedError("Implemented in Issue #172")
|
||||
|
||||
async def get_component_props(self, component: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Get props schema for a specific component.
|
||||
|
||||
Args:
|
||||
component: Component name (e.g., "Button", "TextInput")
|
||||
|
||||
Returns:
|
||||
Dict with props, types, defaults, and enum values
|
||||
"""
|
||||
# Implementation in Issue #172
|
||||
raise NotImplementedError("Implemented in Issue #172")
|
||||
|
||||
async def validate_component(
|
||||
self,
|
||||
component: str,
|
||||
props: Dict[str, Any]
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Validate component props against registry.
|
||||
|
||||
Args:
|
||||
component: Component name
|
||||
props: Props to validate
|
||||
|
||||
Returns:
|
||||
Dict with valid: bool, errors: [], warnings: []
|
||||
"""
|
||||
# Implementation in Issue #172
|
||||
raise NotImplementedError("Implemented in Issue #172")
|
||||
129
mcp-servers/viz-platform/mcp_server/server.py
Normal file
129
mcp-servers/viz-platform/mcp_server/server.py
Normal file
@@ -0,0 +1,129 @@
|
||||
"""
|
||||
MCP Server entry point for viz-platform integration.
|
||||
|
||||
Provides Dash Mantine Components validation, charting, layout, theming, and page tools
|
||||
to Claude Code via JSON-RPC 2.0 over stdio.
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
import json
|
||||
from mcp.server import Server
|
||||
from mcp.server.stdio import stdio_server
|
||||
from mcp.types import Tool, TextContent
|
||||
|
||||
from .config import VizPlatformConfig
|
||||
|
||||
# Suppress noisy MCP validation warnings on stderr
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logging.getLogger("root").setLevel(logging.ERROR)
|
||||
logging.getLogger("mcp").setLevel(logging.ERROR)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class VizPlatformMCPServer:
|
||||
"""MCP Server for visualization platform integration"""
|
||||
|
||||
def __init__(self):
|
||||
self.server = Server("viz-platform-mcp")
|
||||
self.config = None
|
||||
# Tool handlers will be added in subsequent issues
|
||||
# self.dmc_tools = None
|
||||
# self.chart_tools = None
|
||||
# self.layout_tools = None
|
||||
# self.theme_tools = None
|
||||
# self.page_tools = None
|
||||
|
||||
async def initialize(self):
|
||||
"""Initialize server and load configuration."""
|
||||
try:
|
||||
config_loader = VizPlatformConfig()
|
||||
self.config = config_loader.load()
|
||||
|
||||
# Log available capabilities
|
||||
caps = []
|
||||
if self.config.get('dmc_available'):
|
||||
caps.append(f"DMC {self.config.get('dmc_version')}")
|
||||
else:
|
||||
caps.append("DMC (not installed)")
|
||||
|
||||
logger.info(f"viz-platform MCP Server initialized with: {', '.join(caps)}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to initialize: {e}")
|
||||
raise
|
||||
|
||||
def setup_tools(self):
|
||||
"""Register all available tools with the MCP server"""
|
||||
|
||||
@self.server.list_tools()
|
||||
async def list_tools() -> list[Tool]:
|
||||
"""Return list of available tools"""
|
||||
tools = []
|
||||
|
||||
# DMC validation tools (Issue #172)
|
||||
# - list_components
|
||||
# - get_component_props
|
||||
# - validate_component
|
||||
|
||||
# Chart tools (Issue #173)
|
||||
# - chart_create
|
||||
# - chart_configure_interaction
|
||||
|
||||
# Layout tools (Issue #174)
|
||||
# - layout_create
|
||||
# - layout_add_filter
|
||||
# - layout_set_grid
|
||||
|
||||
# Theme tools (Issue #175)
|
||||
# - theme_create
|
||||
# - theme_extend
|
||||
# - theme_validate
|
||||
# - theme_export_css
|
||||
|
||||
# Page tools (Issue #176)
|
||||
# - page_create
|
||||
# - page_add_navbar
|
||||
# - page_set_auth
|
||||
|
||||
return tools
|
||||
|
||||
@self.server.call_tool()
|
||||
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
|
||||
"""Handle tool invocation."""
|
||||
try:
|
||||
# Tool routing will be added as tools are implemented
|
||||
# DMC tools
|
||||
# if name == "list_components":
|
||||
# result = await self.dmc_tools.list_components(**arguments)
|
||||
# ...
|
||||
|
||||
raise ValueError(f"Unknown tool: {name}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Tool {name} failed: {e}")
|
||||
return [TextContent(
|
||||
type="text",
|
||||
text=json.dumps({"error": str(e)}, indent=2)
|
||||
)]
|
||||
|
||||
async def run(self):
|
||||
"""Run the MCP server"""
|
||||
await self.initialize()
|
||||
self.setup_tools()
|
||||
|
||||
async with stdio_server() as (read_stream, write_stream):
|
||||
await self.server.run(
|
||||
read_stream,
|
||||
write_stream,
|
||||
self.server.create_initialization_options()
|
||||
)
|
||||
|
||||
|
||||
async def main():
|
||||
"""Main entry point"""
|
||||
server = VizPlatformMCPServer()
|
||||
await server.run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
45
mcp-servers/viz-platform/pyproject.toml
Normal file
45
mcp-servers/viz-platform/pyproject.toml
Normal file
@@ -0,0 +1,45 @@
|
||||
[build-system]
|
||||
requires = ["setuptools>=61.0", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "viz-platform-mcp"
|
||||
version = "1.0.0"
|
||||
description = "MCP Server for visualization with Dash Mantine Components validation and theming"
|
||||
readme = "README.md"
|
||||
license = {text = "MIT"}
|
||||
requires-python = ">=3.10"
|
||||
authors = [
|
||||
{name = "Leo Miranda"}
|
||||
]
|
||||
classifiers = [
|
||||
"Development Status :: 4 - Beta",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
]
|
||||
dependencies = [
|
||||
"mcp>=0.9.0",
|
||||
"plotly>=5.18.0",
|
||||
"dash>=2.14.0",
|
||||
"dash-mantine-components>=0.14.0",
|
||||
"python-dotenv>=1.0.0",
|
||||
"pydantic>=2.5.0",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"pytest>=7.4.3",
|
||||
"pytest-asyncio>=0.23.0",
|
||||
]
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["."]
|
||||
include = ["mcp_server*"]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
asyncio_mode = "auto"
|
||||
testpaths = ["tests"]
|
||||
15
mcp-servers/viz-platform/requirements.txt
Normal file
15
mcp-servers/viz-platform/requirements.txt
Normal file
@@ -0,0 +1,15 @@
|
||||
# MCP SDK
|
||||
mcp>=0.9.0
|
||||
|
||||
# Visualization
|
||||
plotly>=5.18.0
|
||||
dash>=2.14.0
|
||||
dash-mantine-components>=0.14.0
|
||||
|
||||
# Utilities
|
||||
python-dotenv>=1.0.0
|
||||
pydantic>=2.5.0
|
||||
|
||||
# Testing
|
||||
pytest>=7.4.3
|
||||
pytest-asyncio>=0.23.0
|
||||
1
mcp-servers/viz-platform/tests/__init__.py
Normal file
1
mcp-servers/viz-platform/tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""viz-platform MCP Server tests."""
|
||||
Reference in New Issue
Block a user