Skip to main content
Glama

MCP Memory Service

#!/usr/bin/env python3 """ macOS LaunchAgent installer for MCP Memory Service. Creates and manages LaunchAgent plist files for automatic service startup. """ import os import sys import json import plistlib import argparse import subprocess from pathlib import Path # Add parent directory to path for imports sys.path.insert(0, str(Path(__file__).parent.parent)) try: from scripts.service_utils import ( get_project_root, get_service_paths, get_service_environment, generate_api_key, save_service_config, load_service_config, check_dependencies, get_service_command, print_service_info ) except ImportError as e: print(f"Error importing service utilities: {e}") print("Please ensure you're running this from the project directory") sys.exit(1) SERVICE_LABEL = "com.mcp.memory-service" SERVICE_NAME = "MCP Memory Service" def get_launchd_paths(user_level=True): """Get the paths for LaunchAgent/LaunchDaemon files.""" if user_level: # User-level LaunchAgent plist_dir = Path.home() / "Library" / "LaunchAgents" plist_file = plist_dir / f"{SERVICE_LABEL}.plist" else: # System-level LaunchDaemon (requires root) plist_dir = Path("/Library/LaunchDaemons") plist_file = plist_dir / f"{SERVICE_LABEL}.plist" return plist_dir, plist_file def create_plist(api_key, user_level=True): """Create the LaunchAgent/LaunchDaemon plist configuration.""" paths = get_service_paths() command = get_service_command() environment = get_service_environment() environment['MCP_API_KEY'] = api_key # Create plist dictionary plist_dict = { 'Label': SERVICE_LABEL, 'ProgramArguments': command, 'EnvironmentVariables': environment, 'WorkingDirectory': str(paths['project_root']), 'RunAtLoad': True, 'KeepAlive': { 'SuccessfulExit': False, 'Crashed': True }, 'StandardOutPath': str(paths['log_dir'] / 'mcp-memory-service.log'), 'StandardErrorPath': str(paths['log_dir'] / 'mcp-memory-service.error.log'), 'ProcessType': 'Interactive' if user_level else 'Background', } # Add user/group for system-level daemon if not user_level: plist_dict['UserName'] = os.environ.get('USER', 'nobody') plist_dict['GroupName'] = 'staff' return plist_dict def create_shell_scripts(): """Create convenient shell scripts for service management.""" paths = get_service_paths() scripts_dir = paths['scripts_dir'] / 'macos' scripts_dir.mkdir(exist_ok=True) # Start script start_script = scripts_dir / 'start_service.sh' with open(start_script, 'w') as f: f.write(f'''#!/bin/bash echo "Starting {SERVICE_NAME}..." launchctl load ~/Library/LaunchAgents/{SERVICE_LABEL}.plist if [ $? -eq 0 ]; then echo "✅ Service started successfully!" else echo "❌ Failed to start service" fi ''') start_script.chmod(0o755) # Stop script stop_script = scripts_dir / 'stop_service.sh' with open(stop_script, 'w') as f: f.write(f'''#!/bin/bash echo "Stopping {SERVICE_NAME}..." launchctl unload ~/Library/LaunchAgents/{SERVICE_LABEL}.plist if [ $? -eq 0 ]; then echo "✅ Service stopped successfully!" else echo "❌ Failed to stop service" fi ''') stop_script.chmod(0o755) # Status script status_script = scripts_dir / 'service_status.sh' with open(status_script, 'w') as f: f.write(f'''#!/bin/bash echo "{SERVICE_NAME} Status:" echo "-" | tr '-' '=' launchctl list | grep {SERVICE_LABEL} if [ $? -eq 0 ]; then echo "" echo "Service is loaded. PID shown above (- means not running)" else echo "Service is not loaded" fi ''') status_script.chmod(0o755) # Uninstall script uninstall_script = scripts_dir / 'uninstall_service.sh' with open(uninstall_script, 'w') as f: f.write(f'''#!/bin/bash echo "This will uninstall {SERVICE_NAME}." read -p "Are you sure? (y/N): " confirm if [[ ! "$confirm" =~ ^[Yy]$ ]]; then exit 0 fi echo "Stopping service..." launchctl unload ~/Library/LaunchAgents/{SERVICE_LABEL}.plist 2>/dev/null echo "Removing service files..." rm -f ~/Library/LaunchAgents/{SERVICE_LABEL}.plist echo "✅ Service uninstalled" ''') uninstall_script.chmod(0o755) return scripts_dir def install_service(user_level=True): """Install the macOS LaunchAgent/LaunchDaemon.""" service_type = "LaunchAgent" if user_level else "LaunchDaemon" # Check for root if system-level if not user_level and os.geteuid() != 0: print("\n❌ ERROR: System-level LaunchDaemon requires root privileges") print("Please run with sudo or use --user for user-level installation") sys.exit(1) print(f"\n🔍 Checking dependencies...") deps_ok, deps_msg = check_dependencies() if not deps_ok: print(f"❌ {deps_msg}") sys.exit(1) print(f"✅ {deps_msg}") # Generate API key api_key = generate_api_key() print(f"\n🔑 Generated API key: {api_key}") # Create service configuration config = { 'service_label': SERVICE_LABEL, 'api_key': api_key, 'command': get_service_command(), 'environment': get_service_environment(), 'user_level': user_level } # Save configuration config_file = save_service_config(config) print(f"💾 Saved configuration to: {config_file}") # Get plist paths plist_dir, plist_file = get_launchd_paths(user_level) # Create plist directory if it doesn't exist plist_dir.mkdir(parents=True, exist_ok=True) # Create plist print(f"\n📝 Creating {service_type} plist...") plist_dict = create_plist(api_key, user_level) # Write plist file with open(plist_file, 'wb') as f: plistlib.dump(plist_dict, f) # Set proper permissions if user_level: os.chmod(plist_file, 0o644) else: os.chmod(plist_file, 0o644) os.chown(plist_file, 0, 0) # root:wheel print(f"✅ Created plist at: {plist_file}") # Load the service print(f"\n🚀 Loading {service_type}...") result = subprocess.run([ 'launchctl', 'load', '-w', str(plist_file) ], capture_output=True, text=True) if result.returncode != 0: if "already loaded" in result.stderr: print("ℹ️ Service was already loaded, reloading...") # Unload first subprocess.run(['launchctl', 'unload', str(plist_file)], capture_output=True) # Load again subprocess.run(['launchctl', 'load', '-w', str(plist_file)], capture_output=True) else: print(f"❌ Failed to load service: {result.stderr}") print("\n💡 Try checking Console.app for detailed error messages") sys.exit(1) print(f"✅ {service_type} loaded successfully!") # Create convenience scripts if user_level: scripts_dir = create_shell_scripts() print(f"\n📁 Created management scripts in: {scripts_dir}") # Print service information platform_info = { 'Start Service': f'launchctl load -w {plist_file}', 'Stop Service': f'launchctl unload {plist_file}', 'Service Status': f'launchctl list | grep {SERVICE_LABEL}', 'View Logs': f'tail -f {paths["log_dir"] / "mcp-memory-service.log"}', 'Uninstall': f'python "{Path(__file__)}" --uninstall' } print_service_info(api_key, platform_info) # Additional macOS-specific tips print("\n📌 macOS Tips:") print(" • Check Console.app for detailed service logs") print(" • Service will start automatically on login/boot") print(" • Use Activity Monitor to verify the process is running") return True def uninstall_service(user_level=True): """Uninstall the macOS LaunchAgent/LaunchDaemon.""" service_type = "LaunchAgent" if user_level else "LaunchDaemon" # Check for root if system-level if not user_level and os.geteuid() != 0: print("\n❌ ERROR: System-level LaunchDaemon requires root privileges") print("Please run with sudo") sys.exit(1) print(f"\n🗑️ Uninstalling {SERVICE_NAME} {service_type}...") # Get plist paths plist_dir, plist_file = get_launchd_paths(user_level) if plist_file.exists(): # Unload the service print("⏹️ Stopping service...") subprocess.run([ 'launchctl', 'unload', str(plist_file) ], capture_output=True) # Remove the plist file print("🗑️ Removing plist file...") plist_file.unlink() print(f"✅ {service_type} uninstalled successfully!") else: print(f"ℹ️ {service_type} is not installed") # Clean up configuration config = load_service_config() if config and config.get('service_label') == SERVICE_LABEL: print("🧹 Cleaning up configuration...") config_file = get_service_paths()['config_dir'] / 'service_config.json' config_file.unlink() def start_service(user_level=True): """Start the macOS service.""" plist_dir, plist_file = get_launchd_paths(user_level) if not plist_file.exists(): print(f"❌ Service is not installed. Run without --start to install first.") sys.exit(1) print(f"\n▶️ Starting {SERVICE_NAME}...") result = subprocess.run([ 'launchctl', 'load', str(plist_file) ], capture_output=True, text=True) if result.returncode == 0: print("✅ Service started successfully!") else: if "already loaded" in result.stderr: print("ℹ️ Service is already running") else: print(f"❌ Failed to start service: {result.stderr}") def stop_service(user_level=True): """Stop the macOS service.""" plist_dir, plist_file = get_launchd_paths(user_level) print(f"\n⏹️ Stopping {SERVICE_NAME}...") result = subprocess.run([ 'launchctl', 'unload', str(plist_file) ], capture_output=True, text=True) if result.returncode == 0: print("✅ Service stopped successfully!") else: print(f"ℹ️ Service may not be running: {result.stderr}") def service_status(user_level=True): """Check the macOS service status.""" print(f"\n📊 {SERVICE_NAME} Status:") print("-" * 40) # Check if plist exists plist_dir, plist_file = get_launchd_paths(user_level) if not plist_file.exists(): print("❌ Service is not installed") return # Check launchctl list result = subprocess.run([ 'launchctl', 'list' ], capture_output=True, text=True) service_found = False for line in result.stdout.splitlines(): if SERVICE_LABEL in line: service_found = True parts = line.split() if len(parts) >= 3: pid = parts[0] status = parts[1] if pid != '-': print(f"✅ Service is RUNNING (PID: {pid})") else: print(f"⏹️ Service is STOPPED (last exit: {status})") break if not service_found: print("⏹️ Service is not loaded") # Show configuration config = load_service_config() if config: print(f"\n📋 Configuration:") print(f" Service Label: {SERVICE_LABEL}") print(f" API Key: {config.get('api_key', 'Not set')}") print(f" Type: {'User LaunchAgent' if user_level else 'System LaunchDaemon'}") print(f" Plist: {plist_file}") # Show recent logs paths = get_service_paths() log_file = paths['log_dir'] / 'mcp-memory-service.log' if log_file.exists(): print(f"\n📜 Recent logs from {log_file}:") result = subprocess.run([ 'tail', '-n', '10', str(log_file) ], capture_output=True, text=True) if result.stdout: print(result.stdout) def main(): """Main entry point.""" parser = argparse.ArgumentParser( description="macOS LaunchAgent installer for MCP Memory Service" ) # Service level parser.add_argument('--user', action='store_true', default=True, help='Install as user LaunchAgent (default)') parser.add_argument('--system', action='store_true', help='Install as system LaunchDaemon (requires sudo)') # Actions parser.add_argument('--uninstall', action='store_true', help='Uninstall the service') parser.add_argument('--start', action='store_true', help='Start the service') parser.add_argument('--stop', action='store_true', help='Stop the service') parser.add_argument('--status', action='store_true', help='Check service status') parser.add_argument('--restart', action='store_true', help='Restart the service') args = parser.parse_args() # Determine service level user_level = not args.system if args.uninstall: uninstall_service(user_level) elif args.start: start_service(user_level) elif args.stop: stop_service(user_level) elif args.status: service_status(user_level) elif args.restart: stop_service(user_level) start_service(user_level) else: # Default action is to install install_service(user_level) if __name__ == '__main__': main()

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/doobidoo/mcp-memory-service'

If you have feedback or need assistance with the MCP directory API, please join our Discord server