kazuki nakai 050d5ea2ab
refactor: PEP8 compliance - directory rename and code formatting (#425)
* fix(orchestration): add WebFetch auto-trigger for infrastructure configuration

Problem: Infrastructure configuration changes (e.g., Traefik port settings)
were being made based on assumptions without consulting official documentation,
violating the 'Evidence > assumptions' principle in PRINCIPLES.md.

Solution:
- Added Infrastructure Configuration Validation section to MODE_Orchestration.md
- Auto-triggers WebFetch for infrastructure tools (Traefik, nginx, Docker, etc.)
- Enforces MODE_DeepResearch activation for investigation
- BLOCKS assumption-based configuration changes

Testing: Verified WebFetch successfully retrieves Traefik official docs (port 80 default)

This prevents production outages from infrastructure misconfiguration by ensuring
all technical recommendations are backed by official documentation.

* feat: Add PM Agent (Project Manager Agent) for seamless orchestration

Introduces PM Agent as the default orchestration layer that coordinates
all sub-agents and manages workflows automatically.

Key Features:
- Default orchestration: All user interactions handled by PM Agent
- Auto-delegation: Intelligent sub-agent selection based on task analysis
- Docker Gateway integration: Zero-token baseline with dynamic MCP loading
- Self-improvement loop: Automatic documentation of patterns and mistakes
- Optional override: Users can specify sub-agents explicitly if desired

Architecture:
- Agent spec: SuperClaude/Agents/pm-agent.md
- Command: SuperClaude/Commands/pm.md
- Updated docs: README.md (15→16 agents), agents.md (new Orchestration category)

User Experience:
- Default: PM Agent handles everything (seamless, no manual routing)
- Optional: Explicit --agent flag for direct sub-agent access
- Both modes available simultaneously (no user downside)

Implementation Status:
-  Specification complete
-  Documentation complete
-  Prototype implementation needed
-  Docker Gateway integration needed
-  Testing and validation needed

Refs: kazukinakai/docker-mcp-gateway (IRIS MCP Gateway integration)

* feat: Add Agent Orchestration rules for PM Agent default activation

Implements PM Agent as the default orchestration layer in RULES.md.

Key Changes:
- New 'Agent Orchestration' section (CRITICAL priority)
- PM Agent receives ALL user requests by default
- Manual override with @agent-[name] bypasses PM Agent
- Agent Selection Priority clearly defined:
  1. Manual override → Direct routing
  2. Default → PM Agent → Auto-delegation
  3. Delegation based on keywords, file types, complexity, context

User Experience:
- Default: PM Agent handles everything (seamless)
- Override: @agent-[name] for direct specialist access
- Transparent: PM Agent reports delegation decisions

This establishes PM Agent as the orchestration layer while
respecting existing auto-activation patterns and manual overrides.

Next Steps:
- Local testing in agiletec project
- Iteration based on actual behavior
- Documentation updates as needed

* refactor(pm-agent): redesign as self-improvement meta-layer

Problem Resolution:
PM Agent's initial design competed with existing auto-activation for task routing,
creating confusion about orchestration responsibilities and adding unnecessary complexity.

Design Change:
Redefined PM Agent as a meta-layer agent that operates AFTER specialist agents
complete tasks, focusing on:
- Post-implementation documentation and pattern recording
- Immediate mistake analysis with prevention checklists
- Monthly documentation maintenance and noise reduction
- Pattern extraction and knowledge synthesis

Two-Layer Orchestration System:
1. Task Execution Layer: Existing auto-activation handles task routing (unchanged)
2. Self-Improvement Layer: PM Agent meta-layer handles documentation (new)

Files Modified:
- SuperClaude/Agents/pm-agent.md: Complete rewrite with meta-layer design
  - Category: orchestration → meta
  - Triggers: All user interactions → Post-implementation, mistakes, monthly
  - Behavioral Mindset: Continuous learning system
  - Self-Improvement Workflow: BEFORE/DURING/AFTER/MISTAKE RECOVERY/MAINTENANCE

- SuperClaude/Core/RULES.md: Agent Orchestration section updated
  - Split into Task Execution Layer + Self-Improvement Layer
  - Added orchestration flow diagram
  - Clarified PM Agent activates AFTER task completion

- README.md: Updated PM Agent description
  - "orchestrates all interactions" → "ensures continuous learning"

- Docs/User-Guide/agents.md: PM Agent section rewritten
  - Section: Orchestration Agent → Meta-Layer Agent
  - Expertise: Project orchestration → Self-improvement workflow executor
  - Examples: Task coordination → Post-implementation documentation

- PR_DOCUMENTATION.md: Comprehensive PR documentation added
  - Summary, motivation, changes, testing, breaking changes
  - Two-layer orchestration system diagram
  - Verification checklist

Integration Validated:
Tested with agiletec project's self-improvement-workflow.md:
 PM Agent aligns with existing BEFORE/DURING/AFTER/MISTAKE RECOVERY phases
 Complements (not competes with) existing workflow
 agiletec workflow defines WHAT, PM Agent defines WHO executes it

Breaking Changes: None
- Existing auto-activation continues unchanged
- Specialist agents unaffected
- User workflows remain the same
- New capability: Automatic documentation and knowledge maintenance

Value Proposition:
Transforms SuperClaude into a continuously learning system that accumulates
knowledge, prevents recurring mistakes, and maintains fresh documentation
without manual intervention.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: add Claude Code conversation history management research

Research covering .jsonl file structure, performance impact, and retention policies.

Content:
- Claude Code .jsonl file format and message types
- Performance issues from GitHub (memory leaks, conversation compaction)
- Retention policies (consumer vs enterprise)
- Rotation recommendations based on actual data
- File history snapshot tracking mechanics

Source: Moved from agiletec project (research applicable to all Claude Code projects)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: add Development documentation structure

Phase 1: Documentation Structure complete

- Add Docs/Development/ directory for development documentation
- Add ARCHITECTURE.md - System architecture with PM Agent meta-layer
- Add ROADMAP.md - 5-phase development plan with checkboxes
- Add TASKS.md - Daily task tracking with progress indicators
- Add PROJECT_STATUS.md - Current status dashboard and metrics
- Add pm-agent-integration.md - Implementation guide for PM Agent mode

This establishes comprehensive documentation foundation for:
- System architecture understanding
- Development planning and tracking
- Implementation guidance
- Progress visibility

Related: #pm-agent-mode #documentation #phase-1

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: PM Agent session lifecycle and PDCA implementation

Phase 2: PM Agent Mode Integration (Design Phase)

Commands/pm.md updates:
- Add "Always-Active Foundation Layer" concept
- Add Session Lifecycle (Session Start/During Work/Session End)
- Add PDCA Cycle (Plan/Do/Check/Act) automation
- Add Serena MCP Memory Integration (list/read/write_memory)
- Document auto-activation triggers

Agents/pm-agent.md updates:
- Add Session Start Protocol (MANDATORY auto-activation)
- Add During Work PDCA Cycle with example workflows
- Add Session End Protocol with state preservation
- Add PDCA Self-Evaluation Pattern
- Add Documentation Strategy (temp → patterns/mistakes)
- Add Memory Operations Reference

Key Features:
- Session start auto-activation for context restoration
- 30-minute checkpoint saves during work
- Self-evaluation with think_about_* operations
- Systematic documentation lifecycle
- Knowledge evolution to CLAUDE.md

Implementation Status:
-  Design complete (Commands/pm.md, Agents/pm-agent.md)
-  Implementation pending (Core components)
-  Serena MCP integration pending

Salvaged from mistaken development in ~/.claude directory

Related: #pm-agent-mode #session-lifecycle #pdca-cycle #phase-2

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: disable Serena MCP auto-browser launch

Disable web dashboard and GUI log window auto-launch in Serena MCP server
to prevent intrusive browser popups on startup. Users can still manually
access the dashboard at http://localhost:24282/dashboard/ if needed.

Changes:
- Add CLI flags to Serena run command:
  - --enable-web-dashboard false
  - --enable-gui-log-window false
- Ensures Git-tracked configuration (no reliance on ~/.serena/serena_config.yml)
- Aligns with AIRIS MCP Gateway integration approach

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: rename directories to lowercase for PEP8 compliance

- Rename superclaude/Agents -> superclaude/agents
- Rename superclaude/Commands -> superclaude/commands
- Rename superclaude/Core -> superclaude/core
- Rename superclaude/Examples -> superclaude/examples
- Rename superclaude/MCP -> superclaude/mcp
- Rename superclaude/Modes -> superclaude/modes

This change follows Python PEP8 naming conventions for package directories.

* style: fix PEP8 violations and update package name to lowercase

Changes:
- Format all Python files with black (43 files reformatted)
- Update package name from 'SuperClaude' to 'superclaude' in pyproject.toml
- Fix import statements to use lowercase package name
- Add missing imports (timedelta, __version__)
- Remove old SuperClaude.egg-info directory

PEP8 violations reduced from 2672 to 701 (mostly E501 line length due to black's 88 char vs flake8's 79 char limit).

* docs: add PM Agent development documentation

Add comprehensive PM Agent development documentation:
- PM Agent ideal workflow (7-phase autonomous cycle)
- Project structure understanding (Git vs installed environment)
- Installation flow understanding (CommandsComponent behavior)
- Task management system (current-tasks.md)

Purpose: Eliminate repeated explanations and enable autonomous PDCA cycles

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(pm-agent): add self-correcting execution and warning investigation culture

## Changes

### superclaude/commands/pm.md
- Add "Self-Correcting Execution" section with root cause analysis protocol
- Add "Warning/Error Investigation Culture" section enforcing zero-tolerance for dismissal
- Define error detection protocol: STOP → Investigate → Hypothesis → Different Solution → Execute
- Document anti-patterns (retry without understanding) and correct patterns (research-first)

### docs/Development/hypothesis-pm-autonomous-enhancement-2025-10-14.md
- Add PDCA workflow hypothesis document for PM Agent autonomous enhancement

## Rationale

PM Agent must never retry failed operations without understanding root causes.
All warnings and errors require investigation via context7/WebFetch/documentation
to ensure production-quality code and prevent technical debt accumulation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(installer): add airis-mcp-gateway MCP server option

## Changes

- Add airis-mcp-gateway to MCP server options in installer
- Configuration: GitHub-based installation via uvx
- Repository: https://github.com/oraios/airis-mcp-gateway
- Purpose: Dynamic MCP Gateway for zero-token baseline and on-demand tool loading

## Implementation

Added to setup/components/mcp.py self.mcp_servers dictionary with:
- install_method: github
- install_command: uvx test installation
- run_command: uvx runtime execution
- required: False (optional server)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: kazuki <kazuki@kazukinoMacBook-Air.local>
Co-authored-by: Claude <noreply@anthropic.com>
2025-10-14 08:47:09 +05:30

1105 lines
43 KiB
Python

"""
MCP component for MCP server integration
"""
import os
import platform
import shlex
import subprocess
import sys
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple
from setup import __version__
from ..core.base import Component
from ..utils.ui import display_info, display_warning
class MCPComponent(Component):
"""MCP servers integration component"""
def __init__(self, install_dir: Optional[Path] = None):
"""Initialize MCP component"""
super().__init__(install_dir)
self.installed_servers_in_session: List[str] = []
# Define MCP servers to install
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",
"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",
"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",
"npm_package": "@playwright/mcp@latest",
"required": False,
},
"serena": {
"name": "serena",
"description": "Semantic code analysis and intelligent editing",
"install_method": "github",
"install_command": "uvx --from git+https://github.com/oraios/serena serena --help",
"run_command": "uvx --from git+https://github.com/oraios/serena serena start-mcp-server --context ide-assistant --enable-web-dashboard false --enable-gui-log-window false",
"required": False,
},
"morphllm-fast-apply": {
"name": "morphllm-fast-apply",
"description": "Fast Apply capability for context-aware code modifications",
"npm_package": "@morph-llm/morph-fast-apply",
"required": False,
"api_key_env": "MORPH_API_KEY",
"api_key_description": "Morph API key for Fast Apply",
},
"tavily": {
"name": "tavily",
"description": "Web search and real-time information retrieval for deep research",
"install_method": "npm",
"install_command": "npx -y tavily-mcp@0.1.2",
"required": False,
"api_key_env": "TAVILY_API_KEY",
"api_key_description": "Tavily API key for web search (get from https://app.tavily.com)",
},
"chrome-devtools": {
"name": "chrome-devtools",
"description": "Chrome DevTools debugging and performance analysis",
"install_method": "npm",
"install_command": "npx -y chrome-devtools-mcp@latest",
"required": False,
},
"airis-mcp-gateway": {
"name": "airis-mcp-gateway",
"description": "Dynamic MCP Gateway for zero-token baseline and on-demand tool loading",
"install_method": "github",
"install_command": "uvx --from git+https://github.com/oraios/airis-mcp-gateway airis-mcp-gateway --help",
"run_command": "uvx --from git+https://github.com/oraios/airis-mcp-gateway airis-mcp-gateway",
"required": False,
},
}
def get_metadata(self) -> Dict[str, str]:
"""Get component metadata"""
return {
"name": "mcp",
"version": __version__,
"description": "MCP server integration (Context7, Sequential, Magic, Playwright)",
"category": "integration",
}
def is_reinstallable(self) -> bool:
"""This component manages sub-components (servers) and should be re-run."""
return True
def _run_command_cross_platform(
self, cmd: List[str], **kwargs
) -> subprocess.CompletedProcess:
"""
Run a command with proper cross-platform shell handling.
Args:
cmd: Command as list of strings
**kwargs: Additional subprocess.run arguments
Returns:
CompletedProcess result
"""
if platform.system() == "Windows":
# On Windows, wrap command in 'cmd /c' to properly handle commands like npx
cmd = ["cmd", "/c"] + cmd
return subprocess.run(cmd, **kwargs)
else:
# macOS/Linux: Use string format with proper shell to support aliases
cmd_str = " ".join(shlex.quote(str(arg)) for arg in cmd)
# Use the user's shell to execute the command, supporting aliases
user_shell = os.environ.get("SHELL", "/bin/bash")
return subprocess.run(
cmd_str, shell=True, env=os.environ, executable=user_shell, **kwargs
)
def validate_prerequisites(
self, installSubPath: Optional[Path] = None
) -> Tuple[bool, List[str]]:
"""Check prerequisites"""
errors = []
# Check if Node.js is available
try:
result = self._run_command_cross_platform(
["node", "--version"], capture_output=True, text=True, timeout=10
)
if result.returncode != 0:
errors.append("Node.js not found - required for MCP servers")
else:
version = result.stdout.strip()
self.logger.debug(f"Found Node.js {version}")
# Check version (require 18+)
try:
version_num = int(version.lstrip("v").split(".")[0])
if version_num < 18:
errors.append(
f"Node.js version {version} found, but version 18+ required"
)
except:
self.logger.warning(f"Could not parse Node.js version: {version}")
except (subprocess.TimeoutExpired, FileNotFoundError):
errors.append("Node.js not found - required for MCP servers")
# Check if Claude CLI is available
try:
result = self._run_command_cross_platform(
["claude", "--version"], capture_output=True, text=True, timeout=10
)
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 = self._run_command_cross_platform(
["npm", "--version"], capture_output=True, text=True, timeout=10
)
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 uv is available (required for Serena)
try:
result = self._run_command_cross_platform(
["uv", "--version"], capture_output=True, text=True, timeout=10
)
if result.returncode != 0:
self.logger.warning(
"uv not found - required for Serena MCP server installation"
)
else:
version = result.stdout.strip()
self.logger.debug(f"Found uv {version}")
except (subprocess.TimeoutExpired, FileNotFoundError):
self.logger.warning(
"uv not found - required for Serena MCP server installation"
)
return len(errors) == 0, errors
def get_files_to_install(self) -> List[Tuple[Path, Path]]:
"""Get files to install (none for MCP component)"""
return []
def get_metadata_modifications(self) -> Dict[str, Any]:
"""Get metadata modifications for MCP component"""
return {
"components": {
"mcp": {
"version": __version__,
"installed": True,
"servers_count": len(self.installed_servers_in_session),
}
},
"mcp": {
"enabled": True,
"servers": self.installed_servers_in_session,
"auto_update": False,
},
}
def _install_uv_mcp_server(
self, server_info: Dict[str, Any], config: Dict[str, Any]
) -> bool:
"""Install a single MCP server using uv"""
server_name = server_info["name"]
install_command = server_info.get("install_command")
run_command = server_info.get("run_command")
if not install_command:
self.logger.error(
f"No install_command found for uv-based server {server_name}"
)
return False
if not run_command:
self.logger.error(f"No run_command found for uv-based server {server_name}")
return False
try:
self.logger.info(f"Installing MCP server using uv: {server_name}")
if self._check_mcp_server_installed(server_name):
self.logger.info(f"MCP server {server_name} already installed")
return True
# Check if uv is available
try:
uv_check = self._run_command_cross_platform(
["uv", "--version"], capture_output=True, text=True, timeout=10
)
if uv_check.returncode != 0:
self.logger.error(
f"uv not found - required for {server_name} installation"
)
return False
except (subprocess.TimeoutExpired, FileNotFoundError):
self.logger.error(
f"uv not found - required for {server_name} installation"
)
return False
if config.get("dry_run"):
self.logger.info(
f"Would install MCP server (user scope): {install_command}"
)
self.logger.info(
f"Would register MCP server run command: {run_command}"
)
return True
# Run install command
self.logger.debug(f"Running: {install_command}")
cmd_parts = shlex.split(install_command)
result = self._run_command_cross_platform(
cmd_parts, capture_output=True, text=True, timeout=900 # 15 minutes
)
if result.returncode == 0:
self.logger.success(
f"Successfully installed MCP server (user scope): {server_name}"
)
# For Serena, we need to handle the run command specially
if server_name == "serena":
# Serena needs project-specific registration, use current working directory
current_dir = os.getcwd()
serena_run_cmd = (
f"{run_command} --project {shlex.quote(current_dir)}"
)
self.logger.info(
f"Registering {server_name} with Claude CLI for project: {current_dir}"
)
reg_cmd = [
"claude",
"mcp",
"add",
"-s",
"user",
"--",
server_name,
] + shlex.split(serena_run_cmd)
else:
self.logger.info(
f"Registering {server_name} with Claude CLI. Run command: {run_command}"
)
reg_cmd = [
"claude",
"mcp",
"add",
"-s",
"user",
"--",
server_name,
] + shlex.split(run_command)
reg_result = self._run_command_cross_platform(
reg_cmd, capture_output=True, text=True, timeout=120
)
if reg_result.returncode == 0:
self.logger.success(
f"Successfully registered {server_name} with Claude CLI."
)
return True
else:
error_msg = (
reg_result.stderr.strip()
if reg_result.stderr
else "Unknown error"
)
self.logger.error(
f"Failed to register MCP server {server_name} with Claude CLI: {error_msg}"
)
return False
else:
error_msg = result.stderr.strip() if result.stderr else "Unknown error"
self.logger.error(
f"Failed to install MCP server {server_name} using uv: {error_msg}\n{result.stdout}"
)
return False
except subprocess.TimeoutExpired:
self.logger.error(f"Timeout installing MCP server {server_name} using uv")
return False
except Exception as e:
self.logger.error(
f"Error installing MCP server {server_name} using uv: {e}"
)
return False
def _install_github_mcp_server(
self, server_info: Dict[str, Any], config: Dict[str, Any]
) -> bool:
"""Install a single MCP server from GitHub using uvx"""
server_name = server_info["name"]
install_command = server_info.get("install_command")
run_command = server_info.get("run_command")
if not install_command:
self.logger.error(
f"No install_command found for GitHub-based server {server_name}"
)
return False
if not run_command:
self.logger.error(
f"No run_command found for GitHub-based server {server_name}"
)
return False
try:
self.logger.info(f"Installing MCP server from GitHub: {server_name}")
if self._check_mcp_server_installed(server_name):
self.logger.info(f"MCP server {server_name} already installed")
return True
# Check if uvx is available
try:
uvx_check = self._run_command_cross_platform(
["uvx", "--version"], capture_output=True, text=True, timeout=10
)
if uvx_check.returncode != 0:
self.logger.error(
f"uvx not found - required for {server_name} installation"
)
return False
except (subprocess.TimeoutExpired, FileNotFoundError):
self.logger.error(
f"uvx not found - required for {server_name} installation"
)
return False
if config.get("dry_run"):
self.logger.info(
f"Would install MCP server from GitHub: {install_command}"
)
self.logger.info(
f"Would register MCP server run command: {run_command}"
)
return True
# Run install command to test the GitHub installation
self.logger.debug(f"Testing GitHub installation: {install_command}")
cmd_parts = shlex.split(install_command)
result = self._run_command_cross_platform(
cmd_parts,
capture_output=True,
text=True,
timeout=300, # 5 minutes for GitHub clone and build
)
if result.returncode == 0:
self.logger.success(
f"Successfully tested GitHub MCP server: {server_name}"
)
# Register with Claude CLI using the run command
self.logger.info(
f"Registering {server_name} with Claude CLI. Run command: {run_command}"
)
reg_cmd = [
"claude",
"mcp",
"add",
"-s",
"user",
"--",
server_name,
] + shlex.split(run_command)
reg_result = self._run_command_cross_platform(
reg_cmd, capture_output=True, text=True, timeout=120
)
if reg_result.returncode == 0:
self.logger.success(
f"Successfully registered {server_name} with Claude CLI."
)
return True
else:
error_msg = (
reg_result.stderr.strip()
if reg_result.stderr
else "Unknown error"
)
self.logger.error(
f"Failed to register MCP server {server_name} with Claude CLI: {error_msg}"
)
return False
else:
error_msg = result.stderr.strip() if result.stderr else "Unknown error"
self.logger.error(
f"Failed to install MCP server {server_name} from GitHub: {error_msg}\n{result.stdout}"
)
return False
except subprocess.TimeoutExpired:
self.logger.error(
f"Timeout installing MCP server {server_name} from GitHub"
)
return False
except Exception as e:
self.logger.error(
f"Error installing MCP server {server_name} from GitHub: {e}"
)
return False
def _check_mcp_server_installed(self, server_name: str) -> bool:
"""Check if MCP server is already installed"""
try:
result = self._run_command_cross_platform(
["claude", "mcp", "list"], capture_output=True, text=True, timeout=60
)
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 _detect_existing_mcp_servers_from_config(self) -> List[str]:
"""Detect existing MCP servers from Claude Desktop config"""
detected_servers = []
try:
# Try to find Claude Desktop config file
config_paths = [
self.install_dir / "claude_desktop_config.json",
Path.home() / ".claude" / "claude_desktop_config.json",
Path.home() / ".claude.json", # Claude CLI config
Path.home()
/ "AppData"
/ "Roaming"
/ "Claude"
/ "claude_desktop_config.json", # Windows
Path.home()
/ "Library"
/ "Application Support"
/ "Claude"
/ "claude_desktop_config.json", # macOS
]
config_file = None
for path in config_paths:
if path.exists():
config_file = path
break
if not config_file:
self.logger.debug("No Claude Desktop config file found")
return detected_servers
import json
with open(config_file, "r") as f:
config = json.load(f)
# Extract MCP server names from mcpServers section
mcp_servers = config.get("mcpServers", {})
for server_name in mcp_servers.keys():
# Map common name variations to our standard names
normalized_name = self._normalize_server_name(server_name)
if normalized_name and normalized_name in self.mcp_servers:
detected_servers.append(normalized_name)
if detected_servers:
self.logger.info(
f"Detected existing MCP servers from config: {detected_servers}"
)
except Exception as e:
self.logger.warning(f"Could not read Claude Desktop config: {e}")
return detected_servers
def _detect_existing_mcp_servers_from_cli(self) -> List[str]:
"""Detect existing MCP servers from Claude CLI"""
detected_servers = []
try:
result = self._run_command_cross_platform(
["claude", "mcp", "list"], capture_output=True, text=True, timeout=60
)
if result.returncode != 0:
self.logger.debug(f"Could not list MCP servers: {result.stderr}")
return detected_servers
# Parse the output to extract server names
output_lines = result.stdout.strip().split("\n")
for line in output_lines:
line = line.strip().lower()
if line and not line.startswith("#") and not line.startswith("no"):
# Extract server name (usually the first word or before first space/colon)
server_name = line.split()[0] if line.split() else ""
normalized_name = self._normalize_server_name(server_name)
if normalized_name and normalized_name in self.mcp_servers:
detected_servers.append(normalized_name)
if detected_servers:
self.logger.info(
f"Detected existing MCP servers from CLI: {detected_servers}"
)
except Exception as e:
self.logger.warning(f"Could not detect existing MCP servers from CLI: {e}")
return detected_servers
def _normalize_server_name(self, server_name: str) -> Optional[str]:
"""Normalize server name to match our internal naming"""
if not server_name:
return None
server_name = server_name.lower().strip()
# Map common variations to our standard names
name_mappings = {
"context7": "context7",
"sequential-thinking": "sequential-thinking",
"sequential": "sequential-thinking",
"magic": "magic",
"playwright": "playwright",
"serena": "serena",
"morphllm": "morphllm-fast-apply",
"morphllm-fast-apply": "morphllm-fast-apply",
"morph": "morphllm-fast-apply",
}
return name_mappings.get(server_name)
def _merge_server_lists(
self,
existing_servers: List[str],
selected_servers: List[str],
previous_servers: List[str],
) -> List[str]:
"""Merge existing, selected, and previously installed servers"""
all_servers = set()
# Add all detected servers
all_servers.update(existing_servers)
all_servers.update(selected_servers)
all_servers.update(previous_servers)
# Filter to only include servers we know how to install
valid_servers = [s for s in all_servers if s in self.mcp_servers]
if valid_servers:
self.logger.info(f"Total servers to manage: {valid_servers}")
if existing_servers:
self.logger.info(f" - Existing: {existing_servers}")
if selected_servers:
self.logger.info(f" - Newly selected: {selected_servers}")
if previous_servers:
self.logger.info(f" - Previously installed: {previous_servers}")
return valid_servers
def _install_mcp_server(
self, server_info: Dict[str, Any], config: Dict[str, Any]
) -> bool:
"""Install a single MCP server"""
if server_info.get("install_method") == "uv":
return self._install_uv_mcp_server(server_info, config)
elif server_info.get("install_method") == "github":
return self._install_github_mcp_server(server_info, config)
server_name = server_info["name"]
npm_package = server_info.get("npm_package")
install_command = server_info.get("install_command")
if not npm_package and not install_command:
self.logger.error(
f"No npm_package or install_command found for server {server_name}"
)
return False
command = "npx"
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 install_command:
# Use the full install command (e.g., for tavily-mcp@0.1.2)
install_args = install_command.split()
if config.get("dry_run"):
self.logger.info(
f"Would install MCP server (user scope): claude mcp add -s user {server_name} {' '.join(install_args)}"
)
return True
self.logger.debug(
f"Running: claude mcp add -s user {server_name} {' '.join(install_args)}"
)
result = self._run_command_cross_platform(
["claude", "mcp", "add", "-s", "user", "--", server_name]
+ install_args,
capture_output=True,
text=True,
timeout=120, # 2 minutes timeout for installation
)
else:
# Use npm_package
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 = self._run_command_cross_platform(
[
"claude",
"mcp",
"add",
"-s",
"user",
"--",
server_name,
command,
"-y",
npm_package,
],
capture_output=True,
text=True,
timeout=120, # 2 minutes timeout for installation
)
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"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 = self._run_command_cross_platform(
["claude", "mcp", "remove", server_name],
capture_output=True,
text=True,
timeout=60,
)
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 with auto-detection of existing servers"""
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
# Auto-detect existing servers
self.logger.info("Auto-detecting existing MCP servers...")
existing_from_config = self._detect_existing_mcp_servers_from_config()
existing_from_cli = self._detect_existing_mcp_servers_from_cli()
existing_servers = list(set(existing_from_config + existing_from_cli))
# Get selected servers from config
selected_servers = config.get("selected_mcp_servers", [])
# Get previously installed servers from metadata
previous_servers = self.settings_manager.get_metadata_setting("mcp.servers", [])
# Merge all server lists
all_servers = self._merge_server_lists(
existing_servers, selected_servers, previous_servers
)
if not all_servers:
self.logger.info(
"No MCP servers detected or selected. Skipping MCP installation."
)
# Still run post-install to update metadata
return self._post_install()
self.logger.info(f"Managing MCP servers: {', '.join(all_servers)}")
# Install/verify each server
installed_count = 0
failed_servers = []
verified_servers = []
for server_name in all_servers:
if server_name in self.mcp_servers:
server_info = self.mcp_servers[server_name]
# Check if already installed and working
if self._check_mcp_server_installed(server_name):
self.logger.info(
f"MCP server {server_name} already installed and working"
)
installed_count += 1
verified_servers.append(server_name)
else:
# Try to install
if self._install_mcp_server(server_info, config):
installed_count += 1
verified_servers.append(server_name)
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
else:
self.logger.warning(
f"Unknown MCP server '{server_name}' cannot be managed by SuperClaude"
)
# Update the list of successfully managed servers
self.installed_servers_in_session = verified_servers
# Verify installation
if not config.get("dry_run", False):
self.logger.info("Verifying MCP server installation...")
try:
result = self._run_command_cross_platform(
["claude", "mcp", "list"],
capture_output=True,
text=True,
timeout=60,
)
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 managed ({installed_count} servers)"
)
else:
self.logger.success(
f"MCP component successfully managing ({installed_count} servers)"
)
return self._post_install()
def _post_install(self) -> bool:
"""Post-installation tasks"""
# Update metadata
try:
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": __version__,
"category": "integration",
"servers_count": len(self.installed_servers_in_session),
"installed_servers": self.installed_servers_in_session,
},
)
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
def uninstall(self) -> bool:
"""Uninstall MCP component"""
try:
self.logger.info("Uninstalling SuperClaude MCP servers...")
# Uninstall each MCP server
uninstalled_count = 0
for server_name in self.mcp_servers.keys():
if self._uninstall_mcp_server(server_name):
uninstalled_count += 1
# Update metadata to remove MCP component
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")
except Exception as e:
self.logger.warning(f"Could not update metadata: {e}")
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_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 and validate installed servers
try:
result = self._run_command_cross_platform(
["claude", "mcp", "list"], capture_output=True, text=True, timeout=60
)
if result.returncode != 0:
errors.append(
"Could not communicate with Claude CLI for MCP server verification"
)
else:
claude_mcp_output = result.stdout.lower()
# Get the list of servers that should be installed from metadata
installed_servers = self.settings_manager.get_metadata_setting(
"mcp.servers", []
)
for server_name in installed_servers:
if server_name.lower() not in claude_mcp_output:
errors.append(
f"Installed MCP server '{server_name}' not found in 'claude mcp list' output."
)
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"],
}