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": {
|
||||
"name": "mcp",
|
||||
"version": "3.0.0",
|
||||
"description": "MCP server integration (Context7, Sequential, Magic, Playwright, Morphllm, Serena)",
|
||||
"version": "4.0.0",
|
||||
"description": "MCP server configuration management via .claude.json",
|
||||
"category": "integration",
|
||||
"dependencies": ["core"],
|
||||
"enabled": true,
|
||||
"required_tools": ["node", "claude_cli"]
|
||||
"required_tools": []
|
||||
},
|
||||
"serena": {
|
||||
"name": "serena",
|
||||
"version": "3.0.0",
|
||||
"description": "Semantic code analysis and intelligent editing with project-aware context management",
|
||||
"category": "integration",
|
||||
"dependencies": ["core", "mcp"],
|
||||
"modes": {
|
||||
"name": "modes",
|
||||
"version": "4.0.0",
|
||||
"description": "SuperClaude behavioral modes (Brainstorming, Introspection, Task Management, Token Efficiency)",
|
||||
"category": "modes",
|
||||
"dependencies": ["core"],
|
||||
"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": {
|
||||
"name": "agents",
|
||||
@@ -44,15 +53,6 @@
|
||||
"dependencies": ["core"],
|
||||
"enabled": true,
|
||||
"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",
|
||||
"commands",
|
||||
"agents",
|
||||
"mcp"
|
||||
"modes",
|
||||
"mcp",
|
||||
"mcp_docs"
|
||||
],
|
||||
"features": {
|
||||
"auto_update": false,
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
"components": [
|
||||
"core",
|
||||
"commands",
|
||||
"agents"
|
||||
"agents",
|
||||
"modes"
|
||||
],
|
||||
"features": {
|
||||
"auto_update": false,
|
||||
|
||||
@@ -4,12 +4,14 @@ from .core import CoreComponent
|
||||
from .commands import CommandsComponent
|
||||
from .mcp import MCPComponent
|
||||
from .agents import AgentsComponent
|
||||
# from .hooks import HooksComponent # Commented out - not yet implemented
|
||||
from .modes import ModesComponent
|
||||
from .mcp_docs import MCPDocsComponent
|
||||
|
||||
__all__ = [
|
||||
'CoreComponent',
|
||||
'CommandsComponent',
|
||||
'MCPComponent',
|
||||
'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 sys
|
||||
import json
|
||||
import shutil
|
||||
from typing import Dict, List, Tuple, Optional, Any
|
||||
from pathlib import Path
|
||||
|
||||
@@ -12,360 +12,289 @@ from ..utils.ui import display_info, display_warning
|
||||
|
||||
|
||||
class MCPComponent(Component):
|
||||
"""MCP servers integration component"""
|
||||
"""MCP servers configuration component"""
|
||||
|
||||
def __init__(self, install_dir: Optional[Path] = None):
|
||||
"""Initialize MCP component"""
|
||||
super().__init__(install_dir)
|
||||
|
||||
# Define MCP servers to install
|
||||
# Define MCP servers available for configuration
|
||||
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": {
|
||||
"name": "context7",
|
||||
"name": "context7",
|
||||
"description": "Official library documentation and code examples",
|
||||
"npm_package": "@upstash/context7-mcp",
|
||||
"required": True
|
||||
"config_file": "context7.json",
|
||||
"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": {
|
||||
"name": "magic",
|
||||
"description": "Modern UI component generation and design systems",
|
||||
"npm_package": "@21st-dev/magic",
|
||||
"required": False,
|
||||
"api_key_env": "TWENTYFIRST_API_KEY",
|
||||
"api_key_description": "21st.dev API key for UI component generation"
|
||||
"config_file": "magic.json",
|
||||
"requires_api_key": True,
|
||||
"api_key_env": "TWENTYFIRST_API_KEY"
|
||||
},
|
||||
"playwright": {
|
||||
"name": "playwright",
|
||||
"description": "Cross-browser E2E testing and automation",
|
||||
"npm_package": "@playwright/mcp@latest",
|
||||
"required": False
|
||||
"config_file": "playwright.json",
|
||||
"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]:
|
||||
"""Get component metadata"""
|
||||
return {
|
||||
"name": "mcp",
|
||||
"version": "3.0.0",
|
||||
"description": "MCP server integration (Context7, Sequential, Magic, Playwright)",
|
||||
"version": "4.0.0",
|
||||
"description": "MCP server configuration management via .claude.json",
|
||||
"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]]:
|
||||
"""Check prerequisites"""
|
||||
"""
|
||||
Check prerequisites for MCP component
|
||||
"""
|
||||
errors = []
|
||||
|
||||
# Check if Node.js is available
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["node", "--version"],
|
||||
capture_output=True,
|
||||
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 config source directory exists
|
||||
source_dir = self._get_config_source_dir()
|
||||
if not source_dir or not source_dir.exists():
|
||||
errors.append(f"MCP config source directory not found: {source_dir}")
|
||||
return False, errors
|
||||
|
||||
# Check if Claude CLI is available
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["claude", "--version"],
|
||||
capture_output=True,
|
||||
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")
|
||||
# Check if user's Claude config exists
|
||||
claude_config = Path.home() / ".claude.json"
|
||||
if not claude_config.exists():
|
||||
errors.append(f"Claude configuration file not found: {claude_config}")
|
||||
errors.append("Please run Claude Code at least once to create the configuration file")
|
||||
|
||||
return len(errors) == 0, errors
|
||||
|
||||
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 []
|
||||
|
||||
def get_metadata_modifications(self) -> Dict[str, Any]:
|
||||
"""Get metadata modifications for MCP component"""
|
||||
return {
|
||||
"components": {
|
||||
"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"]
|
||||
def _get_config_source_dir(self) -> Optional[Path]:
|
||||
"""Get source directory for MCP config files"""
|
||||
project_root = Path(__file__).parent.parent.parent
|
||||
config_dir = project_root / "SuperClaude" / "MCP" / "configs"
|
||||
|
||||
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:
|
||||
self.logger.info(f"Installing MCP server: {server_name}")
|
||||
|
||||
# Check if already installed
|
||||
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
|
||||
with open(claude_config_path, 'r') as f:
|
||||
config = json.load(f)
|
||||
return config, claude_config_path
|
||||
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
|
||||
|
||||
def _uninstall_mcp_server(self, server_name: str) -> bool:
|
||||
"""Uninstall a single MCP server"""
|
||||
def _load_mcp_server_config(self, server_key: str) -> Optional[Dict]:
|
||||
"""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:
|
||||
self.logger.info(f"Uninstalling MCP server: {server_name}")
|
||||
|
||||
# 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
|
||||
with open(config_path, 'r') as f:
|
||||
return json.load(f)
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error uninstalling MCP server {server_name}: {e}")
|
||||
return False
|
||||
self.logger.error(f"Failed to load MCP config for {server_key}: {e}")
|
||||
return None
|
||||
|
||||
def _install(self, config: Dict[str, Any]) -> bool:
|
||||
"""Install MCP component"""
|
||||
self.logger.info("Installing SuperClaude MCP servers...")
|
||||
|
||||
"""Install MCP component by configuring .claude.json"""
|
||||
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
|
||||
success, errors = self.validate_prerequisites()
|
||||
if not success:
|
||||
for error in errors:
|
||||
self.logger.error(error)
|
||||
return False
|
||||
|
||||
# Install each MCP server
|
||||
installed_count = 0
|
||||
failed_servers = []
|
||||
|
||||
for server_name, server_info in self.mcp_servers.items():
|
||||
if self._install_mcp_server(server_info, config):
|
||||
installed_count += 1
|
||||
else:
|
||||
failed_servers.append(server_name)
|
||||
|
||||
# Check if this is a required server
|
||||
if server_info.get("required", False):
|
||||
self.logger.error(f"Required MCP server {server_name} failed to install")
|
||||
return False
|
||||
|
||||
# Verify installation
|
||||
if not config.get("dry_run", False):
|
||||
self.logger.info("Verifying MCP server installation...")
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["claude", "mcp", "list"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=15,
|
||||
shell=(sys.platform == "win32")
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
self.logger.debug("MCP servers list:")
|
||||
for line in result.stdout.strip().split('\n'):
|
||||
if line.strip():
|
||||
self.logger.debug(f" {line.strip()}")
|
||||
else:
|
||||
self.logger.warning("Could not verify MCP server installation")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.warning(f"Could not verify MCP installation: {e}")
|
||||
|
||||
if failed_servers:
|
||||
self.logger.warning(f"Some MCP servers failed to install: {failed_servers}")
|
||||
self.logger.success(f"MCP component partially installed ({installed_count} servers)")
|
||||
|
||||
# Load Claude configuration
|
||||
claude_config, config_path = self._load_claude_config()
|
||||
if claude_config is None:
|
||||
return False
|
||||
|
||||
# Ensure mcpServers section exists
|
||||
if "mcpServers" not in claude_config:
|
||||
claude_config["mcpServers"] = {}
|
||||
|
||||
# Configure each selected server
|
||||
configured_count = 0
|
||||
for server_key in selected_servers:
|
||||
if server_key not in self.mcp_servers:
|
||||
self.logger.warning(f"Unknown MCP server: {server_key}")
|
||||
continue
|
||||
|
||||
server_info = self.mcp_servers[server_key]
|
||||
server_config = self._load_mcp_server_config(server_key)
|
||||
|
||||
if server_config is None:
|
||||
self.logger.error(f"Failed to load configuration for {server_key}")
|
||||
continue
|
||||
|
||||
# Handle API key requirements
|
||||
if server_info.get("requires_api_key", False):
|
||||
api_key_env = server_info.get("api_key_env")
|
||||
if api_key_env:
|
||||
display_info(f"Server '{server_key}' requires API key: {api_key_env}")
|
||||
display_info("You can set this environment variable later")
|
||||
|
||||
# Merge server config into Claude config
|
||||
claude_config["mcpServers"].update(server_config)
|
||||
configured_count += 1
|
||||
|
||||
self.logger.info(f"Configured MCP server: {server_info['name']}")
|
||||
|
||||
if configured_count == 0:
|
||||
self.logger.error("No MCP servers were successfully configured")
|
||||
return False
|
||||
|
||||
# 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:
|
||||
self.logger.success(f"MCP component installed successfully ({installed_count} servers)")
|
||||
|
||||
return self._post_install()
|
||||
|
||||
return False
|
||||
|
||||
def _post_install(self) -> bool:
|
||||
# Update metadata
|
||||
"""Post-installation tasks"""
|
||||
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)
|
||||
|
||||
# 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")
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to update metadata: {e}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def uninstall(self) -> bool:
|
||||
"""Uninstall MCP component"""
|
||||
"""Uninstall MCP component by removing servers from .claude.json"""
|
||||
try:
|
||||
self.logger.info("Uninstalling SuperClaude MCP servers...")
|
||||
self.logger.info("Removing MCP server configurations...")
|
||||
|
||||
# Uninstall each MCP server
|
||||
uninstalled_count = 0
|
||||
# Load Claude configuration
|
||||
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 self._uninstall_mcp_server(server_name):
|
||||
uninstalled_count += 1
|
||||
if "mcpServers" not in claude_config:
|
||||
self.logger.info("No MCP servers configured")
|
||||
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:
|
||||
if self.settings_manager.is_component_installed("mcp"):
|
||||
self.settings_manager.remove_component_registration("mcp")
|
||||
# Also remove MCP configuration from metadata
|
||||
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")
|
||||
self.logger.info("Removed MCP component from settings.json")
|
||||
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
|
||||
|
||||
except Exception as e:
|
||||
@@ -376,123 +305,6 @@ class MCPComponent(Component):
|
||||
"""Get dependencies"""
|
||||
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:
|
||||
"""Get estimated installation size"""
|
||||
# MCP servers are installed via npm, estimate based on typical sizes
|
||||
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"]
|
||||
}
|
||||
"""Get estimated size - minimal since we only modify config"""
|
||||
return 4096 # 4KB - just config modifications
|
||||
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
|
||||
if 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
|
||||
|
||||
# Profile-based selection
|
||||
@@ -169,38 +169,141 @@ def get_components_to_install(args: argparse.Namespace, registry: ComponentRegis
|
||||
return interactive_component_selection(registry, config_manager)
|
||||
|
||||
|
||||
def interactive_component_selection(registry: ComponentRegistry, config_manager: ConfigManager) -> Optional[List[str]]:
|
||||
"""Interactive component selection"""
|
||||
def select_mcp_servers(registry: ComponentRegistry) -> List[str]:
|
||||
"""Stage 1: MCP Server Selection"""
|
||||
logger = get_logger()
|
||||
|
||||
try:
|
||||
# Get available components
|
||||
available_components = registry.list_components()
|
||||
# Get MCP component to access server list
|
||||
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:
|
||||
logger.error("No components available for installation")
|
||||
return None
|
||||
# Create MCP server menu
|
||||
mcp_servers = mcp_instance.mcp_servers
|
||||
server_options = []
|
||||
|
||||
# Create component menu with descriptions
|
||||
menu_options = []
|
||||
for server_key, server_info in mcp_servers.items():
|
||||
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 = {}
|
||||
|
||||
for component_name in available_components:
|
||||
for component_name in framework_components:
|
||||
metadata = registry.get_component_metadata(component_name)
|
||||
if metadata:
|
||||
description = metadata.get("description", "No description")
|
||||
category = metadata.get("category", "unknown")
|
||||
menu_options.append(f"{component_name} ({category}) - {description}")
|
||||
component_options.append(f"{component_name} - {description}")
|
||||
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 = [
|
||||
"Quick Installation (recommended components)",
|
||||
"Minimal Installation (core only)",
|
||||
"Custom Selection"
|
||||
"Custom Two-Stage Selection"
|
||||
]
|
||||
|
||||
print(f"\n{Colors.CYAN}SuperClaude Installation Options:{Colors.RESET}")
|
||||
@@ -218,16 +321,19 @@ def interactive_component_selection(registry: ComponentRegistry, config_manager:
|
||||
return ["core"]
|
||||
elif choice == 1: # Minimal
|
||||
return ["core"]
|
||||
elif choice == 2: # Custom
|
||||
print(f"\n{Colors.CYAN}Available Components:{Colors.RESET}")
|
||||
component_menu = Menu("Select components to install:", menu_options, multi_select=True)
|
||||
selections = component_menu.display()
|
||||
elif choice == 2: # Custom Two-Stage
|
||||
# Stage 1: MCP Server Selection
|
||||
selected_mcp_servers = select_mcp_servers(registry)
|
||||
|
||||
if not selections:
|
||||
logger.warning("No components selected")
|
||||
return None
|
||||
# Stage 2: Framework Component Selection
|
||||
selected_components = select_framework_components(registry, config_manager, selected_mcp_servers)
|
||||
|
||||
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
|
||||
|
||||
@@ -331,7 +437,7 @@ def run_system_diagnostics(validator: Validator) -> None:
|
||||
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"""
|
||||
logger = get_logger()
|
||||
start_time = time.time()
|
||||
@@ -370,7 +476,8 @@ def perform_installation(components: List[str], args: argparse.Namespace) -> boo
|
||||
config = {
|
||||
"force": args.force,
|
||||
"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)
|
||||
@@ -518,7 +625,7 @@ def run(args: argparse.Namespace) -> int:
|
||||
return 0
|
||||
|
||||
# Perform installation
|
||||
success = perform_installation(components, args)
|
||||
success = perform_installation(components, args, config_manager)
|
||||
|
||||
if success:
|
||||
if not args.quiet:
|
||||
|
||||
Reference in New Issue
Block a user