mirror of
https://github.com/SuperClaude-Org/SuperClaude_Framework.git
synced 2025-12-29 16:16:08 +00:00
Implement two-stage installation with simplified MCP configuration
- Stage 1: MCP server selection (Context7, Sequential, Magic, Playwright, Serena, Morphllm) - Stage 2: Framework components (Core, Modes, Commands, Agents, MCP Docs) Major Changes: - Created MCP config snippets in SuperClaude/MCP/configs/ for JSON-based configuration - Simplified MCPComponent to modify .claude.json instead of npm/subprocess installation - Added ModesComponent for behavioral modes (Brainstorming, Introspection, etc.) - Added MCPDocsComponent for dynamic MCP documentation installation - Completely removed Hooks component (deleted hooks.py, updated all references) - Implemented two-stage interactive installation flow - Updated profiles and configuration files Benefits: - Much simpler and more reliable MCP configuration - Clear separation between server setup and documentation - Auto-selection of MCP docs based on server choices - User-friendly two-stage selection process 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
9
SuperClaude/MCP/configs/context7.json
Normal file
9
SuperClaude/MCP/configs/context7.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"context7": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": [
|
||||||
|
"-y",
|
||||||
|
"@upstash/context7-mcp@latest"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
12
SuperClaude/MCP/configs/magic.json
Normal file
12
SuperClaude/MCP/configs/magic.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"magic": {
|
||||||
|
"type": "stdio",
|
||||||
|
"command": "npx",
|
||||||
|
"args": [
|
||||||
|
"@21st-dev/magic"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"TWENTYFIRST_API_KEY": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
SuperClaude/MCP/configs/morphllm.json
Normal file
13
SuperClaude/MCP/configs/morphllm.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"morphllm-fast-apply": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": [
|
||||||
|
"@morph-llm/morph-fast-apply",
|
||||||
|
"/app/"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"MORPH_API_KEY": "",
|
||||||
|
"ALL_TOOLS": "true"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
8
SuperClaude/MCP/configs/playwright.json
Normal file
8
SuperClaude/MCP/configs/playwright.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"playwright": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": [
|
||||||
|
"@playwright/mcp@latest"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
9
SuperClaude/MCP/configs/sequential.json
Normal file
9
SuperClaude/MCP/configs/sequential.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"sequential-thinking": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": [
|
||||||
|
"-y",
|
||||||
|
"@modelcontextprotocol/server-sequential-thinking"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
13
SuperClaude/MCP/configs/serena.json
Normal file
13
SuperClaude/MCP/configs/serena.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"serena": {
|
||||||
|
"command": "uv",
|
||||||
|
"args": [
|
||||||
|
"run",
|
||||||
|
"serena",
|
||||||
|
"start-mcp-server",
|
||||||
|
"--context",
|
||||||
|
"ide-assistant"
|
||||||
|
],
|
||||||
|
"cwd": "$HOME/.claude/serena"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,21 +20,30 @@
|
|||||||
},
|
},
|
||||||
"mcp": {
|
"mcp": {
|
||||||
"name": "mcp",
|
"name": "mcp",
|
||||||
"version": "3.0.0",
|
"version": "4.0.0",
|
||||||
"description": "MCP server integration (Context7, Sequential, Magic, Playwright, Morphllm, Serena)",
|
"description": "MCP server configuration management via .claude.json",
|
||||||
"category": "integration",
|
"category": "integration",
|
||||||
"dependencies": ["core"],
|
"dependencies": ["core"],
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"required_tools": ["node", "claude_cli"]
|
"required_tools": []
|
||||||
},
|
},
|
||||||
"serena": {
|
"modes": {
|
||||||
"name": "serena",
|
"name": "modes",
|
||||||
"version": "3.0.0",
|
"version": "4.0.0",
|
||||||
"description": "Semantic code analysis and intelligent editing with project-aware context management",
|
"description": "SuperClaude behavioral modes (Brainstorming, Introspection, Task Management, Token Efficiency)",
|
||||||
"category": "integration",
|
"category": "modes",
|
||||||
"dependencies": ["core", "mcp"],
|
"dependencies": ["core"],
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"required_tools": ["uvx", "python3", "claude_cli"]
|
"required_tools": []
|
||||||
|
},
|
||||||
|
"mcp_docs": {
|
||||||
|
"name": "mcp_docs",
|
||||||
|
"version": "4.0.0",
|
||||||
|
"description": "MCP server documentation and usage guides",
|
||||||
|
"category": "documentation",
|
||||||
|
"dependencies": ["core"],
|
||||||
|
"enabled": true,
|
||||||
|
"required_tools": []
|
||||||
},
|
},
|
||||||
"agents": {
|
"agents": {
|
||||||
"name": "agents",
|
"name": "agents",
|
||||||
@@ -44,15 +53,6 @@
|
|||||||
"dependencies": ["core"],
|
"dependencies": ["core"],
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"required_tools": []
|
"required_tools": []
|
||||||
},
|
|
||||||
"hooks": {
|
|
||||||
"name": "hooks",
|
|
||||||
"version": "2.0.0",
|
|
||||||
"description": "Enhanced Task Management System - Hook Infrastructure (NOT YET IMPLEMENTED)",
|
|
||||||
"category": "integration",
|
|
||||||
"dependencies": ["core"],
|
|
||||||
"enabled": false,
|
|
||||||
"required_tools": ["python3"]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,9 @@
|
|||||||
"core",
|
"core",
|
||||||
"commands",
|
"commands",
|
||||||
"agents",
|
"agents",
|
||||||
"mcp"
|
"modes",
|
||||||
|
"mcp",
|
||||||
|
"mcp_docs"
|
||||||
],
|
],
|
||||||
"features": {
|
"features": {
|
||||||
"auto_update": false,
|
"auto_update": false,
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
"components": [
|
"components": [
|
||||||
"core",
|
"core",
|
||||||
"commands",
|
"commands",
|
||||||
"agents"
|
"agents",
|
||||||
|
"modes"
|
||||||
],
|
],
|
||||||
"features": {
|
"features": {
|
||||||
"auto_update": false,
|
"auto_update": false,
|
||||||
|
|||||||
@@ -4,12 +4,14 @@ from .core import CoreComponent
|
|||||||
from .commands import CommandsComponent
|
from .commands import CommandsComponent
|
||||||
from .mcp import MCPComponent
|
from .mcp import MCPComponent
|
||||||
from .agents import AgentsComponent
|
from .agents import AgentsComponent
|
||||||
# from .hooks import HooksComponent # Commented out - not yet implemented
|
from .modes import ModesComponent
|
||||||
|
from .mcp_docs import MCPDocsComponent
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'CoreComponent',
|
'CoreComponent',
|
||||||
'CommandsComponent',
|
'CommandsComponent',
|
||||||
'MCPComponent',
|
'MCPComponent',
|
||||||
'AgentsComponent',
|
'AgentsComponent',
|
||||||
# 'HooksComponent' # Commented out - not yet implemented
|
'ModesComponent',
|
||||||
|
'MCPDocsComponent'
|
||||||
]
|
]
|
||||||
@@ -1,366 +0,0 @@
|
|||||||
"""
|
|
||||||
Hooks component for Claude Code hooks integration (future-ready)
|
|
||||||
"""
|
|
||||||
|
|
||||||
from typing import Dict, List, Tuple, Optional, Any
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from ..base.component import Component
|
|
||||||
|
|
||||||
|
|
||||||
class HooksComponent(Component):
|
|
||||||
"""Claude Code hooks integration component"""
|
|
||||||
|
|
||||||
def __init__(self, install_dir: Optional[Path] = None):
|
|
||||||
"""Initialize hooks component"""
|
|
||||||
super().__init__(install_dir, Path("hooks"))
|
|
||||||
|
|
||||||
# Define hook files to install (when hooks are ready)
|
|
||||||
self.hook_files = [
|
|
||||||
"pre_tool_use.py",
|
|
||||||
"post_tool_use.py",
|
|
||||||
"error_handler.py",
|
|
||||||
"context_accumulator.py",
|
|
||||||
"performance_monitor.py"
|
|
||||||
]
|
|
||||||
|
|
||||||
def get_metadata(self) -> Dict[str, str]:
|
|
||||||
"""Get component metadata"""
|
|
||||||
return {
|
|
||||||
"name": "hooks",
|
|
||||||
"version": "3.0.0",
|
|
||||||
"description": "Claude Code hooks integration (NOT YET IMPLEMENTED - future-ready)",
|
|
||||||
"category": "integration"
|
|
||||||
}
|
|
||||||
def get_metadata_modifications(self) -> Dict[str, Any]:
|
|
||||||
# Build hooks configuration based on available files
|
|
||||||
hook_config = {}
|
|
||||||
for filename in self.hook_files:
|
|
||||||
hook_path = self.install_component_subdir / filename
|
|
||||||
if hook_path.exists():
|
|
||||||
hook_name = filename.replace('.py', '')
|
|
||||||
hook_config[hook_name] = [str(hook_path)]
|
|
||||||
|
|
||||||
metadata_mods = {
|
|
||||||
"components": {
|
|
||||||
"hooks": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"installed": True,
|
|
||||||
"files_count": len(hook_config)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Only add hooks configuration if we have actual hook files
|
|
||||||
if hook_config:
|
|
||||||
metadata_mods["hooks"] = {
|
|
||||||
"enabled": True,
|
|
||||||
**hook_config
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return metadata_mods
|
|
||||||
|
|
||||||
def _install(self, config: Dict[str, Any]) -> bool:
|
|
||||||
"""Install hooks component"""
|
|
||||||
self.logger.info("Installing SuperClaude hooks component...")
|
|
||||||
|
|
||||||
# This component is future-ready - hooks aren't implemented yet
|
|
||||||
source_dir = self._get_source_dir()
|
|
||||||
|
|
||||||
if not source_dir.exists() or (source_dir / "PLACEHOLDER.py").exists :
|
|
||||||
self.logger.info("Hooks are not yet implemented - installing placeholder component")
|
|
||||||
|
|
||||||
# Create placeholder hooks directory
|
|
||||||
if not self.file_manager.ensure_directory(self.install_component_subdir):
|
|
||||||
self.logger.error(f"Could not create hooks directory: {self.install_component_subdir}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Create placeholder file
|
|
||||||
placeholder_content = '''"""
|
|
||||||
SuperClaude Hooks - Future Implementation
|
|
||||||
|
|
||||||
This directory is reserved for Claude Code hooks integration.
|
|
||||||
Hooks will provide lifecycle management and automation capabilities.
|
|
||||||
|
|
||||||
Planned hooks:
|
|
||||||
- pre_tool_use: Execute before tool usage
|
|
||||||
- post_tool_use: Execute after tool completion
|
|
||||||
- error_handler: Handle tool errors and recovery
|
|
||||||
- context_accumulator: Manage context across operations
|
|
||||||
- performance_monitor: Track and optimize performance
|
|
||||||
|
|
||||||
For more information, see SuperClaude documentation.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Placeholder for future hooks implementation
|
|
||||||
def placeholder_hook():
|
|
||||||
"""Placeholder hook function"""
|
|
||||||
pass
|
|
||||||
'''
|
|
||||||
|
|
||||||
placeholder_path = self.install_component_subdir / "PLACEHOLDER.py"
|
|
||||||
try:
|
|
||||||
with open(placeholder_path, 'w') as f:
|
|
||||||
f.write(placeholder_content)
|
|
||||||
self.logger.debug("Created hooks placeholder file")
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.warning(f"Could not create placeholder file: {e}")
|
|
||||||
|
|
||||||
# Update settings with placeholder registration
|
|
||||||
try:
|
|
||||||
metadata_mods = {
|
|
||||||
"components": {
|
|
||||||
"hooks": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"installed": True,
|
|
||||||
"status": "placeholder",
|
|
||||||
"files_count": 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.settings_manager.update_metadata(metadata_mods)
|
|
||||||
self.logger.info("Updated metadata with hooks component registration")
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error(f"Failed to update metadata for hooks component: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
self.logger.success("Hooks component installed successfully (placeholder)")
|
|
||||||
return True
|
|
||||||
|
|
||||||
# If hooks source directory exists, install actual hooks
|
|
||||||
self.logger.info("Installing actual hook files...")
|
|
||||||
|
|
||||||
# Validate installation
|
|
||||||
success, errors = self.validate_prerequisites(Path("hooks"))
|
|
||||||
if not success:
|
|
||||||
for error in errors:
|
|
||||||
self.logger.error(error)
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Get files to install
|
|
||||||
files_to_install = self.get_files_to_install()
|
|
||||||
|
|
||||||
if not files_to_install:
|
|
||||||
self.logger.warning("No hook files found to install")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Copy hook files
|
|
||||||
success_count = 0
|
|
||||||
for source, target in files_to_install:
|
|
||||||
self.logger.debug(f"Copying {source.name} to {target}")
|
|
||||||
|
|
||||||
if self.file_manager.copy_file(source, target):
|
|
||||||
success_count += 1
|
|
||||||
self.logger.debug(f"Successfully copied {source.name}")
|
|
||||||
else:
|
|
||||||
self.logger.error(f"Failed to copy {source.name}")
|
|
||||||
|
|
||||||
if success_count != len(files_to_install):
|
|
||||||
self.logger.error(f"Only {success_count}/{len(files_to_install)} hook files copied successfully")
|
|
||||||
return False
|
|
||||||
|
|
||||||
self.logger.success(f"Hooks component installed successfully ({success_count} hook files)")
|
|
||||||
|
|
||||||
return self._post_install()
|
|
||||||
|
|
||||||
def _post_install(self):
|
|
||||||
# Update metadata
|
|
||||||
try:
|
|
||||||
metadata_mods = self.get_metadata_modifications()
|
|
||||||
self.settings_manager.update_metadata(metadata_mods)
|
|
||||||
self.logger.info("Updated metadata with hooks configuration")
|
|
||||||
|
|
||||||
# Add hook registration to metadata
|
|
||||||
self.settings_manager.add_component_registration("hooks", {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"category": "commands",
|
|
||||||
"files_count": len(self.hook_files)
|
|
||||||
})
|
|
||||||
|
|
||||||
self.logger.info("Updated metadata with commands component registration")
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error(f"Failed to update metadata: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def uninstall(self) -> bool:
|
|
||||||
"""Uninstall hooks component"""
|
|
||||||
try:
|
|
||||||
self.logger.info("Uninstalling SuperClaude hooks component...")
|
|
||||||
|
|
||||||
# Remove hook files and placeholder
|
|
||||||
removed_count = 0
|
|
||||||
|
|
||||||
# Remove actual hook files
|
|
||||||
for filename in self.hook_files:
|
|
||||||
file_path = self.install_component_subdir / filename
|
|
||||||
if self.file_manager.remove_file(file_path):
|
|
||||||
removed_count += 1
|
|
||||||
self.logger.debug(f"Removed {filename}")
|
|
||||||
|
|
||||||
# Remove placeholder file
|
|
||||||
placeholder_path = self.install_component_subdir / "PLACEHOLDER.py"
|
|
||||||
if self.file_manager.remove_file(placeholder_path):
|
|
||||||
removed_count += 1
|
|
||||||
self.logger.debug("Removed hooks placeholder")
|
|
||||||
|
|
||||||
# Remove hooks directory if empty
|
|
||||||
try:
|
|
||||||
if self.install_component_subdir.exists():
|
|
||||||
remaining_files = list(self.install_component_subdir.iterdir())
|
|
||||||
if not remaining_files:
|
|
||||||
self.install_component_subdir.rmdir()
|
|
||||||
self.logger.debug("Removed empty hooks directory")
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.warning(f"Could not remove hooks directory: {e}")
|
|
||||||
|
|
||||||
# Update settings.json to remove hooks component and configuration
|
|
||||||
try:
|
|
||||||
if self.settings_manager.is_component_installed("hooks"):
|
|
||||||
self.settings_manager.remove_component_registration("hooks")
|
|
||||||
|
|
||||||
# Also remove hooks configuration section if it exists
|
|
||||||
settings = self.settings_manager.load_settings()
|
|
||||||
if "hooks" in settings:
|
|
||||||
del settings["hooks"]
|
|
||||||
self.settings_manager.save_settings(settings)
|
|
||||||
|
|
||||||
self.logger.info("Removed hooks component and configuration from settings.json")
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.warning(f"Could not update settings.json: {e}")
|
|
||||||
|
|
||||||
self.logger.success(f"Hooks component uninstalled ({removed_count} files removed)")
|
|
||||||
return True
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.exception(f"Unexpected error during hooks uninstallation: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_dependencies(self) -> List[str]:
|
|
||||||
"""Get dependencies"""
|
|
||||||
return ["core"]
|
|
||||||
|
|
||||||
def update(self, config: Dict[str, Any]) -> bool:
|
|
||||||
"""Update hooks component"""
|
|
||||||
try:
|
|
||||||
self.logger.info("Updating SuperClaude hooks component...")
|
|
||||||
|
|
||||||
# Check current version
|
|
||||||
current_version = self.settings_manager.get_component_version("hooks")
|
|
||||||
target_version = self.get_metadata()["version"]
|
|
||||||
|
|
||||||
if current_version == target_version:
|
|
||||||
self.logger.info(f"Hooks component already at version {target_version}")
|
|
||||||
return True
|
|
||||||
|
|
||||||
self.logger.info(f"Updating hooks component from {current_version} to {target_version}")
|
|
||||||
|
|
||||||
# Create backup of existing hook files
|
|
||||||
backup_files = []
|
|
||||||
|
|
||||||
if self.install_component_subdir.exists():
|
|
||||||
for filename in self.hook_files + ["PLACEHOLDER.py"]:
|
|
||||||
file_path = self.install_component_subdir / filename
|
|
||||||
if file_path.exists():
|
|
||||||
backup_path = self.file_manager.backup_file(file_path)
|
|
||||||
if backup_path:
|
|
||||||
backup_files.append(backup_path)
|
|
||||||
self.logger.debug(f"Backed up {filename}")
|
|
||||||
|
|
||||||
# Perform installation (overwrites existing files)
|
|
||||||
success = self.install(config)
|
|
||||||
|
|
||||||
if success:
|
|
||||||
# Remove backup files on successful update
|
|
||||||
for backup_path in backup_files:
|
|
||||||
try:
|
|
||||||
backup_path.unlink()
|
|
||||||
except Exception:
|
|
||||||
pass # Ignore cleanup errors
|
|
||||||
|
|
||||||
self.logger.success(f"Hooks component updated to version {target_version}")
|
|
||||||
else:
|
|
||||||
# Restore from backup on failure
|
|
||||||
self.logger.warning("Update failed, restoring from backup...")
|
|
||||||
for backup_path in backup_files:
|
|
||||||
try:
|
|
||||||
original_path = backup_path.with_suffix('')
|
|
||||||
backup_path.rename(original_path)
|
|
||||||
self.logger.debug(f"Restored {original_path.name}")
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error(f"Could not restore {backup_path}: {e}")
|
|
||||||
|
|
||||||
return success
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.exception(f"Unexpected error during hooks update: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def validate_installation(self) -> Tuple[bool, List[str]]:
|
|
||||||
"""Validate hooks component installation"""
|
|
||||||
errors = []
|
|
||||||
|
|
||||||
# Check if hooks directory exists
|
|
||||||
if not self.install_component_subdir.exists():
|
|
||||||
errors.append("Hooks directory not found")
|
|
||||||
return False, errors
|
|
||||||
|
|
||||||
# Check settings.json registration
|
|
||||||
if not self.settings_manager.is_component_installed("hooks"):
|
|
||||||
errors.append("Hooks component not registered in settings.json")
|
|
||||||
else:
|
|
||||||
# Check version matches
|
|
||||||
installed_version = self.settings_manager.get_component_version("hooks")
|
|
||||||
expected_version = self.get_metadata()["version"]
|
|
||||||
if installed_version != expected_version:
|
|
||||||
errors.append(f"Version mismatch: installed {installed_version}, expected {expected_version}")
|
|
||||||
|
|
||||||
# Check if we have either actual hooks or placeholder
|
|
||||||
has_placeholder = (self.install_component_subdir / "PLACEHOLDER.py").exists()
|
|
||||||
has_actual_hooks = any((self.install_component_subdir / filename).exists() for filename in self.hook_files)
|
|
||||||
|
|
||||||
if not has_placeholder and not has_actual_hooks:
|
|
||||||
errors.append("No hook files or placeholder found")
|
|
||||||
|
|
||||||
return len(errors) == 0, errors
|
|
||||||
|
|
||||||
def _get_source_dir(self) -> Path:
|
|
||||||
"""Get source directory for hook files"""
|
|
||||||
# Assume we're in SuperClaude/setup/components/hooks.py
|
|
||||||
# and hook files are in SuperClaude/SuperClaude/Hooks/
|
|
||||||
project_root = Path(__file__).parent.parent.parent
|
|
||||||
return project_root / "SuperClaude" / "Hooks"
|
|
||||||
|
|
||||||
def get_size_estimate(self) -> int:
|
|
||||||
"""Get estimated installation size"""
|
|
||||||
# Estimate based on placeholder or actual files
|
|
||||||
source_dir = self._get_source_dir()
|
|
||||||
total_size = 0
|
|
||||||
|
|
||||||
if source_dir.exists():
|
|
||||||
for filename in self.hook_files:
|
|
||||||
file_path = source_dir / filename
|
|
||||||
if file_path.exists():
|
|
||||||
total_size += file_path.stat().st_size
|
|
||||||
|
|
||||||
# Add placeholder overhead or minimum size
|
|
||||||
total_size = max(total_size, 10240) # At least 10KB
|
|
||||||
|
|
||||||
return total_size
|
|
||||||
|
|
||||||
def get_installation_summary(self) -> Dict[str, Any]:
|
|
||||||
"""Get installation summary"""
|
|
||||||
source_dir = self._get_source_dir()
|
|
||||||
status = "placeholder" if not source_dir.exists() else "implemented"
|
|
||||||
|
|
||||||
return {
|
|
||||||
"component": self.get_metadata()["name"],
|
|
||||||
"version": self.get_metadata()["version"],
|
|
||||||
"status": status,
|
|
||||||
"hook_files": self.hook_files if source_dir.exists() else ["PLACEHOLDER.py"],
|
|
||||||
"estimated_size": self.get_size_estimate(),
|
|
||||||
"install_directory": str(self.install_dir / "hooks"),
|
|
||||||
"dependencies": self.get_dependencies()
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
"""
|
"""
|
||||||
MCP component for MCP server integration
|
MCP component for MCP server configuration via .claude.json
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import subprocess
|
import json
|
||||||
import sys
|
import shutil
|
||||||
from typing import Dict, List, Tuple, Optional, Any
|
from typing import Dict, List, Tuple, Optional, Any
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -12,360 +12,289 @@ from ..utils.ui import display_info, display_warning
|
|||||||
|
|
||||||
|
|
||||||
class MCPComponent(Component):
|
class MCPComponent(Component):
|
||||||
"""MCP servers integration component"""
|
"""MCP servers configuration component"""
|
||||||
|
|
||||||
def __init__(self, install_dir: Optional[Path] = None):
|
def __init__(self, install_dir: Optional[Path] = None):
|
||||||
"""Initialize MCP component"""
|
"""Initialize MCP component"""
|
||||||
super().__init__(install_dir)
|
super().__init__(install_dir)
|
||||||
|
|
||||||
# Define MCP servers to install
|
# Define MCP servers available for configuration
|
||||||
self.mcp_servers = {
|
self.mcp_servers = {
|
||||||
"sequential-thinking": {
|
|
||||||
"name": "sequential-thinking",
|
|
||||||
"description": "Multi-step problem solving and systematic analysis",
|
|
||||||
"npm_package": "@modelcontextprotocol/server-sequential-thinking",
|
|
||||||
"required": True
|
|
||||||
},
|
|
||||||
"context7": {
|
"context7": {
|
||||||
"name": "context7",
|
"name": "context7",
|
||||||
"description": "Official library documentation and code examples",
|
"description": "Official library documentation and code examples",
|
||||||
"npm_package": "@upstash/context7-mcp",
|
"config_file": "context7.json",
|
||||||
"required": True
|
"requires_api_key": False
|
||||||
|
},
|
||||||
|
"sequential": {
|
||||||
|
"name": "sequential-thinking",
|
||||||
|
"description": "Multi-step problem solving and systematic analysis",
|
||||||
|
"config_file": "sequential.json",
|
||||||
|
"requires_api_key": False
|
||||||
},
|
},
|
||||||
"magic": {
|
"magic": {
|
||||||
"name": "magic",
|
"name": "magic",
|
||||||
"description": "Modern UI component generation and design systems",
|
"description": "Modern UI component generation and design systems",
|
||||||
"npm_package": "@21st-dev/magic",
|
"config_file": "magic.json",
|
||||||
"required": False,
|
"requires_api_key": True,
|
||||||
"api_key_env": "TWENTYFIRST_API_KEY",
|
"api_key_env": "TWENTYFIRST_API_KEY"
|
||||||
"api_key_description": "21st.dev API key for UI component generation"
|
|
||||||
},
|
},
|
||||||
"playwright": {
|
"playwright": {
|
||||||
"name": "playwright",
|
"name": "playwright",
|
||||||
"description": "Cross-browser E2E testing and automation",
|
"description": "Cross-browser E2E testing and automation",
|
||||||
"npm_package": "@playwright/mcp@latest",
|
"config_file": "playwright.json",
|
||||||
"required": False
|
"requires_api_key": False
|
||||||
|
},
|
||||||
|
"serena": {
|
||||||
|
"name": "serena",
|
||||||
|
"description": "Semantic code analysis and intelligent editing",
|
||||||
|
"config_file": "serena.json",
|
||||||
|
"requires_api_key": False
|
||||||
|
},
|
||||||
|
"morphllm": {
|
||||||
|
"name": "morphllm-fast-apply",
|
||||||
|
"description": "Fast Apply capability for context-aware code modifications",
|
||||||
|
"config_file": "morphllm.json",
|
||||||
|
"requires_api_key": True,
|
||||||
|
"api_key_env": "MORPH_API_KEY"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# This will be set during installation
|
||||||
|
self.selected_servers = []
|
||||||
|
|
||||||
def get_metadata(self) -> Dict[str, str]:
|
def get_metadata(self) -> Dict[str, str]:
|
||||||
"""Get component metadata"""
|
"""Get component metadata"""
|
||||||
return {
|
return {
|
||||||
"name": "mcp",
|
"name": "mcp",
|
||||||
"version": "3.0.0",
|
"version": "4.0.0",
|
||||||
"description": "MCP server integration (Context7, Sequential, Magic, Playwright)",
|
"description": "MCP server configuration management via .claude.json",
|
||||||
"category": "integration"
|
"category": "integration"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def set_selected_servers(self, selected_servers: List[str]) -> None:
|
||||||
|
"""Set which MCP servers were selected for configuration"""
|
||||||
|
self.selected_servers = selected_servers
|
||||||
|
self.logger.debug(f"MCP servers to configure: {selected_servers}")
|
||||||
|
|
||||||
def validate_prerequisites(self, installSubPath: Optional[Path] = None) -> Tuple[bool, List[str]]:
|
def validate_prerequisites(self, installSubPath: Optional[Path] = None) -> Tuple[bool, List[str]]:
|
||||||
"""Check prerequisites"""
|
"""
|
||||||
|
Check prerequisites for MCP component
|
||||||
|
"""
|
||||||
errors = []
|
errors = []
|
||||||
|
|
||||||
# Check if Node.js is available
|
# Check if config source directory exists
|
||||||
try:
|
source_dir = self._get_config_source_dir()
|
||||||
result = subprocess.run(
|
if not source_dir or not source_dir.exists():
|
||||||
["node", "--version"],
|
errors.append(f"MCP config source directory not found: {source_dir}")
|
||||||
capture_output=True,
|
return False, errors
|
||||||
text=True,
|
|
||||||
timeout=10,
|
|
||||||
shell=(sys.platform == "win32")
|
|
||||||
)
|
|
||||||
if result.returncode != 0:
|
|
||||||
errors.append("Node.js not found - required for MCP servers")
|
|
||||||
else:
|
|
||||||
version = result.stdout.strip()
|
|
||||||
self.logger.debug(f"Found Node.js {version}")
|
|
||||||
|
|
||||||
# Check version (require 18+)
|
|
||||||
try:
|
|
||||||
version_num = int(version.lstrip('v').split('.')[0])
|
|
||||||
if version_num < 18:
|
|
||||||
errors.append(f"Node.js version {version} found, but version 18+ required")
|
|
||||||
except:
|
|
||||||
self.logger.warning(f"Could not parse Node.js version: {version}")
|
|
||||||
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
||||||
errors.append("Node.js not found - required for MCP servers")
|
|
||||||
|
|
||||||
# Check if Claude CLI is available
|
# Check if user's Claude config exists
|
||||||
try:
|
claude_config = Path.home() / ".claude.json"
|
||||||
result = subprocess.run(
|
if not claude_config.exists():
|
||||||
["claude", "--version"],
|
errors.append(f"Claude configuration file not found: {claude_config}")
|
||||||
capture_output=True,
|
errors.append("Please run Claude Code at least once to create the configuration file")
|
||||||
text=True,
|
|
||||||
timeout=10,
|
|
||||||
shell=(sys.platform == "win32")
|
|
||||||
)
|
|
||||||
if result.returncode != 0:
|
|
||||||
errors.append("Claude CLI not found - required for MCP server management")
|
|
||||||
else:
|
|
||||||
version = result.stdout.strip()
|
|
||||||
self.logger.debug(f"Found Claude CLI {version}")
|
|
||||||
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
||||||
errors.append("Claude CLI not found - required for MCP server management")
|
|
||||||
|
|
||||||
# Check if npm is available
|
|
||||||
try:
|
|
||||||
result = subprocess.run(
|
|
||||||
["npm", "--version"],
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
timeout=10,
|
|
||||||
shell=(sys.platform == "win32")
|
|
||||||
)
|
|
||||||
if result.returncode != 0:
|
|
||||||
errors.append("npm not found - required for MCP server installation")
|
|
||||||
else:
|
|
||||||
version = result.stdout.strip()
|
|
||||||
self.logger.debug(f"Found npm {version}")
|
|
||||||
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
||||||
errors.append("npm not found - required for MCP server installation")
|
|
||||||
|
|
||||||
return len(errors) == 0, errors
|
return len(errors) == 0, errors
|
||||||
|
|
||||||
def get_files_to_install(self) -> List[Tuple[Path, Path]]:
|
def get_files_to_install(self) -> List[Tuple[Path, Path]]:
|
||||||
"""Get files to install (none for MCP component)"""
|
"""MCP component doesn't install files - it modifies .claude.json"""
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def get_metadata_modifications(self) -> Dict[str, Any]:
|
def _get_config_source_dir(self) -> Optional[Path]:
|
||||||
"""Get metadata modifications for MCP component"""
|
"""Get source directory for MCP config files"""
|
||||||
return {
|
project_root = Path(__file__).parent.parent.parent
|
||||||
"components": {
|
config_dir = project_root / "SuperClaude" / "MCP" / "configs"
|
||||||
"mcp": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"installed": True,
|
|
||||||
"servers_count": len(self.mcp_servers)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"mcp": {
|
|
||||||
"enabled": True,
|
|
||||||
"servers": list(self.mcp_servers.keys()),
|
|
||||||
"auto_update": False
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def _check_mcp_server_installed(self, server_name: str) -> bool:
|
|
||||||
"""Check if MCP server is already installed"""
|
|
||||||
try:
|
|
||||||
result = subprocess.run(
|
|
||||||
["claude", "mcp", "list"],
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
timeout=15,
|
|
||||||
shell=(sys.platform == "win32")
|
|
||||||
)
|
|
||||||
|
|
||||||
if result.returncode != 0:
|
|
||||||
self.logger.warning(f"Could not list MCP servers: {result.stderr}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Parse output to check if server is installed
|
|
||||||
output = result.stdout.lower()
|
|
||||||
return server_name.lower() in output
|
|
||||||
|
|
||||||
except (subprocess.TimeoutExpired, subprocess.SubprocessError) as e:
|
|
||||||
self.logger.warning(f"Error checking MCP server status: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _install_mcp_server(self, server_info: Dict[str, Any], config: Dict[str, Any]) -> bool:
|
|
||||||
"""Install a single MCP server"""
|
|
||||||
server_name = server_info["name"]
|
|
||||||
npm_package = server_info["npm_package"]
|
|
||||||
|
|
||||||
command = "npx"
|
if not config_dir.exists():
|
||||||
|
return None
|
||||||
|
|
||||||
|
return config_dir
|
||||||
|
|
||||||
|
def _get_source_dir(self) -> Optional[Path]:
|
||||||
|
"""Override parent method - MCP component doesn't use traditional file installation"""
|
||||||
|
return self._get_config_source_dir()
|
||||||
|
|
||||||
|
def _load_claude_config(self) -> Tuple[Optional[Dict], Path]:
|
||||||
|
"""Load user's Claude configuration"""
|
||||||
|
claude_config_path = Path.home() / ".claude.json"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.logger.info(f"Installing MCP server: {server_name}")
|
with open(claude_config_path, 'r') as f:
|
||||||
|
config = json.load(f)
|
||||||
# Check if already installed
|
return config, claude_config_path
|
||||||
if self._check_mcp_server_installed(server_name):
|
|
||||||
self.logger.info(f"MCP server {server_name} already installed")
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Handle API key requirements
|
|
||||||
if "api_key_env" in server_info:
|
|
||||||
api_key_env = server_info["api_key_env"]
|
|
||||||
api_key_desc = server_info.get("api_key_description", f"API key for {server_name}")
|
|
||||||
|
|
||||||
if not config.get("dry_run", False):
|
|
||||||
display_info(f"MCP server '{server_name}' requires an API key")
|
|
||||||
display_info(f"Environment variable: {api_key_env}")
|
|
||||||
display_info(f"Description: {api_key_desc}")
|
|
||||||
|
|
||||||
# Check if API key is already set
|
|
||||||
import os
|
|
||||||
if not os.getenv(api_key_env):
|
|
||||||
display_warning(f"API key {api_key_env} not found in environment")
|
|
||||||
self.logger.warning(f"Proceeding without {api_key_env} - server may not function properly")
|
|
||||||
|
|
||||||
# Install using Claude CLI
|
|
||||||
if config.get("dry_run"):
|
|
||||||
self.logger.info(f"Would install MCP server (user scope): claude mcp add -s user {server_name} {command} -y {npm_package}")
|
|
||||||
return True
|
|
||||||
|
|
||||||
self.logger.debug(f"Running: claude mcp add -s user {server_name} {command} -y {npm_package}")
|
|
||||||
|
|
||||||
result = subprocess.run(
|
|
||||||
["claude", "mcp", "add", "-s", "user", "--", server_name, command, "-y", npm_package],
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
timeout=120, # 2 minutes timeout for installation
|
|
||||||
shell=(sys.platform == "win32")
|
|
||||||
)
|
|
||||||
|
|
||||||
if result.returncode == 0:
|
|
||||||
self.logger.success(f"Successfully installed MCP server (user scope): {server_name}")
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
error_msg = result.stderr.strip() if result.stderr else "Unknown error"
|
|
||||||
self.logger.error(f"Failed to install MCP server {server_name}: {error_msg}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
except subprocess.TimeoutExpired:
|
|
||||||
self.logger.error(f"Timeout installing MCP server {server_name}")
|
|
||||||
return False
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Error installing MCP server {server_name}: {e}")
|
self.logger.error(f"Failed to load Claude config: {e}")
|
||||||
|
return None, claude_config_path
|
||||||
|
|
||||||
|
def _save_claude_config(self, config: Dict, config_path: Path) -> bool:
|
||||||
|
"""Save user's Claude configuration with backup"""
|
||||||
|
try:
|
||||||
|
# Create backup
|
||||||
|
backup_path = config_path.with_suffix('.json.backup')
|
||||||
|
shutil.copy2(config_path, backup_path)
|
||||||
|
self.logger.debug(f"Created backup: {backup_path}")
|
||||||
|
|
||||||
|
# Save updated config
|
||||||
|
with open(config_path, 'w') as f:
|
||||||
|
json.dump(config, f, indent=2)
|
||||||
|
|
||||||
|
self.logger.debug("Updated Claude configuration")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Failed to save Claude config: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _uninstall_mcp_server(self, server_name: str) -> bool:
|
def _load_mcp_server_config(self, server_key: str) -> Optional[Dict]:
|
||||||
"""Uninstall a single MCP server"""
|
"""Load MCP server configuration snippet"""
|
||||||
|
if server_key not in self.mcp_servers:
|
||||||
|
return None
|
||||||
|
|
||||||
|
server_info = self.mcp_servers[server_key]
|
||||||
|
config_file = server_info["config_file"]
|
||||||
|
config_source_dir = self._get_config_source_dir()
|
||||||
|
|
||||||
|
if not config_source_dir:
|
||||||
|
return None
|
||||||
|
|
||||||
|
config_path = config_source_dir / config_file
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.logger.info(f"Uninstalling MCP server: {server_name}")
|
with open(config_path, 'r') as f:
|
||||||
|
return json.load(f)
|
||||||
# Check if installed
|
|
||||||
if not self._check_mcp_server_installed(server_name):
|
|
||||||
self.logger.info(f"MCP server {server_name} not installed")
|
|
||||||
return True
|
|
||||||
|
|
||||||
self.logger.debug(f"Running: claude mcp remove {server_name} (auto-detect scope)")
|
|
||||||
|
|
||||||
result = subprocess.run(
|
|
||||||
["claude", "mcp", "remove", server_name],
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
timeout=60,
|
|
||||||
shell=(sys.platform == "win32")
|
|
||||||
)
|
|
||||||
|
|
||||||
if result.returncode == 0:
|
|
||||||
self.logger.success(f"Successfully uninstalled MCP server: {server_name}")
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
error_msg = result.stderr.strip() if result.stderr else "Unknown error"
|
|
||||||
self.logger.error(f"Failed to uninstall MCP server {server_name}: {error_msg}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
except subprocess.TimeoutExpired:
|
|
||||||
self.logger.error(f"Timeout uninstalling MCP server {server_name}")
|
|
||||||
return False
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Error uninstalling MCP server {server_name}: {e}")
|
self.logger.error(f"Failed to load MCP config for {server_key}: {e}")
|
||||||
return False
|
return None
|
||||||
|
|
||||||
def _install(self, config: Dict[str, Any]) -> bool:
|
def _install(self, config: Dict[str, Any]) -> bool:
|
||||||
"""Install MCP component"""
|
"""Install MCP component by configuring .claude.json"""
|
||||||
self.logger.info("Installing SuperClaude MCP servers...")
|
self.logger.info("Configuring MCP servers in Claude...")
|
||||||
|
|
||||||
|
# Get selected servers from config
|
||||||
|
selected_servers = config.get("selected_mcp_servers", [])
|
||||||
|
if not selected_servers:
|
||||||
|
self.logger.info("No MCP servers selected - skipping MCP configuration")
|
||||||
|
return True
|
||||||
|
|
||||||
|
self.set_selected_servers(selected_servers)
|
||||||
|
|
||||||
# Validate prerequisites
|
# Validate prerequisites
|
||||||
success, errors = self.validate_prerequisites()
|
success, errors = self.validate_prerequisites()
|
||||||
if not success:
|
if not success:
|
||||||
for error in errors:
|
for error in errors:
|
||||||
self.logger.error(error)
|
self.logger.error(error)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Install each MCP server
|
# Load Claude configuration
|
||||||
installed_count = 0
|
claude_config, config_path = self._load_claude_config()
|
||||||
failed_servers = []
|
if claude_config is None:
|
||||||
|
return False
|
||||||
for server_name, server_info in self.mcp_servers.items():
|
|
||||||
if self._install_mcp_server(server_info, config):
|
# Ensure mcpServers section exists
|
||||||
installed_count += 1
|
if "mcpServers" not in claude_config:
|
||||||
else:
|
claude_config["mcpServers"] = {}
|
||||||
failed_servers.append(server_name)
|
|
||||||
|
# Configure each selected server
|
||||||
# Check if this is a required server
|
configured_count = 0
|
||||||
if server_info.get("required", False):
|
for server_key in selected_servers:
|
||||||
self.logger.error(f"Required MCP server {server_name} failed to install")
|
if server_key not in self.mcp_servers:
|
||||||
return False
|
self.logger.warning(f"Unknown MCP server: {server_key}")
|
||||||
|
continue
|
||||||
# Verify installation
|
|
||||||
if not config.get("dry_run", False):
|
server_info = self.mcp_servers[server_key]
|
||||||
self.logger.info("Verifying MCP server installation...")
|
server_config = self._load_mcp_server_config(server_key)
|
||||||
try:
|
|
||||||
result = subprocess.run(
|
if server_config is None:
|
||||||
["claude", "mcp", "list"],
|
self.logger.error(f"Failed to load configuration for {server_key}")
|
||||||
capture_output=True,
|
continue
|
||||||
text=True,
|
|
||||||
timeout=15,
|
# Handle API key requirements
|
||||||
shell=(sys.platform == "win32")
|
if server_info.get("requires_api_key", False):
|
||||||
)
|
api_key_env = server_info.get("api_key_env")
|
||||||
|
if api_key_env:
|
||||||
if result.returncode == 0:
|
display_info(f"Server '{server_key}' requires API key: {api_key_env}")
|
||||||
self.logger.debug("MCP servers list:")
|
display_info("You can set this environment variable later")
|
||||||
for line in result.stdout.strip().split('\n'):
|
|
||||||
if line.strip():
|
# Merge server config into Claude config
|
||||||
self.logger.debug(f" {line.strip()}")
|
claude_config["mcpServers"].update(server_config)
|
||||||
else:
|
configured_count += 1
|
||||||
self.logger.warning("Could not verify MCP server installation")
|
|
||||||
|
self.logger.info(f"Configured MCP server: {server_info['name']}")
|
||||||
except Exception as e:
|
|
||||||
self.logger.warning(f"Could not verify MCP installation: {e}")
|
if configured_count == 0:
|
||||||
|
self.logger.error("No MCP servers were successfully configured")
|
||||||
if failed_servers:
|
return False
|
||||||
self.logger.warning(f"Some MCP servers failed to install: {failed_servers}")
|
|
||||||
self.logger.success(f"MCP component partially installed ({installed_count} servers)")
|
# Save updated configuration
|
||||||
|
success = self._save_claude_config(claude_config, config_path)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
self.logger.success(f"Successfully configured {configured_count} MCP servers")
|
||||||
|
return self._post_install()
|
||||||
else:
|
else:
|
||||||
self.logger.success(f"MCP component installed successfully ({installed_count} servers)")
|
return False
|
||||||
|
|
||||||
return self._post_install()
|
|
||||||
|
|
||||||
def _post_install(self) -> bool:
|
def _post_install(self) -> bool:
|
||||||
# Update metadata
|
"""Post-installation tasks"""
|
||||||
try:
|
try:
|
||||||
metadata_mods = self.get_metadata_modifications()
|
# Update metadata
|
||||||
|
metadata_mods = {
|
||||||
|
"components": {
|
||||||
|
"mcp": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"installed": True,
|
||||||
|
"servers_configured": len(self.selected_servers),
|
||||||
|
"configured_servers": self.selected_servers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
self.settings_manager.update_metadata(metadata_mods)
|
self.settings_manager.update_metadata(metadata_mods)
|
||||||
|
|
||||||
# Add component registration to metadata
|
|
||||||
self.settings_manager.add_component_registration("mcp", {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"category": "integration",
|
|
||||||
"servers_count": len(self.mcp_servers)
|
|
||||||
})
|
|
||||||
|
|
||||||
self.logger.info("Updated metadata with MCP component registration")
|
self.logger.info("Updated metadata with MCP component registration")
|
||||||
|
|
||||||
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Failed to update metadata: {e}")
|
self.logger.error(f"Failed to update metadata: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def uninstall(self) -> bool:
|
def uninstall(self) -> bool:
|
||||||
"""Uninstall MCP component"""
|
"""Uninstall MCP component by removing servers from .claude.json"""
|
||||||
try:
|
try:
|
||||||
self.logger.info("Uninstalling SuperClaude MCP servers...")
|
self.logger.info("Removing MCP server configurations...")
|
||||||
|
|
||||||
# Uninstall each MCP server
|
# Load Claude configuration
|
||||||
uninstalled_count = 0
|
claude_config, config_path = self._load_claude_config()
|
||||||
|
if claude_config is None:
|
||||||
|
self.logger.warning("Could not load Claude config for cleanup")
|
||||||
|
return True # Not a failure if config doesn't exist
|
||||||
|
|
||||||
for server_name in self.mcp_servers.keys():
|
if "mcpServers" not in claude_config:
|
||||||
if self._uninstall_mcp_server(server_name):
|
self.logger.info("No MCP servers configured")
|
||||||
uninstalled_count += 1
|
return True
|
||||||
|
|
||||||
# Update metadata to remove MCP component
|
# Remove all servers we know about
|
||||||
|
removed_count = 0
|
||||||
|
for server_info in self.mcp_servers.values():
|
||||||
|
server_name = server_info["name"]
|
||||||
|
if server_name in claude_config["mcpServers"]:
|
||||||
|
del claude_config["mcpServers"][server_name]
|
||||||
|
removed_count += 1
|
||||||
|
self.logger.debug(f"Removed MCP server: {server_name}")
|
||||||
|
|
||||||
|
# Save updated configuration
|
||||||
|
if removed_count > 0:
|
||||||
|
success = self._save_claude_config(claude_config, config_path)
|
||||||
|
if not success:
|
||||||
|
self.logger.warning("Failed to save updated Claude configuration")
|
||||||
|
|
||||||
|
# Update settings.json
|
||||||
try:
|
try:
|
||||||
if self.settings_manager.is_component_installed("mcp"):
|
if self.settings_manager.is_component_installed("mcp"):
|
||||||
self.settings_manager.remove_component_registration("mcp")
|
self.settings_manager.remove_component_registration("mcp")
|
||||||
# Also remove MCP configuration from metadata
|
self.logger.info("Removed MCP component from settings.json")
|
||||||
metadata = self.settings_manager.load_metadata()
|
|
||||||
if "mcp" in metadata:
|
|
||||||
del metadata["mcp"]
|
|
||||||
self.settings_manager.save_metadata(metadata)
|
|
||||||
self.logger.info("Removed MCP component from metadata")
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.warning(f"Could not update metadata: {e}")
|
self.logger.warning(f"Could not update settings.json: {e}")
|
||||||
|
|
||||||
self.logger.success(f"MCP component uninstalled ({uninstalled_count} servers removed)")
|
self.logger.success(f"MCP component uninstalled ({removed_count} servers removed)")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -376,123 +305,6 @@ class MCPComponent(Component):
|
|||||||
"""Get dependencies"""
|
"""Get dependencies"""
|
||||||
return ["core"]
|
return ["core"]
|
||||||
|
|
||||||
def update(self, config: Dict[str, Any]) -> bool:
|
|
||||||
"""Update MCP component"""
|
|
||||||
try:
|
|
||||||
self.logger.info("Updating SuperClaude MCP servers...")
|
|
||||||
|
|
||||||
# Check current version
|
|
||||||
current_version = self.settings_manager.get_component_version("mcp")
|
|
||||||
target_version = self.get_metadata()["version"]
|
|
||||||
|
|
||||||
if current_version == target_version:
|
|
||||||
self.logger.info(f"MCP component already at version {target_version}")
|
|
||||||
return True
|
|
||||||
|
|
||||||
self.logger.info(f"Updating MCP component from {current_version} to {target_version}")
|
|
||||||
|
|
||||||
# For MCP servers, update means reinstall to get latest versions
|
|
||||||
updated_count = 0
|
|
||||||
failed_servers = []
|
|
||||||
|
|
||||||
for server_name, server_info in self.mcp_servers.items():
|
|
||||||
try:
|
|
||||||
# Uninstall old version
|
|
||||||
if self._check_mcp_server_installed(server_name):
|
|
||||||
self._uninstall_mcp_server(server_name)
|
|
||||||
|
|
||||||
# Install new version
|
|
||||||
if self._install_mcp_server(server_info, config):
|
|
||||||
updated_count += 1
|
|
||||||
else:
|
|
||||||
failed_servers.append(server_name)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error(f"Error updating MCP server {server_name}: {e}")
|
|
||||||
failed_servers.append(server_name)
|
|
||||||
|
|
||||||
# Update metadata
|
|
||||||
try:
|
|
||||||
# Update component version in metadata
|
|
||||||
metadata = self.settings_manager.load_metadata()
|
|
||||||
if "components" in metadata and "mcp" in metadata["components"]:
|
|
||||||
metadata["components"]["mcp"]["version"] = target_version
|
|
||||||
metadata["components"]["mcp"]["servers_count"] = len(self.mcp_servers)
|
|
||||||
if "mcp" in metadata:
|
|
||||||
metadata["mcp"]["servers"] = list(self.mcp_servers.keys())
|
|
||||||
self.settings_manager.save_metadata(metadata)
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.warning(f"Could not update metadata: {e}")
|
|
||||||
|
|
||||||
if failed_servers:
|
|
||||||
self.logger.warning(f"Some MCP servers failed to update: {failed_servers}")
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
self.logger.success(f"MCP component updated to version {target_version}")
|
|
||||||
return True
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.exception(f"Unexpected error during MCP update: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def validate_installation(self) -> Tuple[bool, List[str]]:
|
|
||||||
"""Validate MCP component installation"""
|
|
||||||
errors = []
|
|
||||||
|
|
||||||
# Check metadata registration
|
|
||||||
if not self.settings_manager.is_component_installed("mcp"):
|
|
||||||
errors.append("MCP component not registered in metadata")
|
|
||||||
return False, errors
|
|
||||||
|
|
||||||
# Check version matches
|
|
||||||
installed_version = self.settings_manager.get_component_version("mcp")
|
|
||||||
expected_version = self.get_metadata()["version"]
|
|
||||||
if installed_version != expected_version:
|
|
||||||
errors.append(f"Version mismatch: installed {installed_version}, expected {expected_version}")
|
|
||||||
|
|
||||||
# Check if Claude CLI is available
|
|
||||||
try:
|
|
||||||
result = subprocess.run(
|
|
||||||
["claude", "mcp", "list"],
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
timeout=15,
|
|
||||||
shell=(sys.platform == "win32")
|
|
||||||
)
|
|
||||||
|
|
||||||
if result.returncode != 0:
|
|
||||||
errors.append("Could not communicate with Claude CLI for MCP server verification")
|
|
||||||
else:
|
|
||||||
# Check if required servers are installed
|
|
||||||
output = result.stdout.lower()
|
|
||||||
for server_name, server_info in self.mcp_servers.items():
|
|
||||||
if server_info.get("required", False):
|
|
||||||
if server_name.lower() not in output:
|
|
||||||
errors.append(f"Required MCP server not found: {server_name}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
errors.append(f"Could not verify MCP server installation: {e}")
|
|
||||||
|
|
||||||
return len(errors) == 0, errors
|
|
||||||
|
|
||||||
def _get_source_dir(self):
|
|
||||||
"""Get source directory for framework files"""
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_size_estimate(self) -> int:
|
def get_size_estimate(self) -> int:
|
||||||
"""Get estimated installation size"""
|
"""Get estimated size - minimal since we only modify config"""
|
||||||
# MCP servers are installed via npm, estimate based on typical sizes
|
return 4096 # 4KB - just config modifications
|
||||||
base_size = 50 * 1024 * 1024 # ~50MB for all servers combined
|
|
||||||
return base_size
|
|
||||||
|
|
||||||
def get_installation_summary(self) -> Dict[str, Any]:
|
|
||||||
"""Get installation summary"""
|
|
||||||
return {
|
|
||||||
"component": self.get_metadata()["name"],
|
|
||||||
"version": self.get_metadata()["version"],
|
|
||||||
"servers_count": len(self.mcp_servers),
|
|
||||||
"mcp_servers": list(self.mcp_servers.keys()),
|
|
||||||
"estimated_size": self.get_size_estimate(),
|
|
||||||
"dependencies": self.get_dependencies(),
|
|
||||||
"required_tools": ["node", "npm", "claude"]
|
|
||||||
}
|
|
||||||
225
setup/components/mcp_docs.py
Normal file
225
setup/components/mcp_docs.py
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
"""
|
||||||
|
MCP Documentation component for SuperClaude MCP server documentation
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Dict, List, Tuple, Optional, Any
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from ..base.component import Component
|
||||||
|
|
||||||
|
|
||||||
|
class MCPDocsComponent(Component):
|
||||||
|
"""MCP documentation component - installs docs for selected MCP servers"""
|
||||||
|
|
||||||
|
def __init__(self, install_dir: Optional[Path] = None):
|
||||||
|
"""Initialize MCP docs component"""
|
||||||
|
super().__init__(install_dir, Path("mcp"))
|
||||||
|
|
||||||
|
# Map server names to documentation files
|
||||||
|
self.server_docs_map = {
|
||||||
|
"context7": "MCP_Context7.md",
|
||||||
|
"sequential": "MCP_Sequential.md",
|
||||||
|
"magic": "MCP_Magic.md",
|
||||||
|
"playwright": "MCP_Playwright.md",
|
||||||
|
"serena": "MCP_Serena.md",
|
||||||
|
"morphllm": "MCP_Morphllm.md"
|
||||||
|
}
|
||||||
|
|
||||||
|
# This will be set during installation
|
||||||
|
self.selected_servers = []
|
||||||
|
|
||||||
|
def get_metadata(self) -> Dict[str, str]:
|
||||||
|
"""Get component metadata"""
|
||||||
|
return {
|
||||||
|
"name": "mcp_docs",
|
||||||
|
"version": "4.0.0",
|
||||||
|
"description": "MCP server documentation and usage guides",
|
||||||
|
"category": "documentation"
|
||||||
|
}
|
||||||
|
|
||||||
|
def set_selected_servers(self, selected_servers: List[str]) -> None:
|
||||||
|
"""Set which MCP servers were selected for documentation installation"""
|
||||||
|
self.selected_servers = selected_servers
|
||||||
|
self.logger.debug(f"MCP docs will be installed for: {selected_servers}")
|
||||||
|
|
||||||
|
def get_files_to_install(self) -> List[Tuple[Path, Path]]:
|
||||||
|
"""
|
||||||
|
Return list of files to install based on selected MCP servers
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of tuples (source_path, target_path)
|
||||||
|
"""
|
||||||
|
source_dir = self._get_source_dir()
|
||||||
|
files = []
|
||||||
|
|
||||||
|
if source_dir and self.selected_servers:
|
||||||
|
for server_name in self.selected_servers:
|
||||||
|
if server_name in self.server_docs_map:
|
||||||
|
doc_file = self.server_docs_map[server_name]
|
||||||
|
source = source_dir / doc_file
|
||||||
|
target = self.install_component_subdir / doc_file
|
||||||
|
if source.exists():
|
||||||
|
files.append((source, target))
|
||||||
|
self.logger.debug(f"Will install documentation for {server_name}: {doc_file}")
|
||||||
|
else:
|
||||||
|
self.logger.warning(f"Documentation file not found for {server_name}: {doc_file}")
|
||||||
|
|
||||||
|
return files
|
||||||
|
|
||||||
|
def _discover_component_files(self) -> List[str]:
|
||||||
|
"""
|
||||||
|
Override parent method to dynamically discover files based on selected servers
|
||||||
|
"""
|
||||||
|
files = []
|
||||||
|
if self.selected_servers:
|
||||||
|
for server_name in self.selected_servers:
|
||||||
|
if server_name in self.server_docs_map:
|
||||||
|
files.append(self.server_docs_map[server_name])
|
||||||
|
return files
|
||||||
|
|
||||||
|
def _install(self, config: Dict[str, Any]) -> bool:
|
||||||
|
"""Install MCP documentation component"""
|
||||||
|
self.logger.info("Installing MCP server documentation...")
|
||||||
|
|
||||||
|
# Get selected servers from config
|
||||||
|
selected_servers = config.get("selected_mcp_servers", [])
|
||||||
|
if not selected_servers:
|
||||||
|
self.logger.info("No MCP servers selected - skipping documentation installation")
|
||||||
|
return True
|
||||||
|
|
||||||
|
self.set_selected_servers(selected_servers)
|
||||||
|
|
||||||
|
# Update component files based on selection
|
||||||
|
self.component_files = self._discover_component_files()
|
||||||
|
|
||||||
|
# Validate installation
|
||||||
|
success, errors = self.validate_prerequisites()
|
||||||
|
if not success:
|
||||||
|
for error in errors:
|
||||||
|
self.logger.error(error)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Get files to install
|
||||||
|
files_to_install = self.get_files_to_install()
|
||||||
|
|
||||||
|
if not files_to_install:
|
||||||
|
self.logger.warning("No MCP documentation files found to install")
|
||||||
|
return True # Not an error - just no docs to install
|
||||||
|
|
||||||
|
# Copy documentation files
|
||||||
|
success_count = 0
|
||||||
|
for source, target in files_to_install:
|
||||||
|
self.logger.debug(f"Copying {source.name} to {target}")
|
||||||
|
|
||||||
|
if self.file_manager.copy_file(source, target):
|
||||||
|
success_count += 1
|
||||||
|
self.logger.debug(f"Successfully copied {source.name}")
|
||||||
|
else:
|
||||||
|
self.logger.error(f"Failed to copy {source.name}")
|
||||||
|
|
||||||
|
if success_count != len(files_to_install):
|
||||||
|
self.logger.error(f"Only {success_count}/{len(files_to_install)} documentation files copied successfully")
|
||||||
|
return False
|
||||||
|
|
||||||
|
self.logger.success(f"MCP documentation installed successfully ({success_count} files for {len(selected_servers)} servers)")
|
||||||
|
|
||||||
|
return self._post_install()
|
||||||
|
|
||||||
|
def _post_install(self) -> bool:
|
||||||
|
"""Post-installation tasks"""
|
||||||
|
try:
|
||||||
|
# Update metadata
|
||||||
|
metadata_mods = {
|
||||||
|
"components": {
|
||||||
|
"mcp_docs": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"installed": True,
|
||||||
|
"files_count": len(self.component_files),
|
||||||
|
"servers_documented": self.selected_servers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.settings_manager.update_metadata(metadata_mods)
|
||||||
|
self.logger.info("Updated metadata with MCP docs component registration")
|
||||||
|
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Failed to update metadata: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def uninstall(self) -> bool:
|
||||||
|
"""Uninstall MCP documentation component"""
|
||||||
|
try:
|
||||||
|
self.logger.info("Uninstalling MCP documentation component...")
|
||||||
|
|
||||||
|
# Remove all MCP documentation files
|
||||||
|
removed_count = 0
|
||||||
|
source_dir = self._get_source_dir()
|
||||||
|
|
||||||
|
if source_dir and source_dir.exists():
|
||||||
|
# Remove all possible MCP doc files
|
||||||
|
for doc_file in self.server_docs_map.values():
|
||||||
|
file_path = self.install_component_subdir / doc_file
|
||||||
|
if self.file_manager.remove_file(file_path):
|
||||||
|
removed_count += 1
|
||||||
|
self.logger.debug(f"Removed {doc_file}")
|
||||||
|
|
||||||
|
# Remove mcp directory if empty
|
||||||
|
try:
|
||||||
|
if self.install_component_subdir.exists():
|
||||||
|
remaining_files = list(self.install_component_subdir.iterdir())
|
||||||
|
if not remaining_files:
|
||||||
|
self.install_component_subdir.rmdir()
|
||||||
|
self.logger.debug("Removed empty mcp directory")
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.warning(f"Could not remove mcp directory: {e}")
|
||||||
|
|
||||||
|
# Update settings.json
|
||||||
|
try:
|
||||||
|
if self.settings_manager.is_component_installed("mcp_docs"):
|
||||||
|
self.settings_manager.remove_component_registration("mcp_docs")
|
||||||
|
self.logger.info("Removed MCP docs component from settings.json")
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.warning(f"Could not update settings.json: {e}")
|
||||||
|
|
||||||
|
self.logger.success(f"MCP documentation uninstalled ({removed_count} files removed)")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.exception(f"Unexpected error during MCP docs uninstallation: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_dependencies(self) -> List[str]:
|
||||||
|
"""Get dependencies"""
|
||||||
|
return ["core"]
|
||||||
|
|
||||||
|
def _get_source_dir(self) -> Optional[Path]:
|
||||||
|
"""Get source directory for MCP documentation files"""
|
||||||
|
# Assume we're in SuperClaude/setup/components/mcp_docs.py
|
||||||
|
# and MCP docs are in SuperClaude/SuperClaude/MCP/
|
||||||
|
project_root = Path(__file__).parent.parent.parent
|
||||||
|
mcp_dir = project_root / "SuperClaude" / "MCP"
|
||||||
|
|
||||||
|
# Return None if directory doesn't exist to prevent warning
|
||||||
|
if not mcp_dir.exists():
|
||||||
|
return None
|
||||||
|
|
||||||
|
return mcp_dir
|
||||||
|
|
||||||
|
def get_size_estimate(self) -> int:
|
||||||
|
"""Get estimated installation size"""
|
||||||
|
source_dir = self._get_source_dir()
|
||||||
|
total_size = 0
|
||||||
|
|
||||||
|
if source_dir and source_dir.exists() and self.selected_servers:
|
||||||
|
for server_name in self.selected_servers:
|
||||||
|
if server_name in self.server_docs_map:
|
||||||
|
doc_file = self.server_docs_map[server_name]
|
||||||
|
file_path = source_dir / doc_file
|
||||||
|
if file_path.exists():
|
||||||
|
total_size += file_path.stat().st_size
|
||||||
|
|
||||||
|
# Minimum size estimate
|
||||||
|
total_size = max(total_size, 10240) # At least 10KB
|
||||||
|
|
||||||
|
return total_size
|
||||||
153
setup/components/modes.py
Normal file
153
setup/components/modes.py
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
"""
|
||||||
|
Modes component for SuperClaude behavioral modes
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Dict, List, Tuple, Optional, Any
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from ..base.component import Component
|
||||||
|
|
||||||
|
|
||||||
|
class ModesComponent(Component):
|
||||||
|
"""SuperClaude behavioral modes component"""
|
||||||
|
|
||||||
|
def __init__(self, install_dir: Optional[Path] = None):
|
||||||
|
"""Initialize modes component"""
|
||||||
|
super().__init__(install_dir, Path("modes"))
|
||||||
|
|
||||||
|
def get_metadata(self) -> Dict[str, str]:
|
||||||
|
"""Get component metadata"""
|
||||||
|
return {
|
||||||
|
"name": "modes",
|
||||||
|
"version": "4.0.0",
|
||||||
|
"description": "SuperClaude behavioral modes (Brainstorming, Introspection, Task Management, Token Efficiency)",
|
||||||
|
"category": "modes"
|
||||||
|
}
|
||||||
|
|
||||||
|
def _install(self, config: Dict[str, Any]) -> bool:
|
||||||
|
"""Install modes component"""
|
||||||
|
self.logger.info("Installing SuperClaude behavioral modes...")
|
||||||
|
|
||||||
|
# Validate installation
|
||||||
|
success, errors = self.validate_prerequisites()
|
||||||
|
if not success:
|
||||||
|
for error in errors:
|
||||||
|
self.logger.error(error)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Get files to install
|
||||||
|
files_to_install = self.get_files_to_install()
|
||||||
|
|
||||||
|
if not files_to_install:
|
||||||
|
self.logger.warning("No mode files found to install")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Copy mode files
|
||||||
|
success_count = 0
|
||||||
|
for source, target in files_to_install:
|
||||||
|
self.logger.debug(f"Copying {source.name} to {target}")
|
||||||
|
|
||||||
|
if self.file_manager.copy_file(source, target):
|
||||||
|
success_count += 1
|
||||||
|
self.logger.debug(f"Successfully copied {source.name}")
|
||||||
|
else:
|
||||||
|
self.logger.error(f"Failed to copy {source.name}")
|
||||||
|
|
||||||
|
if success_count != len(files_to_install):
|
||||||
|
self.logger.error(f"Only {success_count}/{len(files_to_install)} mode files copied successfully")
|
||||||
|
return False
|
||||||
|
|
||||||
|
self.logger.success(f"Modes component installed successfully ({success_count} mode files)")
|
||||||
|
|
||||||
|
return self._post_install()
|
||||||
|
|
||||||
|
def _post_install(self) -> bool:
|
||||||
|
"""Post-installation tasks"""
|
||||||
|
try:
|
||||||
|
# Update metadata
|
||||||
|
metadata_mods = {
|
||||||
|
"components": {
|
||||||
|
"modes": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"installed": True,
|
||||||
|
"files_count": len(self.component_files)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.settings_manager.update_metadata(metadata_mods)
|
||||||
|
self.logger.info("Updated metadata with modes component registration")
|
||||||
|
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Failed to update metadata: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def uninstall(self) -> bool:
|
||||||
|
"""Uninstall modes component"""
|
||||||
|
try:
|
||||||
|
self.logger.info("Uninstalling SuperClaude modes component...")
|
||||||
|
|
||||||
|
# Remove mode files
|
||||||
|
removed_count = 0
|
||||||
|
for _, target in self.get_files_to_install():
|
||||||
|
if self.file_manager.remove_file(target):
|
||||||
|
removed_count += 1
|
||||||
|
self.logger.debug(f"Removed {target.name}")
|
||||||
|
|
||||||
|
# Remove modes directory if empty
|
||||||
|
try:
|
||||||
|
if self.install_component_subdir.exists():
|
||||||
|
remaining_files = list(self.install_component_subdir.iterdir())
|
||||||
|
if not remaining_files:
|
||||||
|
self.install_component_subdir.rmdir()
|
||||||
|
self.logger.debug("Removed empty modes directory")
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.warning(f"Could not remove modes directory: {e}")
|
||||||
|
|
||||||
|
# Update settings.json
|
||||||
|
try:
|
||||||
|
if self.settings_manager.is_component_installed("modes"):
|
||||||
|
self.settings_manager.remove_component_registration("modes")
|
||||||
|
self.logger.info("Removed modes component from settings.json")
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.warning(f"Could not update settings.json: {e}")
|
||||||
|
|
||||||
|
self.logger.success(f"Modes component uninstalled ({removed_count} files removed)")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.exception(f"Unexpected error during modes uninstallation: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_dependencies(self) -> List[str]:
|
||||||
|
"""Get dependencies"""
|
||||||
|
return ["core"]
|
||||||
|
|
||||||
|
def _get_source_dir(self) -> Optional[Path]:
|
||||||
|
"""Get source directory for mode files"""
|
||||||
|
# Assume we're in SuperClaude/setup/components/modes.py
|
||||||
|
# and mode files are in SuperClaude/SuperClaude/Modes/
|
||||||
|
project_root = Path(__file__).parent.parent.parent
|
||||||
|
modes_dir = project_root / "SuperClaude" / "Modes"
|
||||||
|
|
||||||
|
# Return None if directory doesn't exist to prevent warning
|
||||||
|
if not modes_dir.exists():
|
||||||
|
return None
|
||||||
|
|
||||||
|
return modes_dir
|
||||||
|
|
||||||
|
def get_size_estimate(self) -> int:
|
||||||
|
"""Get estimated installation size"""
|
||||||
|
source_dir = self._get_source_dir()
|
||||||
|
total_size = 0
|
||||||
|
|
||||||
|
if source_dir and source_dir.exists():
|
||||||
|
for filename in self.component_files:
|
||||||
|
file_path = source_dir / filename
|
||||||
|
if file_path.exists():
|
||||||
|
total_size += file_path.stat().st_size
|
||||||
|
|
||||||
|
# Minimum size estimate
|
||||||
|
total_size = max(total_size, 20480) # At least 20KB
|
||||||
|
|
||||||
|
return total_size
|
||||||
@@ -138,7 +138,7 @@ def get_components_to_install(args: argparse.Namespace, registry: ComponentRegis
|
|||||||
# Explicit components specified
|
# Explicit components specified
|
||||||
if args.components:
|
if args.components:
|
||||||
if 'all' in args.components:
|
if 'all' in args.components:
|
||||||
return ["core", "commands", "agents", "mcp"] # hooks removed - not yet implemented
|
return ["core", "commands", "agents", "modes", "mcp", "mcp_docs"]
|
||||||
return args.components
|
return args.components
|
||||||
|
|
||||||
# Profile-based selection
|
# Profile-based selection
|
||||||
@@ -169,38 +169,141 @@ def get_components_to_install(args: argparse.Namespace, registry: ComponentRegis
|
|||||||
return interactive_component_selection(registry, config_manager)
|
return interactive_component_selection(registry, config_manager)
|
||||||
|
|
||||||
|
|
||||||
def interactive_component_selection(registry: ComponentRegistry, config_manager: ConfigManager) -> Optional[List[str]]:
|
def select_mcp_servers(registry: ComponentRegistry) -> List[str]:
|
||||||
"""Interactive component selection"""
|
"""Stage 1: MCP Server Selection"""
|
||||||
logger = get_logger()
|
logger = get_logger()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Get available components
|
# Get MCP component to access server list
|
||||||
available_components = registry.list_components()
|
mcp_instance = registry.get_component_instance("mcp", Path.home() / ".claude")
|
||||||
|
if not mcp_instance or not hasattr(mcp_instance, 'mcp_servers'):
|
||||||
|
logger.error("Could not access MCP server information")
|
||||||
|
return []
|
||||||
|
|
||||||
if not available_components:
|
# Create MCP server menu
|
||||||
logger.error("No components available for installation")
|
mcp_servers = mcp_instance.mcp_servers
|
||||||
return None
|
server_options = []
|
||||||
|
|
||||||
# Create component menu with descriptions
|
for server_key, server_info in mcp_servers.items():
|
||||||
menu_options = []
|
description = server_info["description"]
|
||||||
|
api_key_note = " (requires API key)" if server_info.get("requires_api_key", False) else ""
|
||||||
|
server_options.append(f"{server_key} - {description}{api_key_note}")
|
||||||
|
|
||||||
|
print(f"\n{Colors.CYAN}{Colors.BRIGHT}═══════════════════════════════════════════════════{Colors.RESET}")
|
||||||
|
print(f"{Colors.CYAN}{Colors.BRIGHT}Stage 1: MCP Server Selection (Optional){Colors.RESET}")
|
||||||
|
print(f"{Colors.CYAN}{Colors.BRIGHT}═══════════════════════════════════════════════════{Colors.RESET}")
|
||||||
|
print(f"\n{Colors.BLUE}MCP servers extend Claude Code with specialized capabilities.{Colors.RESET}")
|
||||||
|
print(f"{Colors.BLUE}Select servers to configure (you can always add more later):{Colors.RESET}")
|
||||||
|
|
||||||
|
# Add option to skip MCP
|
||||||
|
server_options.append("Skip MCP Server installation")
|
||||||
|
|
||||||
|
menu = Menu("Select MCP servers to configure:", server_options, multi_select=True)
|
||||||
|
selections = menu.display()
|
||||||
|
|
||||||
|
if not selections:
|
||||||
|
logger.info("No MCP servers selected")
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Filter out the "skip" option and return server keys
|
||||||
|
server_keys = list(mcp_servers.keys())
|
||||||
|
selected_servers = []
|
||||||
|
|
||||||
|
for i in selections:
|
||||||
|
if i < len(server_keys): # Not the "skip" option
|
||||||
|
selected_servers.append(server_keys[i])
|
||||||
|
|
||||||
|
if selected_servers:
|
||||||
|
logger.info(f"Selected MCP servers: {', '.join(selected_servers)}")
|
||||||
|
else:
|
||||||
|
logger.info("No MCP servers selected")
|
||||||
|
|
||||||
|
return selected_servers
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in MCP server selection: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def select_framework_components(registry: ComponentRegistry, config_manager: ConfigManager, selected_mcp_servers: List[str]) -> List[str]:
|
||||||
|
"""Stage 2: Framework Component Selection"""
|
||||||
|
logger = get_logger()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Framework components (excluding MCP-related ones)
|
||||||
|
framework_components = ["core", "modes", "commands", "agents"]
|
||||||
|
|
||||||
|
# Create component menu
|
||||||
|
component_options = []
|
||||||
component_info = {}
|
component_info = {}
|
||||||
|
|
||||||
for component_name in available_components:
|
for component_name in framework_components:
|
||||||
metadata = registry.get_component_metadata(component_name)
|
metadata = registry.get_component_metadata(component_name)
|
||||||
if metadata:
|
if metadata:
|
||||||
description = metadata.get("description", "No description")
|
description = metadata.get("description", "No description")
|
||||||
category = metadata.get("category", "unknown")
|
component_options.append(f"{component_name} - {description}")
|
||||||
menu_options.append(f"{component_name} ({category}) - {description}")
|
|
||||||
component_info[component_name] = metadata
|
component_info[component_name] = metadata
|
||||||
else:
|
|
||||||
menu_options.append(f"{component_name} - Component description unavailable")
|
|
||||||
component_info[component_name] = {"description": "Unknown"}
|
|
||||||
|
|
||||||
# Add preset options
|
# Add MCP documentation option
|
||||||
|
if selected_mcp_servers:
|
||||||
|
mcp_docs_desc = f"MCP documentation for {', '.join(selected_mcp_servers)} (auto-selected)"
|
||||||
|
component_options.append(f"mcp_docs - {mcp_docs_desc}")
|
||||||
|
auto_selected_mcp_docs = True
|
||||||
|
else:
|
||||||
|
component_options.append("mcp_docs - MCP server documentation (none selected)")
|
||||||
|
auto_selected_mcp_docs = False
|
||||||
|
|
||||||
|
print(f"\n{Colors.CYAN}{Colors.BRIGHT}═══════════════════════════════════════════════════{Colors.RESET}")
|
||||||
|
print(f"{Colors.CYAN}{Colors.BRIGHT}Stage 2: Framework Component Selection{Colors.RESET}")
|
||||||
|
print(f"{Colors.CYAN}{Colors.BRIGHT}═══════════════════════════════════════════════════{Colors.RESET}")
|
||||||
|
print(f"\n{Colors.BLUE}Select SuperClaude framework components to install:{Colors.RESET}")
|
||||||
|
|
||||||
|
menu = Menu("Select components (Core is recommended):", component_options, multi_select=True)
|
||||||
|
selections = menu.display()
|
||||||
|
|
||||||
|
if not selections:
|
||||||
|
# Default to core if nothing selected
|
||||||
|
logger.info("No components selected, defaulting to core")
|
||||||
|
selected_components = ["core"]
|
||||||
|
else:
|
||||||
|
selected_components = []
|
||||||
|
all_components = framework_components + ["mcp_docs"]
|
||||||
|
|
||||||
|
for i in selections:
|
||||||
|
if i < len(all_components):
|
||||||
|
selected_components.append(all_components[i])
|
||||||
|
|
||||||
|
# Auto-select MCP docs if not explicitly deselected and we have MCP servers
|
||||||
|
if auto_selected_mcp_docs and "mcp_docs" not in selected_components:
|
||||||
|
# Check if user explicitly deselected it
|
||||||
|
mcp_docs_index = len(framework_components) # Index of mcp_docs in the menu
|
||||||
|
if mcp_docs_index not in selections:
|
||||||
|
# User didn't select it, but we auto-select it
|
||||||
|
selected_components.append("mcp_docs")
|
||||||
|
logger.info("Auto-selected MCP documentation for configured servers")
|
||||||
|
|
||||||
|
# Always include MCP component if servers were selected
|
||||||
|
if selected_mcp_servers and "mcp" not in selected_components:
|
||||||
|
selected_components.append("mcp")
|
||||||
|
|
||||||
|
logger.info(f"Selected framework components: {', '.join(selected_components)}")
|
||||||
|
return selected_components
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in framework component selection: {e}")
|
||||||
|
return ["core"] # Fallback to core
|
||||||
|
|
||||||
|
|
||||||
|
def interactive_component_selection(registry: ComponentRegistry, config_manager: ConfigManager) -> Optional[List[str]]:
|
||||||
|
"""Two-stage interactive component selection"""
|
||||||
|
logger = get_logger()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Add preset options first
|
||||||
preset_options = [
|
preset_options = [
|
||||||
"Quick Installation (recommended components)",
|
"Quick Installation (recommended components)",
|
||||||
"Minimal Installation (core only)",
|
"Minimal Installation (core only)",
|
||||||
"Custom Selection"
|
"Custom Two-Stage Selection"
|
||||||
]
|
]
|
||||||
|
|
||||||
print(f"\n{Colors.CYAN}SuperClaude Installation Options:{Colors.RESET}")
|
print(f"\n{Colors.CYAN}SuperClaude Installation Options:{Colors.RESET}")
|
||||||
@@ -218,16 +321,19 @@ def interactive_component_selection(registry: ComponentRegistry, config_manager:
|
|||||||
return ["core"]
|
return ["core"]
|
||||||
elif choice == 1: # Minimal
|
elif choice == 1: # Minimal
|
||||||
return ["core"]
|
return ["core"]
|
||||||
elif choice == 2: # Custom
|
elif choice == 2: # Custom Two-Stage
|
||||||
print(f"\n{Colors.CYAN}Available Components:{Colors.RESET}")
|
# Stage 1: MCP Server Selection
|
||||||
component_menu = Menu("Select components to install:", menu_options, multi_select=True)
|
selected_mcp_servers = select_mcp_servers(registry)
|
||||||
selections = component_menu.display()
|
|
||||||
|
|
||||||
if not selections:
|
# Stage 2: Framework Component Selection
|
||||||
logger.warning("No components selected")
|
selected_components = select_framework_components(registry, config_manager, selected_mcp_servers)
|
||||||
return None
|
|
||||||
|
|
||||||
return [available_components[i] for i in selections]
|
# Store selected MCP servers for components to use
|
||||||
|
if not hasattr(config_manager, '_installation_context'):
|
||||||
|
config_manager._installation_context = {}
|
||||||
|
config_manager._installation_context["selected_mcp_servers"] = selected_mcp_servers
|
||||||
|
|
||||||
|
return selected_components
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -331,7 +437,7 @@ def run_system_diagnostics(validator: Validator) -> None:
|
|||||||
print(" 3. Run 'SuperClaude install --diagnose' again to verify")
|
print(" 3. Run 'SuperClaude install --diagnose' again to verify")
|
||||||
|
|
||||||
|
|
||||||
def perform_installation(components: List[str], args: argparse.Namespace) -> bool:
|
def perform_installation(components: List[str], args: argparse.Namespace, config_manager: ConfigManager = None) -> bool:
|
||||||
"""Perform the actual installation"""
|
"""Perform the actual installation"""
|
||||||
logger = get_logger()
|
logger = get_logger()
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
@@ -370,7 +476,8 @@ def perform_installation(components: List[str], args: argparse.Namespace) -> boo
|
|||||||
config = {
|
config = {
|
||||||
"force": args.force,
|
"force": args.force,
|
||||||
"backup": not args.no_backup,
|
"backup": not args.no_backup,
|
||||||
"dry_run": args.dry_run
|
"dry_run": args.dry_run,
|
||||||
|
"selected_mcp_servers": getattr(config_manager, '_installation_context', {}).get("selected_mcp_servers", [])
|
||||||
}
|
}
|
||||||
|
|
||||||
success = installer.install_components(ordered_components, config)
|
success = installer.install_components(ordered_components, config)
|
||||||
@@ -518,7 +625,7 @@ def run(args: argparse.Namespace) -> int:
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
# Perform installation
|
# Perform installation
|
||||||
success = perform_installation(components, args)
|
success = perform_installation(components, args, config_manager)
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
if not args.quiet:
|
if not args.quiet:
|
||||||
|
|||||||
Reference in New Issue
Block a user