Update mcp.py

Fixed the mcp issue

Signed-off-by: Mithun Gowda B <mithungowda.b7411@gmail.com>
This commit is contained in:
Mithun Gowda B 2025-09-05 15:06:36 +05:30 committed by GitHub
parent 073cc80cb8
commit d0560b03b9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -1,473 +1,498 @@
"""
MCP component for MCP server configuration via .claude.json
MCP component for MCP server integration
"""
import json
import shutil
import time
import subprocess
import sys
from typing import Dict, List, Tuple, Optional, Any
from pathlib import Path
# Platform-specific file locking imports
try:
if sys.platform == "win32":
import msvcrt
LOCKING_AVAILABLE = "windows"
else:
import fcntl
LOCKING_AVAILABLE = "unix"
except ImportError:
LOCKING_AVAILABLE = None
from ..core.base import Component
from setup import __version__
from ..base.component import Component
from ..utils.ui import display_info, display_warning
from setup import __version__
class MCPComponent(Component):
"""MCP servers configuration component"""
"""MCP servers integration component"""
def __init__(self, install_dir: Optional[Path] = None):
"""Initialize MCP component"""
super().__init__(install_dir)
# Define MCP servers available for configuration
# Define MCP servers to install
self.mcp_servers = {
"context7": {
"name": "context7",
"description": "Official library documentation and code examples",
"config_file": "context7.json",
"requires_api_key": False
},
"sequential": {
"name": "sequential-thinking",
"sequential-thinking": {
"name": "sequential-thinking",
"description": "Multi-step problem solving and systematic analysis",
"config_file": "sequential.json",
"requires_api_key": False
"npm_package": "@modelcontextprotocol/server-sequential-thinking",
"required": True
},
"context7": {
"name": "context7",
"description": "Official library documentation and code examples",
"npm_package": "@upstash/context7-mcp",
"required": True
},
"magic": {
"name": "magic",
"description": "Modern UI component generation and design systems",
"config_file": "magic.json",
"requires_api_key": True,
"api_key_env": "TWENTYFIRST_API_KEY"
"npm_package": "@21st-dev/magic",
"required": False,
"api_key_env": "TWENTYFIRST_API_KEY",
"api_key_description": "21st.dev API key for UI component generation"
},
"playwright": {
"name": "playwright",
"description": "Cross-browser E2E testing and automation",
"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"
"npm_package": "@playwright/mcp@latest",
"required": False
}
}
# This will be set during installation - initialize as empty list
self.selected_servers: List[str] = []
# Store collected API keys for configuration
self.collected_api_keys: Dict[str, str] = {}
def _lock_file(self, file_handle, exclusive: bool = False):
"""Cross-platform file locking"""
if LOCKING_AVAILABLE == "unix":
lock_type = fcntl.LOCK_EX if exclusive else fcntl.LOCK_SH
fcntl.flock(file_handle.fileno(), lock_type)
elif LOCKING_AVAILABLE == "windows":
# Windows locking using msvcrt
if exclusive:
msvcrt.locking(file_handle.fileno(), msvcrt.LK_LOCK, 1)
# If no locking available, continue without locking
def _unlock_file(self, file_handle):
"""Cross-platform file unlocking"""
if LOCKING_AVAILABLE == "unix":
fcntl.flock(file_handle.fileno(), fcntl.LOCK_UN)
elif LOCKING_AVAILABLE == "windows":
msvcrt.locking(file_handle.fileno(), msvcrt.LK_UNLCK, 1)
# If no locking available, continue without unlocking
def get_metadata(self) -> Dict[str, str]:
"""Get component metadata"""
return {
"name": "mcp",
"version": __version__,
"description": "MCP server configuration management via .claude.json",
"description": "MCP server integration (Context7, Sequential, Magic, Playwright)",
"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 for MCP component
"""
"""Check prerequisites"""
errors = []
# 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 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 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")
# 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")
return len(errors) == 0, errors
def get_files_to_install(self) -> List[Tuple[Path, Path]]:
"""MCP component doesn't install files - it modifies .claude.json"""
"""Get files to install (none for MCP component)"""
return []
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"
if not config_dir.exists():
return None
return config_dir
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 _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 with file locking"""
claude_config_path = Path.home() / ".claude.json"
def _check_mcp_server_installed(self, server_name: str) -> bool:
"""Check if MCP server is already installed"""
try:
with open(claude_config_path, 'r') as f:
# Apply shared lock for reading
self._lock_file(f, exclusive=False)
try:
config = json.load(f)
return config, claude_config_path
finally:
self._unlock_file(f)
except Exception as 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 and file locking"""
max_retries = 3
retry_delay = 0.1
for attempt in range(max_retries):
try:
# Create backup first
if config_path.exists():
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 exclusive lock
with open(config_path, 'w') as f:
# Apply exclusive lock for writing
self._lock_file(f, exclusive=True)
try:
json.dump(config, f, indent=2)
f.flush() # Ensure data is written
finally:
self._unlock_file(f)
self.logger.debug("Updated Claude configuration")
return True
except (OSError, IOError) as e:
if attempt < max_retries - 1:
self.logger.warning(f"File lock attempt {attempt + 1} failed, retrying: {e}")
time.sleep(retry_delay * (2 ** attempt)) # Exponential backoff
continue
else:
self.logger.error(f"Failed to save Claude config after {max_retries} attempts: {e}")
return False
except Exception as e:
self.logger.error(f"Failed to save Claude config: {e}")
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
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 _merge_mcp_server_config(self, existing_config: Dict, new_config: Dict, server_key: str) -> None:
"""Precisely merge MCP server config, preserving user customizations
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"]
Args:
existing_config: User's current mcpServers configuration
new_config: New MCP server configuration to merge
server_key: Server key for logging purposes
"""
for server_name, server_def in new_config.items():
if server_name in existing_config:
# Server already exists - preserve user customizations
existing_server = existing_config[server_name]
# Only add missing keys, never overwrite existing ones
for key, value in server_def.items():
if key not in existing_server:
existing_server[key] = value
self.logger.debug(f"Added missing key '{key}' to existing server '{server_name}'")
else:
self.logger.debug(f"Preserved user customization for '{server_name}.{key}'")
# NEW: Apply environment variable references for API keys
if "env" in existing_server and self.collected_api_keys:
for env_key, env_value in existing_server["env"].items():
if env_key in self.collected_api_keys and env_value == "":
# Update to use environment variable reference
existing_server["env"][env_key] = f"${{{env_key}}}"
self.logger.info(f"Configured {env_key} to use environment variable")
self.logger.info(f"Updated existing MCP server '{server_name}' (preserved user customizations)")
else:
# New server - add complete configuration
# Apply environment variable references if we have collected keys
if "env" in server_def and self.collected_api_keys:
for env_key in server_def["env"]:
if env_key in self.collected_api_keys and server_def["env"][env_key] == "":
server_def["env"][env_key] = f"${{{env_key}}}"
existing_config[server_name] = server_def
self.logger.info(f"Added new MCP server '{server_name}' from {server_key}")
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
command = "npx"
try:
with open(config_path, 'r') as f:
return json.load(f)
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
except Exception as e:
self.logger.error(f"Failed to load MCP config for {server_key}: {e}")
return None
self.logger.error(f"Error installing MCP server {server_name}: {e}")
return False
def _uninstall_mcp_server(self, server_name: str) -> bool:
"""Uninstall a single MCP server"""
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
except Exception as e:
self.logger.error(f"Error uninstalling MCP server {server_name}: {e}")
return False
def _install(self, config: Dict[str, Any]) -> bool:
"""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)
# NEW: Log collected API keys information
if hasattr(self, 'collected_api_keys') and self.collected_api_keys:
self.logger.info(f"Using {len(self.collected_api_keys)} collected API keys for configuration")
"""Install MCP component"""
self.logger.info("Installing SuperClaude MCP servers...")
# Validate prerequisites
success, errors = self.validate_prerequisites()
if not success:
for error in errors:
self.logger.error(error)
return False
# 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")
# Precisely merge server config, preserving user customizations
self._merge_mcp_server_config(claude_config["mcpServers"], server_config, server_key)
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()
# 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)")
else:
return False
self.logger.success(f"MCP component installed successfully ({installed_count} servers)")
return self._post_install()
def _post_install(self) -> bool:
"""Post-installation tasks"""
# Update metadata
try:
# Update metadata
metadata_mods = {
"components": {
"mcp": {
"version": __version__,
"installed": True,
"servers_configured": len(self.selected_servers),
"configured_servers": self.selected_servers
}
}
}
metadata_mods = self.get_metadata_modifications()
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 by removing servers from .claude.json"""
"""Uninstall MCP component"""
try:
self.logger.info("Removing MCP server configurations...")
self.logger.info("Uninstalling SuperClaude MCP servers...")
# 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
# Uninstall each MCP server
uninstalled_count = 0
if "mcpServers" not in claude_config:
self.logger.info("No MCP servers configured")
return True
for server_name in self.mcp_servers.keys():
if self._uninstall_mcp_server(server_name):
uninstalled_count += 1
# Only remove servers that were installed by SuperClaude
removed_count = 0
installed_servers = self._get_installed_servers()
for server_name in installed_servers:
if server_name in claude_config["mcpServers"]:
# Check if this server was installed by SuperClaude by comparing with our configs
if self._is_superclaude_managed_server(claude_config["mcpServers"][server_name], server_name):
del claude_config["mcpServers"][server_name]
removed_count += 1
self.logger.debug(f"Removed SuperClaude-managed MCP server: {server_name}")
else:
self.logger.info(f"Preserved user-customized 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
# Update metadata to remove MCP component
try:
if self.settings_manager.is_component_installed("mcp"):
self.settings_manager.remove_component_registration("mcp")
self.logger.info("Removed MCP component from settings.json")
# 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")
except Exception as e:
self.logger.warning(f"Could not update settings.json: {e}")
self.logger.warning(f"Could not update metadata: {e}")
if removed_count > 0:
self.logger.success(f"MCP component uninstalled ({removed_count} SuperClaude-managed servers removed)")
else:
self.logger.info("MCP component uninstalled (no SuperClaude-managed servers to remove)")
self.logger.success(f"MCP component uninstalled ({uninstalled_count} servers removed)")
return True
except Exception as e:
self.logger.exception(f"Unexpected error during MCP uninstallation: {e}")
return False
def _get_installed_servers(self) -> List[str]:
"""Get list of servers that were installed by SuperClaude"""
try:
metadata = self.settings_manager.get_metadata_setting("components")
if metadata and "mcp" in metadata:
return metadata["mcp"].get("configured_servers", [])
except Exception:
pass
return []
def _is_superclaude_managed_server(self, server_config: Dict, server_name: str) -> bool:
"""Check if a server configuration matches SuperClaude's templates
This helps determine if a server was installed by SuperClaude or manually
configured by the user, allowing us to preserve user customizations.
"""
# Find the server key that maps to this server name
server_key = None
for key, info in self.mcp_servers.items():
if info["name"] == server_name:
server_key = key
break
if not server_key:
return False # Unknown server, don't remove
# Load our template config for comparison
template_config = self._load_mcp_server_config(server_key)
if not template_config or server_name not in template_config:
return False
template_server = template_config[server_name]
# Check if the current config has the same structure as our template
# If user has customized it, the structure might be different
required_keys = {"command", "args"}
# Check if all required keys exist and match our template
for key in required_keys:
if key not in server_config or key not in template_server:
return False
# For command and basic structure, they should match our template
if key == "command" and server_config[key] != template_server[key]:
return False
return True
def get_dependencies(self) -> List[str]:
"""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 size - minimal since we only modify config"""
return 4096 # 4KB - just config modifications
"""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"]
}