Initial commit: Clean DSS implementation

Migrated from design-system-swarm with fresh git history.
Old project history preserved in /home/overbits/apps/design-system-swarm

Core components:
- MCP Server (Python FastAPI with mcp 1.23.1)
- Claude Plugin (agents, commands, skills, strategies, hooks, core)
- DSS Backend (dss-mvp1 - token translation, Figma sync)
- Admin UI (Node.js/React)
- Server (Node.js/Express)
- Storybook integration (dss-mvp1/.storybook)

Self-contained configuration:
- All paths relative or use DSS_BASE_PATH=/home/overbits/dss
- PYTHONPATH configured for dss-mvp1 and dss-claude-plugin
- .env file with all configuration
- Claude plugin uses ${CLAUDE_PLUGIN_ROOT} for portability

Migration completed: $(date)
🤖 Clean migration with full functionality preserved
This commit is contained in:
Digital Production Factory
2025-12-09 18:45:48 -03:00
commit 276ed71f31
884 changed files with 373737 additions and 0 deletions

View File

@@ -0,0 +1,275 @@
"""
Dynamic Plugin Registry for DSS MCP Server
Automatically discovers and registers MCP tools from the plugins/ directory.
Plugins follow a simple contract: export TOOLS list and a handler class with execute_tool() method.
"""
import pkgutil
import importlib
import inspect
import logging
import types as python_types
from typing import List, Dict, Any, Optional
from mcp import types
logger = logging.getLogger("dss.mcp.plugins")
class PluginRegistry:
"""
Discovers and manages dynamically loaded plugins.
Plugin Contract:
- Must export TOOLS: List[types.Tool] - MCP tool definitions
- Must have a class with execute_tool(name: str, arguments: dict) method
- Optional: PLUGIN_METADATA dict with name, version, author
Example Plugin Structure:
```python
from mcp import types
PLUGIN_METADATA = {
"name": "Example Plugin",
"version": "1.0.0",
"author": "DSS Team"
}
TOOLS = [
types.Tool(
name="example_tool",
description="Example tool",
inputSchema={...}
)
]
class PluginTools:
async def execute_tool(self, name: str, arguments: dict):
if name == "example_tool":
return {"result": "success"}
raise ValueError(f"Unknown tool: {name}")
```
"""
def __init__(self):
self.tools: List[types.Tool] = []
self.handlers: Dict[str, Any] = {} # tool_name -> handler_instance
self.plugins: List[Dict[str, Any]] = [] # plugin metadata
self._loaded_modules: set = set()
def load_plugins(self, plugins_package_name: str = "dss_mcp.plugins"):
"""
Scans the plugins directory and registers valid tool modules.
Args:
plugins_package_name: Fully qualified name of plugins package
Default: "dss_mcp.plugins" (works when called from tools/ dir)
"""
try:
# Dynamically import the plugins package
plugins_pkg = importlib.import_module(plugins_package_name)
path = plugins_pkg.__path__
prefix = plugins_pkg.__name__ + "."
logger.info(f"Scanning for plugins in: {path}")
# Iterate through all modules in the plugins directory
for _, name, is_pkg in pkgutil.iter_modules(path, prefix):
# Skip packages (only load .py files)
if is_pkg:
continue
# Skip template and private modules
module_basename = name.split('.')[-1]
if module_basename.startswith('_'):
logger.debug(f"Skipping private module: {module_basename}")
continue
try:
module = importlib.import_module(name)
self._register_module(module)
except Exception as e:
logger.error(f"Failed to load plugin module {name}: {e}", exc_info=True)
except ImportError as e:
logger.warning(f"Plugins package not found: {plugins_package_name} ({e})")
logger.info("Server will run without plugins")
def _register_module(self, module: python_types.ModuleType):
"""
Validates and registers a single plugin module.
Args:
module: The imported plugin module
"""
module_name = module.__name__
# Check if already loaded
if module_name in self._loaded_modules:
logger.debug(f"Module already loaded: {module_name}")
return
# Contract Check 1: Must export TOOLS list
if not hasattr(module, 'TOOLS'):
logger.debug(f"Module {module_name} has no TOOLS export, skipping")
return
if not isinstance(module.TOOLS, list):
logger.error(f"Module {module_name} TOOLS must be a list, got {type(module.TOOLS)}")
return
if len(module.TOOLS) == 0:
logger.warning(f"Module {module_name} has empty TOOLS list")
return
# Contract Check 2: Must have a class with execute_tool method
handler_instance = self._find_and_instantiate_handler(module)
if not handler_instance:
logger.warning(f"Plugin {module_name} has TOOLS but no valid handler class")
return
# Contract Check 3: execute_tool must be async (coroutine)
execute_tool_method = getattr(handler_instance, 'execute_tool', None)
if execute_tool_method and not inspect.iscoroutinefunction(execute_tool_method):
logger.error(
f"Plugin '{module_name}' is invalid: 'PluginTools.execute_tool' must be "
f"an async function ('async def'). Skipping plugin."
)
return
# Extract metadata
metadata = getattr(module, 'PLUGIN_METADATA', {})
plugin_name = metadata.get('name', module_name.split('.')[-1])
plugin_version = metadata.get('version', 'unknown')
# Validate tools and check for name collisions
registered_count = 0
for tool in module.TOOLS:
if not hasattr(tool, 'name'):
logger.error(f"Tool in {module_name} missing 'name' attribute")
continue
# Check for name collision
if tool.name in self.handlers:
logger.error(
f"Tool name collision: '{tool.name}' already registered. "
f"Skipping duplicate from {module_name}"
)
continue
# Register tool
self.tools.append(tool)
self.handlers[tool.name] = handler_instance
registered_count += 1
logger.debug(f"Registered tool: {tool.name}")
# Track plugin metadata
self.plugins.append({
"name": plugin_name,
"version": plugin_version,
"module": module_name,
"tools_count": registered_count,
"author": metadata.get('author', 'unknown')
})
self._loaded_modules.add(module_name)
logger.info(
f"Loaded plugin: {plugin_name} v{plugin_version} "
f"({registered_count} tools from {module_name})"
)
def _find_and_instantiate_handler(self, module: python_types.ModuleType) -> Optional[Any]:
"""
Finds a class implementing execute_tool and instantiates it.
Args:
module: The plugin module to search
Returns:
Instantiated handler class or None if not found
"""
for name, obj in inspect.getmembers(module, inspect.isclass):
# Only consider classes defined in this module (not imports)
if obj.__module__ != module.__name__:
continue
# Look for execute_tool method
if hasattr(obj, 'execute_tool'):
try:
# Try to instantiate with no args
instance = obj()
logger.debug(f"Instantiated handler class: {name}")
return instance
except TypeError:
# Try with **kwargs for flexible initialization
try:
instance = obj(**{})
logger.debug(f"Instantiated handler class with kwargs: {name}")
return instance
except Exception as e:
logger.error(
f"Failed to instantiate handler {name} in {module.__name__}: {e}"
)
return None
except Exception as e:
logger.error(
f"Failed to instantiate handler {name} in {module.__name__}: {e}"
)
return None
return None
async def execute_tool(self, name: str, arguments: dict) -> Any:
"""
Routes tool execution to the correct plugin handler.
Args:
name: Tool name
arguments: Tool arguments
Returns:
Tool execution result
Raises:
ValueError: If tool not found in registry
"""
if name not in self.handlers:
raise ValueError(f"Tool '{name}' not found in plugin registry")
handler = self.handlers[name]
# Support both async and sync implementations
if inspect.iscoroutinefunction(handler.execute_tool):
return await handler.execute_tool(name, arguments)
else:
return handler.execute_tool(name, arguments)
def get_all_tools(self) -> List[types.Tool]:
"""Get merged list of all plugin tools"""
return self.tools.copy()
def get_plugin_info(self) -> List[Dict[str, Any]]:
"""Get metadata for all loaded plugins"""
return self.plugins.copy()
def reload_plugins(self, plugins_package_name: str = "dss_mcp.plugins"):
"""
Reload all plugins (useful for development).
WARNING: This clears all registered plugins and reloads from scratch.
Args:
plugins_package_name: Fully qualified name of plugins package
"""
logger.info("Reloading all plugins...")
# Clear existing registrations
self.tools.clear()
self.handlers.clear()
self.plugins.clear()
self._loaded_modules.clear()
# Reload
self.load_plugins(plugins_package_name)
logger.info(f"Plugin reload complete. Loaded {len(self.plugins)} plugins, {len(self.tools)} tools")