Skip to main content
Glama

MCP Bridge Server

serviceManager.ts7.5 kB
import { promises as fs } from 'fs'; import { join, dirname } from 'path'; import { exec } from 'child_process'; import { promisify } from 'util'; import { MacOSConfig } from '../../types.js'; import { MacOSDirectoryManager, DEFAULT_MACOS_PATHS } from './directoryManager.js'; const execAsync = promisify(exec); /** * Service status */ export enum ServiceStatus { RUNNING = 'running', STOPPED = 'stopped', NOT_INSTALLED = 'not_installed', UNKNOWN = 'unknown' } /** * Service error types */ export enum ServiceErrorType { INSTALLATION_FAILED = 'installation_failed', UNINSTALLATION_FAILED = 'uninstallation_failed', START_FAILED = 'start_failed', STOP_FAILED = 'stop_failed', STATUS_CHECK_FAILED = 'status_check_failed' } /** * Service error */ export class ServiceError extends Error { constructor( public type: ServiceErrorType, message: string, public metadata?: any ) { super(message); this.name = 'ServiceError'; } } /** * macOS Service Manager * Handles installation and management of macOS LaunchAgent service */ export class MacOSServiceManager { private directoryManager: MacOSDirectoryManager; private config: MacOSConfig; constructor(config: MacOSConfig = {}) { this.config = { ...DEFAULT_MACOS_PATHS, ...config, autoStart: config.autoStart !== false, keepAlive: config.keepAlive !== false, runAtLoad: config.runAtLoad !== false }; this.directoryManager = new MacOSDirectoryManager(config); } /** * Initialize service manager */ public async initialize(): Promise<void> { await this.directoryManager.initialize(); } /** * Install service as LaunchAgent * @param execPath Path to the executable */ public async install(execPath: string): Promise<void> { try { // Create LaunchAgent plist const plistContent = this.generateLaunchAgentPlist(execPath); // Ensure LaunchAgent directory exists const launchAgentPath = this.directoryManager.getLaunchAgentPath(); await fs.mkdir(dirname(launchAgentPath), { recursive: true }); // Write plist file await fs.writeFile(launchAgentPath, plistContent); // Set permissions await fs.chmod(launchAgentPath, 0o644); console.log(`LaunchAgent installed at ${launchAgentPath}`); // Load service if autoStart is enabled if (this.config.autoStart) { await this.start(); } } catch (error) { throw new ServiceError( ServiceErrorType.INSTALLATION_FAILED, 'Failed to install LaunchAgent', error ); } } /** * Uninstall service */ public async uninstall(): Promise<void> { try { // Stop service first await this.stop(); // Remove plist file const launchAgentPath = this.directoryManager.getLaunchAgentPath(); await fs.unlink(launchAgentPath); console.log(`LaunchAgent uninstalled from ${launchAgentPath}`); } catch (error) { throw new ServiceError( ServiceErrorType.UNINSTALLATION_FAILED, 'Failed to uninstall LaunchAgent', error ); } } /** * Start service */ public async start(): Promise<void> { try { const launchAgentPath = this.directoryManager.getLaunchAgentPath(); // Check if service is already running const status = await this.getStatus(); if (status === ServiceStatus.RUNNING) { console.log('Service is already running'); return; } // Load service await execAsync(`launchctl load -w ${launchAgentPath}`); console.log('Service started'); } catch (error) { throw new ServiceError( ServiceErrorType.START_FAILED, 'Failed to start service', error ); } } /** * Stop service */ public async stop(): Promise<void> { try { const launchAgentPath = this.directoryManager.getLaunchAgentPath(); // Check if service is running const status = await this.getStatus(); if (status !== ServiceStatus.RUNNING) { console.log('Service is not running'); return; } // Unload service await execAsync(`launchctl unload -w ${launchAgentPath}`); console.log('Service stopped'); } catch (error) { throw new ServiceError( ServiceErrorType.STOP_FAILED, 'Failed to stop service', error ); } } /** * Restart service */ public async restart(): Promise<void> { await this.stop(); await this.start(); } /** * Get service status */ public async getStatus(): Promise<ServiceStatus> { try { // Get service label from plist name const plistName = this.config.launchAgentName || DEFAULT_MACOS_PATHS.launchAgentName; const serviceLabel = plistName.replace('.plist', ''); // Check if plist file exists const launchAgentPath = this.directoryManager.getLaunchAgentPath(); try { await fs.access(launchAgentPath); } catch { return ServiceStatus.NOT_INSTALLED; } // Check if service is running const { stdout } = await execAsync(`launchctl list | grep ${serviceLabel}`); return stdout.trim() ? ServiceStatus.RUNNING : ServiceStatus.STOPPED; } catch (error) { // If grep returns non-zero exit code, service is not running if (error instanceof Error && 'code' in error && error.code === 1) { return ServiceStatus.STOPPED; } // For other errors, return unknown console.error('Failed to check service status:', error); return ServiceStatus.UNKNOWN; } } /** * Generate LaunchAgent plist content * @param execPath Path to the executable */ private generateLaunchAgentPlist(execPath: string): string { // Get service label from plist name const plistName = this.config.launchAgentName || DEFAULT_MACOS_PATHS.launchAgentName; const serviceLabel = plistName.replace('.plist', ''); // Get socket path const socketPath = this.directoryManager.getSocketPath(); // Get log file path const logFilePath = this.directoryManager.getLogFilePath(); return `<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>${serviceLabel}</string> <key>ProgramArguments</key> <array> <string>${execPath}</string> <string>--transport</string> <string>unix-socket</string> <string>--socket-path</string> <string>${socketPath}</string> </array> <key>RunAtLoad</key> <${this.config.runAtLoad ? 'true' : 'false'}/> <key>KeepAlive</key> <${this.config.keepAlive ? 'true' : 'false'}/> <key>StandardOutPath</key> <string>${logFilePath}</string> <key>StandardErrorPath</key> <string>${logFilePath}</string> <key>EnvironmentVariables</key> <dict> <key>PATH</key> <string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string> <key>NODE_ENV</key> <string>production</string> </dict> <key>WorkingDirectory</key> <string>${dirname(execPath)}</string> </dict> </plist>`; } } // Export singleton instance with default config export const macOSServiceManager = new MacOSServiceManager();

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/glassBead-tc/SubspaceDomain'

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