MCP Memory Service
by doobidoo
- mcp-memory-service
- scripts
#!/usr/bin/env python3
"""
Fix script for PyTorch dependency issues.
This script pins PyTorch to the currently installed version and prevents automatic upgrades.
"""
import sys
import os
import subprocess
import pkg_resources
import site
import tempfile
from pathlib import Path
def print_header(text):
"""Print a formatted header."""
print("\n" + "=" * 80)
print(f" {text}")
print("=" * 80)
def print_info(text):
"""Print formatted info text."""
print(f" → {text}")
def print_error(text):
"""Print formatted error text."""
print(f" ❌ ERROR: {text}")
def print_success(text):
"""Print formatted success text."""
print(f" ✅ {text}")
def print_warning(text):
"""Print formatted warning text."""
print(f" ⚠️ {text}")
def get_installed_pytorch_version():
"""Get the currently installed PyTorch version."""
try:
import torch
return torch.__version__
except ImportError:
return None
def create_constraint_file():
"""Create a constraints file to pin PyTorch version."""
torch_version = get_installed_pytorch_version()
if not torch_version:
print_error("PyTorch is not installed, cannot create constraint file")
return None
print_info(f"Creating constraint file for PyTorch {torch_version}")
# Create a temporary constraints file
fd, constraints_path = tempfile.mkstemp(suffix='.txt', prefix='torch_constraints_')
os.close(fd)
with open(constraints_path, 'w') as f:
f.write(f"torch=={torch_version}\n")
f.write(f"torchvision=={torch_version}\n")
f.write(f"torchaudio=={torch_version}\n")
print_success(f"Created constraints file at {constraints_path}")
return constraints_path
def get_installed_package_version(package_name):
"""Get the currently installed version of a package."""
try:
import pkg_resources
return pkg_resources.get_distribution(package_name).version
except (pkg_resources.DistributionNotFound, ImportError):
return None
def pin_pytorch_version(constraints_path):
"""Pin PyTorch version using pip constraints."""
if not constraints_path or not os.path.exists(constraints_path):
print_error("Invalid constraints file path")
return False
# Get the installed PyTorch version
torch_version = get_installed_pytorch_version()
if not torch_version:
print_error("PyTorch is not installed, cannot pin version")
return False
# Get the installed torchvision and torchaudio versions
torchvision_version = get_installed_package_version("torchvision")
torchaudio_version = get_installed_package_version("torchaudio")
print_info(f"Installed versions: torch={torch_version}, torchvision={torchvision_version}, torchaudio={torchaudio_version}")
# Check if version has CUDA suffix
if '+cu' in torch_version:
print_info(f"Detected CUDA-enabled PyTorch version: {torch_version}")
print_info("Using direct index URL approach instead of constraints")
# Extract the CUDA version from the PyTorch version
cuda_suffix = torch_version.split('+')[1] # e.g., 'cu118'
# Use the appropriate index URL
index_url = f"https://download.pytorch.org/whl/{cuda_suffix}"
# Get the base version without the CUDA suffix
torch_base_version = torch_version.split('+')[0] # e.g., '2.6.0'
# Get the base versions for torchvision and torchaudio if they have CUDA suffixes
torchvision_base_version = torchvision_version.split('+')[0] if torchvision_version and '+' in torchvision_version else torchvision_version
torchaudio_base_version = torchaudio_version.split('+')[0] if torchaudio_version and '+' in torchaudio_version else torchaudio_version
print_info(f"Using index URL: {index_url} for PyTorch packages")
try:
# Run pip install with the index URL for torch
cmd = [
sys.executable, '-m', 'pip', 'install',
'--no-deps',
f"torch=={torch_base_version}",
f"--index-url={index_url}"
]
print_info(f"Running: {' '.join(cmd)}")
subprocess.check_call(cmd)
# Install torchvision if available
if torchvision_version:
cmd = [
sys.executable, '-m', 'pip', 'install',
'--no-deps',
f"torchvision=={torchvision_base_version}",
f"--index-url={index_url}"
]
print_info(f"Running: {' '.join(cmd)}")
try:
subprocess.check_call(cmd)
except subprocess.SubprocessError as e:
print_warning(f"Failed to install torchvision: {e}")
print_info("This is not critical, continuing...")
# Install torchaudio if available
if torchaudio_version:
cmd = [
sys.executable, '-m', 'pip', 'install',
'--no-deps',
f"torchaudio=={torchaudio_base_version}",
f"--index-url={index_url}"
]
print_info(f"Running: {' '.join(cmd)}")
try:
subprocess.check_call(cmd)
except subprocess.SubprocessError as e:
print_warning(f"Failed to install torchaudio: {e}")
print_info("This is not critical, continuing...")
print_success("PyTorch packages pinned successfully")
return True
except subprocess.SubprocessError as e:
print_error(f"Failed to pin PyTorch version: {e}")
return False
else:
# For non-CUDA versions, use the constraints file
print_info("Pinning PyTorch version using pip constraints")
try:
# Run pip install with constraints to pin the version
cmd = [
sys.executable, '-m', 'pip', 'install',
'--upgrade',
'--force-reinstall',
'--no-deps',
'--constraint', constraints_path,
'torch', 'torchvision', 'torchaudio'
]
print_info(f"Running: {' '.join(cmd)}")
subprocess.check_call(cmd)
print_success("PyTorch version pinned successfully")
return True
except subprocess.SubprocessError as e:
print_error(f"Failed to pin PyTorch version: {e}")
return False
def create_no_deps_wrapper():
"""Create a wrapper script that prevents automatic dependency installation."""
print_info("Creating no-deps wrapper for pip")
# Get site-packages directory
site_packages = site.getsitepackages()[0]
# Create a directory for the wrapper if it doesn't exist
wrapper_dir = os.path.join(site_packages, 'pip_no_deps_wrapper')
os.makedirs(wrapper_dir, exist_ok=True)
# Create an __init__.py file
with open(os.path.join(wrapper_dir, '__init__.py'), 'w') as f:
f.write("# Pip no-deps wrapper\n")
# Create a wrapper.py file
wrapper_path = os.path.join(wrapper_dir, 'wrapper.py')
with open(wrapper_path, 'w') as f:
f.write("""
# Wrapper to prevent automatic dependency installation
import sys
import os
import importlib.util
import types
class PipNoDepsFinder:
def __init__(self, blocked_packages=None):
self.blocked_packages = blocked_packages or ['torch', 'torchvision', 'torchaudio']
def find_spec(self, fullname, path=None, target=None):
# Block automatic installation of specified packages
if any(fullname.startswith(pkg) for pkg in self.blocked_packages):
# Check if the package is already installed
spec = importlib.util.find_spec(fullname)
if spec is not None:
return spec
# If not installed, print a warning and return None
print(f"WARNING: Blocked automatic installation of {fullname}", file=sys.stderr)
return None
# Let the normal import system handle other packages
return None
# Install the import hook
sys.meta_path.insert(0, PipNoDepsFinder())
# Monkey patch pip to prevent automatic installation
try:
import pip._internal.resolution.resolvelib.factory
original_get_installation_candidate = pip._internal.resolution.resolvelib.factory.Factory.get_installation_requirement
def patched_get_installation_requirement(self, requirement):
# Block torch installations from PyPI
if requirement.name in ['torch', 'torchvision', 'torchaudio']:
print(f"WARNING: Blocked automatic installation of {requirement.name}", file=sys.stderr)
return None
return original_get_installation_candidate(self, requirement)
pip._internal.resolution.resolvelib.factory.Factory.get_installation_requirement = patched_get_installation_requirement
except (ImportError, AttributeError):
pass
print("Pip no-deps wrapper installed", file=sys.stderr)
""")
print_success(f"Created wrapper at {wrapper_path}")
# Create a .pth file to automatically load the wrapper
pth_path = os.path.join(site_packages, 'pip_no_deps_wrapper.pth')
with open(pth_path, 'w') as f:
f.write(f"import pip_no_deps_wrapper.wrapper\n")
print_success(f"Created .pth file at {pth_path}")
return True
def create_sitecustomize():
"""Create a sitecustomize.py file to prevent automatic dependency installation."""
print_info("Creating sitecustomize.py to prevent automatic dependency installation")
# Get site-packages directory
site_packages = site.getsitepackages()[0]
# Create sitecustomize.py
sitecustomize_path = os.path.join(site_packages, 'sitecustomize.py')
# Check if file already exists
if os.path.exists(sitecustomize_path):
print_warning(f"sitecustomize.py already exists at {sitecustomize_path}")
print_info("Creating backup of existing file")
backup_path = sitecustomize_path + '.bak'
os.rename(sitecustomize_path, backup_path)
print_success(f"Backup created at {backup_path}")
# Create new sitecustomize.py
with open(sitecustomize_path, 'w') as f:
f.write("""
# sitecustomize.py to prevent automatic dependency installation
import sys
import os
import importlib.util
# Print debug info
print("sitecustomize.py loaded", file=sys.stderr)
# Create a custom import hook to prevent automatic installation
class PreventAutoInstallImportHook:
def __init__(self):
self.blocked_packages = ['torch', 'torchvision', 'torchaudio']
def find_spec(self, fullname, path, target=None):
if any(fullname.startswith(pkg) for pkg in self.blocked_packages):
# Check if the package is already installed
spec = importlib.util.find_spec(fullname)
if spec is not None:
return spec
# If not installed, print a warning and return None
print(f"WARNING: Blocked automatic installation of {fullname}", file=sys.stderr)
return None
# Return None to let the normal import system handle it
return None
# Register the import hook
sys.meta_path.insert(0, PreventAutoInstallImportHook())
# Set environment variables to prevent pip from installing dependencies
os.environ["PIP_NO_DEPENDENCIES"] = "1"
os.environ["PIP_NO_INSTALL"] = "1"
""")
print_success(f"Created sitecustomize.py at {sitecustomize_path}")
return True
def update_requirements_file():
"""Update requirements.txt to pin PyTorch version."""
torch_version = get_installed_pytorch_version()
if not torch_version:
print_error("PyTorch is not installed, cannot update requirements.txt")
return False
# Get the installed torchvision and torchaudio versions
torchvision_version = get_installed_package_version("torchvision")
torchaudio_version = get_installed_package_version("torchaudio")
print_info(f"Updating requirements.txt to pin PyTorch {torch_version}, torchvision {torchvision_version}, torchaudio {torchaudio_version}")
# Check if version has CUDA suffix
has_cuda_suffix = '+cu' in torch_version
if has_cuda_suffix:
# Extract the base version and CUDA suffix
torch_base_version = torch_version.split('+')[0] # e.g., '2.6.0'
cuda_suffix = torch_version.split('+')[1] # e.g., 'cu118'
print_info(f"Detected CUDA suffix: {cuda_suffix}")
# Get the base versions for torchvision and torchaudio if they have CUDA suffixes
torchvision_base_version = torchvision_version.split('+')[0] if torchvision_version and '+' in torchvision_version else torchvision_version
torchaudio_base_version = torchaudio_version.split('+')[0] if torchaudio_version and '+' in torchaudio_version else torchaudio_version
# Read the current requirements.txt
try:
with open('requirements.txt', 'r') as f:
lines = f.readlines()
# Update or add PyTorch requirements
torch_lines_added = False
new_lines = []
for line in lines:
# Skip commented PyTorch lines
if line.strip().startswith('#') and 'torch' in line:
new_lines.append(line)
# Skip existing torch requirements
elif line.strip().startswith('torch'):
continue
# Skip existing torchvision requirements
elif line.strip().startswith('torchvision'):
continue
# Skip existing torchaudio requirements
elif line.strip().startswith('torchaudio'):
continue
else:
new_lines.append(line)
# Add pinned PyTorch requirements after the comments
if has_cuda_suffix:
# For CUDA versions, add special comments with installation instructions
torch_comment = f"\n# PyTorch requirements with CUDA {cuda_suffix}\n"
torch_comment += f"# These must be installed with: pip install <package>==<version> --index-url=https://download.pytorch.org/whl/{cuda_suffix}\n"
torch_lines = [torch_comment]
# Add torch with correct version
torch_lines.append(f"# torch=={torch_base_version} # Install from {cuda_suffix} index\n")
# Add torchvision with correct version if available
if torchvision_version:
torch_lines.append(f"# torchvision=={torchvision_base_version} # Install from {cuda_suffix} index\n")
# Add torchaudio with correct version if available
if torchaudio_version:
torch_lines.append(f"# torchaudio=={torchaudio_base_version} # Install from {cuda_suffix} index\n")
else:
# For non-CUDA versions, add normal requirements
torch_lines = ["\n# PyTorch requirements\n"]
# Add torch with correct version
torch_lines.append(f"torch=={torch_version}\n")
# Add torchvision with correct version if available
if torchvision_version:
torch_lines.append(f"torchvision=={torchvision_version}\n")
# Add torchaudio with correct version if available
if torchaudio_version:
torch_lines.append(f"torchaudio=={torchaudio_version}\n")
# Add the torch lines at the appropriate location
for i, line in enumerate(new_lines):
if line.strip().startswith('#') and 'PyTorch' in line and not torch_lines_added:
# Add pinned requirements after the comment
new_lines[i+1:i+1] = torch_lines
torch_lines_added = True
break
# If we didn't add the lines after a comment, add them at the end
if not torch_lines_added:
new_lines.extend(torch_lines)
# Write the updated requirements.txt
with open('requirements.txt', 'w') as f:
f.writelines(new_lines)
print_success("Updated requirements.txt with pinned PyTorch version")
return True
except Exception as e:
print_error(f"Failed to update requirements.txt: {e}")
return False
def create_pip_config():
"""Create a pip.conf file to set default options."""
print_info("Creating pip configuration to prevent automatic dependency installation")
# Get the installed PyTorch version to determine the CUDA suffix
torch_version = get_installed_pytorch_version()
cuda_suffix = "cu118" # Default to CUDA 11.8
if torch_version and '+cu' in torch_version:
# Extract the CUDA suffix from the PyTorch version
cuda_suffix = torch_version.split('+')[1] # e.g., 'cu118'
print_info(f"Using CUDA suffix from installed PyTorch: {cuda_suffix}")
else:
print_info(f"Using default CUDA suffix: {cuda_suffix}")
# Determine the pip config directory
if os.name == 'nt': # Windows
pip_config_dir = os.path.join(os.path.expanduser('~'), 'pip')
else: # Unix/Linux/macOS
pip_config_dir = os.path.join(os.path.expanduser('~'), '.pip')
# Create the directory if it doesn't exist
os.makedirs(pip_config_dir, exist_ok=True)
# Determine the config file name
if os.name == 'nt': # Windows
pip_config_file = os.path.join(pip_config_dir, 'pip.ini')
else: # Unix/Linux/macOS
pip_config_file = os.path.join(pip_config_dir, 'pip.conf')
# Check if file already exists
if os.path.exists(pip_config_file):
print_warning(f"Pip config file already exists at {pip_config_file}")
print_info("Creating backup of existing file")
backup_path = pip_config_file + '.bak'
os.rename(pip_config_file, backup_path)
print_success(f"Backup created at {backup_path}")
# Create new pip config file
with open(pip_config_file, 'w') as f:
f.write(f"""
[global]
no-dependencies = true
no-cache-dir = false
timeout = 60
index-url = https://download.pytorch.org/whl/{cuda_suffix}
[install]
no-dependencies = true
ignore-installed = false
no-warn-script-location = true
""")
print_success(f"Created pip config file at {pip_config_file}")
return True
def update_claude_config():
"""Update Claude Desktop configuration to use the virtual environment Python."""
print_info("Updating Claude Desktop configuration")
# Get the virtual environment Python executable
venv_python = sys.executable
# Get the script directory
script_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(script_dir)
# Get the memory_wrapper.py path
memory_wrapper_path = os.path.join(parent_dir, 'memory_wrapper.py')
# Determine the Claude config file path
if os.name == 'nt': # Windows
claude_config_dir = os.path.join(os.path.expanduser('~'), 'AppData', 'Roaming', 'Claude')
else: # Unix/Linux/macOS
claude_config_dir = os.path.join(os.path.expanduser('~'), 'Library', 'Application Support', 'Claude')
claude_config_file = os.path.join(claude_config_dir, 'claude_desktop_config.json')
# Check if file exists
if not os.path.exists(claude_config_file):
print_warning(f"Claude config file not found at {claude_config_file}")
return False
try:
import json
# Read the current config
with open(claude_config_file, 'r') as f:
config = json.load(f)
# Update the memory server configuration
if 'mcpServers' in config and 'memory' in config['mcpServers']:
# Create backup of existing config
backup_path = claude_config_file + '.bak'
with open(backup_path, 'w') as f:
json.dump(config, f, indent=2)
print_success(f"Created backup of Claude config at {backup_path}")
# Update the configuration
config['mcpServers']['memory']['command'] = venv_python
config['mcpServers']['memory']['args'] = [memory_wrapper_path, '--debug', '--no-auto-install']
# Add environment variables to prevent automatic installation
if 'env' not in config['mcpServers']['memory']:
config['mcpServers']['memory']['env'] = {}
config['mcpServers']['memory']['env']['PIP_NO_DEPENDENCIES'] = '1'
config['mcpServers']['memory']['env']['PIP_NO_INSTALL'] = '1'
# Write the updated config
with open(claude_config_file, 'w') as f:
json.dump(config, f, indent=2)
print_success(f"Updated Claude config at {claude_config_file}")
# Also update the claude_desktop_config_updated.json file if it exists
updated_config_file = os.path.join(parent_dir, 'claude_desktop_config_updated.json')
if os.path.exists(updated_config_file):
with open(updated_config_file, 'r') as f:
updated_config = json.load(f)
# Update the configuration
if 'mcpServers' in updated_config and 'memory' in updated_config['mcpServers']:
updated_config['mcpServers']['memory']['command'] = venv_python
updated_config['mcpServers']['memory']['args'] = [memory_wrapper_path, '--debug', '--no-auto-install']
# Add environment variables to prevent automatic installation
if 'env' not in updated_config['mcpServers']['memory']:
updated_config['mcpServers']['memory']['env'] = {}
updated_config['mcpServers']['memory']['env']['PIP_NO_DEPENDENCIES'] = '1'
updated_config['mcpServers']['memory']['env']['PIP_NO_INSTALL'] = '1'
# Write the updated config
with open(updated_config_file, 'w') as f:
json.dump(updated_config, f, indent=2)
print_success(f"Updated {updated_config_file}")
return True
else:
print_warning("Memory server configuration not found in Claude config")
return False
except Exception as e:
print_error(f"Failed to update Claude config: {e}")
return False
def main():
"""Main function."""
print_header("PyTorch Dependency Fixer")
# Get the currently installed PyTorch version
torch_version = get_installed_pytorch_version()
if not torch_version:
print_error("PyTorch is not installed, cannot fix dependencies")
sys.exit(1)
print_success(f"Found PyTorch {torch_version}")
# Create a constraints file
constraints_path = create_constraint_file()
if not constraints_path:
print_error("Failed to create constraints file")
sys.exit(1)
# Pin PyTorch version
if not pin_pytorch_version(constraints_path):
print_error("Failed to pin PyTorch version")
sys.exit(1)
# Create no-deps wrapper
if not create_no_deps_wrapper():
print_warning("Failed to create no-deps wrapper")
# Create sitecustomize.py
if not create_sitecustomize():
print_warning("Failed to create sitecustomize.py")
# Update requirements.txt
if not update_requirements_file():
print_warning("Failed to update requirements.txt")
# Create pip.conf
if not create_pip_config():
print_warning("Failed to create pip configuration")
# Update Claude Desktop configuration
if not update_claude_config():
print_warning("Failed to update Claude Desktop configuration")
print_header("Fix Complete")
print_info(f"PyTorch {torch_version} has been pinned and automatic installation has been disabled")
print_info("You should now be able to run the memory server without issues")
print_info("If you still encounter problems, try restarting Claude Desktop")
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print_info("Script interrupted by user")
sys.exit(0)
except Exception as e:
print_error(f"Unhandled exception: {e}")
import traceback
traceback.print_exc()
sys.exit(1)