fix: mcp cross-platform shell handling and rename morphllm (#369)

fix: mcp cross-platform shell handling and rename morphllm to morphllm-fast-apply
This commit is contained in:
Xingxing Wang
2025-09-18 19:39:52 +12:00
committed by GitHub
parent 99e7a01e2c
commit a7a0a180cd

View File

@@ -2,15 +2,19 @@
MCP component for MCP server integration MCP component for MCP server integration
""" """
import os
import platform
import shlex
import subprocess import subprocess
import sys import sys
from typing import Dict, List, Tuple, Optional, Any
from pathlib import Path from pathlib import Path
import shlex from typing import Any, Dict, List, Optional, Tuple
from setup import __version__
from ..core.base import Component from ..core.base import Component
from ..utils.ui import display_info, display_warning from ..utils.ui import display_info, display_warning
from setup import __version__
class MCPComponent(Component): class MCPComponent(Component):
"""MCP servers integration component""" """MCP servers integration component"""
@@ -56,7 +60,7 @@ class MCPComponent(Component):
"run_command": "serena start-mcp-server --context ide-assistant --project $(pwd)", "run_command": "serena start-mcp-server --context ide-assistant --project $(pwd)",
"required": False "required": False
}, },
"morphllm": { "morphllm-fast-apply": {
"name": "morphllm-fast-apply", "name": "morphllm-fast-apply",
"description": "Fast Apply capability for context-aware code modifications", "description": "Fast Apply capability for context-aware code modifications",
"npm_package": "@morph-llm/morph-fast-apply", "npm_package": "@morph-llm/morph-fast-apply",
@@ -79,18 +83,42 @@ class MCPComponent(Component):
"""This component manages sub-components (servers) and should be re-run.""" """This component manages sub-components (servers) and should be re-run."""
return True 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":
# Windows: Use list format with shell=True
return subprocess.run(cmd, shell=True, **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 with interactive mode to load aliases
user_shell = os.environ.get('SHELL', '/bin/bash')
# Execute command with user's shell in interactive mode to load aliases
full_cmd = f"{user_shell} -i -c {shlex.quote(cmd_str)}"
return subprocess.run(full_cmd, shell=True, env=os.environ, **kwargs)
def validate_prerequisites(self, installSubPath: Optional[Path] = None) -> Tuple[bool, List[str]]: def validate_prerequisites(self, installSubPath: Optional[Path] = None) -> Tuple[bool, List[str]]:
"""Check prerequisites""" """Check prerequisites"""
errors = [] errors = []
# Check if Node.js is available # Check if Node.js is available
try: try:
result = subprocess.run( result = self._run_command_cross_platform(
["node", "--version"], ["node", "--version"],
capture_output=True, capture_output=True,
text=True, text=True,
timeout=10, timeout=10
shell=(sys.platform == "win32")
) )
if result.returncode != 0: if result.returncode != 0:
errors.append("Node.js not found - required for MCP servers") errors.append("Node.js not found - required for MCP servers")
@@ -110,12 +138,11 @@ class MCPComponent(Component):
# Check if Claude CLI is available # Check if Claude CLI is available
try: try:
result = subprocess.run( result = self._run_command_cross_platform(
["claude", "--version"], ["claude", "--version"],
capture_output=True, capture_output=True,
text=True, text=True,
timeout=10, timeout=10
shell=(sys.platform == "win32")
) )
if result.returncode != 0: if result.returncode != 0:
errors.append("Claude CLI not found - required for MCP server management") errors.append("Claude CLI not found - required for MCP server management")
@@ -127,12 +154,11 @@ class MCPComponent(Component):
# Check if npm is available # Check if npm is available
try: try:
result = subprocess.run( result = self._run_command_cross_platform(
["npm", "--version"], ["npm", "--version"],
capture_output=True, capture_output=True,
text=True, text=True,
timeout=10, timeout=10
shell=(sys.platform == "win32")
) )
if result.returncode != 0: if result.returncode != 0:
errors.append("npm not found - required for MCP server installation") errors.append("npm not found - required for MCP server installation")
@@ -193,24 +219,22 @@ class MCPComponent(Component):
# Run install command # Run install command
self.logger.debug(f"Running: {install_command}") self.logger.debug(f"Running: {install_command}")
cmd_parts = shlex.split(install_command) cmd_parts = shlex.split(install_command)
result = subprocess.run( result = self._run_command_cross_platform(
cmd_parts, cmd_parts,
capture_output=True, capture_output=True,
text=True, text=True,
timeout=900, # 15 minutes timeout=900 # 15 minutes
shell=(sys.platform == "win32")
) )
if result.returncode == 0: if result.returncode == 0:
self.logger.success(f"Successfully installed MCP server (user scope): {server_name}") self.logger.success(f"Successfully installed MCP server (user scope): {server_name}")
self.logger.info(f"Registering {server_name} with Claude CLI. Run command: {run_command}") self.logger.info(f"Registering {server_name} with Claude CLI. Run command: {run_command}")
reg_result = subprocess.run( reg_result = self._run_command_cross_platform(
["claude", "mcp", "add", "-s", "user", "--", server_name] + shlex.split(run_command), ["claude", "mcp", "add", "-s", "user", "--", server_name] + shlex.split(run_command),
capture_output=True, capture_output=True,
text=True, text=True,
timeout=120, timeout=120
shell=(sys.platform == "win32")
) )
if reg_result.returncode == 0: if reg_result.returncode == 0:
@@ -235,12 +259,11 @@ class MCPComponent(Component):
def _check_mcp_server_installed(self, server_name: str) -> bool: def _check_mcp_server_installed(self, server_name: str) -> bool:
"""Check if MCP server is already installed""" """Check if MCP server is already installed"""
try: try:
result = subprocess.run( result = self._run_command_cross_platform(
["claude", "mcp", "list"], ["claude", "mcp", "list"],
capture_output=True, capture_output=True,
text=True, text=True,
timeout=60, timeout=60
shell=(sys.platform == "win32")
) )
if result.returncode != 0: if result.returncode != 0:
@@ -300,12 +323,11 @@ class MCPComponent(Component):
self.logger.debug(f"Running: claude mcp add -s user {server_name} {command} -y {npm_package}") self.logger.debug(f"Running: claude mcp add -s user {server_name} {command} -y {npm_package}")
result = subprocess.run( result = self._run_command_cross_platform(
["claude", "mcp", "add", "-s", "user", "--", server_name, command, "-y", npm_package], ["claude", "mcp", "add", "-s", "user", "--", server_name, command, "-y", npm_package],
capture_output=True, capture_output=True,
text=True, text=True,
timeout=120, # 2 minutes timeout for installation timeout=120 # 2 minutes timeout for installation
shell=(sys.platform == "win32")
) )
if result.returncode == 0: if result.returncode == 0:
@@ -335,12 +357,11 @@ class MCPComponent(Component):
self.logger.debug(f"Running: claude mcp remove {server_name} (auto-detect scope)") self.logger.debug(f"Running: claude mcp remove {server_name} (auto-detect scope)")
result = subprocess.run( result = self._run_command_cross_platform(
["claude", "mcp", "remove", server_name], ["claude", "mcp", "remove", server_name],
capture_output=True, capture_output=True,
text=True, text=True,
timeout=60, timeout=60
shell=(sys.platform == "win32")
) )
if result.returncode == 0: if result.returncode == 0:
@@ -403,12 +424,11 @@ class MCPComponent(Component):
if not config.get("dry_run", False): if not config.get("dry_run", False):
self.logger.info("Verifying MCP server installation...") self.logger.info("Verifying MCP server installation...")
try: try:
result = subprocess.run( result = self._run_command_cross_platform(
["claude", "mcp", "list"], ["claude", "mcp", "list"],
capture_output=True, capture_output=True,
text=True, text=True,
timeout=60, timeout=60
shell=(sys.platform == "win32")
) )
if result.returncode == 0: if result.returncode == 0:
@@ -562,12 +582,11 @@ class MCPComponent(Component):
# Check if Claude CLI is available and validate installed servers # Check if Claude CLI is available and validate installed servers
try: try:
result = subprocess.run( result = self._run_command_cross_platform(
["claude", "mcp", "list"], ["claude", "mcp", "list"],
capture_output=True, capture_output=True,
text=True, text=True,
timeout=60, timeout=60
shell=(sys.platform == "win32")
) )
if result.returncode != 0: if result.returncode != 0: