Skip to main content
Glama
server-manager.ts8.25 kB
#!/usr/bin/env node import { spawn, ChildProcess, StdioOptions } from 'child_process'; import * as path from 'path'; import * as fs from 'fs'; import * as net from 'net'; import * as os from 'os'; import { fileURLToPath } from 'url'; import { exec } from 'child_process'; // Get the directory name of the current module const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Path to store the server PID file const PID_DIR = path.join(os.homedir(), '.phonepi-mcp'); const PID_FILE = path.join(PID_DIR, 'server.pid'); const PORT_FILE = path.join(PID_DIR, 'server.port'); // Ensure the PID directory exists if (!fs.existsSync(PID_DIR)) { fs.mkdirSync(PID_DIR, { recursive: true }); } /** * Force kill any process using the specified port */ async function forceKillProcessOnPort(port: number): Promise<boolean> { return new Promise((resolve) => { // Command to find and kill process on port (works on both Unix and Windows) const cmd = process.platform === 'win32' ? `for /f "tokens=5" %a in ('netstat -aon ^| find ":${port}"') do taskkill /F /PID %a` : `lsof -i :${port} | grep LISTEN | awk '{print $2}' | xargs -r kill -9`; exec(cmd, (error) => { if (error) { console.error(`Failed to force kill process on port ${port}:`, error); resolve(false); } else { console.error(`Successfully killed process on port ${port}`); resolve(true); } }); }); } /** * Check if the server is already running by attempting to connect to the port */ export async function isServerRunning(port?: number): Promise<boolean> { try { // If port is not specified, try to get it from the PORT_FILE if (!port && fs.existsSync(PORT_FILE)) { port = parseInt(fs.readFileSync(PORT_FILE, 'utf-8').trim()); } // Default to 11041 if port is still not available or invalid if (!port || isNaN(port)) { port = 11041; } // Now port is guaranteed to be a number const portNumber: number = port; return new Promise((resolve) => { const testSocket = new net.Socket(); // Set a shorter timeout for faster response testSocket.setTimeout(500); // Handle connection success (server is running) testSocket.on('connect', () => { testSocket.destroy(); resolve(true); }); // Handle connection errors (server might not be running) testSocket.on('error', () => { testSocket.destroy(); resolve(false); }); // Handle timeout (server might not be running) testSocket.on('timeout', () => { testSocket.destroy(); resolve(false); }); // Attempt to connect to the server port testSocket.connect(portNumber, 'localhost'); }); } catch (error) { console.error('Error checking if server is running:', error); return false; } } /** * Start the MCP server and store its PID */ export async function startServer(port = 11041, background = false, inheritStdio = false): Promise<ChildProcess | null> { try { // Check if server is already running const serverRunning = await isServerRunning(port); if (serverRunning) { console.error(`MCP server is already running on port ${port}`); return null; } console.error(`Starting MCP server on port ${port}...`); // Get the path to the server script const serverScriptPath = path.join(__dirname, 'index.js'); // Ensure the script exists if (!fs.existsSync(serverScriptPath)) { console.error(`Server script not found at: ${serverScriptPath}`); return null; } // Set stdio option based on mode let stdio: StdioOptions; if (inheritStdio) { // Pass through all stdio for MCP communication stdio = 'inherit'; } else if (background) { // Ignore all stdio in background mode stdio = ['ignore', 'ignore', 'ignore']; } else { // Only capture stdout/stderr in foreground mode stdio = ['ignore', process.stdout, process.stderr]; } // Spawn the server process const serverProcess: ChildProcess = spawn('node', [serverScriptPath, '--port', port.toString(), '--stdio'], { detached: background, // Only detach if running in background stdio, env: { ...process.env, PORT: port.toString() } }); // Store the server PID const pid = serverProcess.pid; if (pid) { fs.writeFileSync(PID_FILE, pid.toString()); fs.writeFileSync(PORT_FILE, port.toString()); console.error(`Server started with PID: ${pid} on port ${port}`); if (background) { // Unref the process to allow the parent to exit independently serverProcess.unref(); } else { // Handle process exit in foreground mode serverProcess.on('exit', (code: number | null) => { console.error(`Server process exited with code ${code}`); // Clean up PID file when server exits if (fs.existsSync(PID_FILE)) { fs.unlinkSync(PID_FILE); } if (fs.existsSync(PORT_FILE)) { fs.unlinkSync(PORT_FILE); } }); } return serverProcess; } else { console.error('Failed to get server PID'); return null; } } catch (error) { console.error('Error starting server:', error); return null; } } /** * Stop the MCP server if it's running with enhanced port cleanup */ export async function stopServer(): Promise<boolean> { try { // Get the port from PORT_FILE let port: number | undefined; if (fs.existsSync(PORT_FILE)) { port = parseInt(fs.readFileSync(PORT_FILE, 'utf-8').trim()); } // Default to 11041 if port is not found if (!port) { port = 11041; } // Check if server is running const serverRunning = await isServerRunning(port); if (!serverRunning) { console.log('MCP server is not running'); // Clean up PID and PORT files if they exist but server is not running if (fs.existsSync(PID_FILE)) { fs.unlinkSync(PID_FILE); } if (fs.existsSync(PORT_FILE)) { fs.unlinkSync(PORT_FILE); } return true; } let success = false; // First try graceful shutdown using PID if (fs.existsSync(PID_FILE)) { const pid = parseInt(fs.readFileSync(PID_FILE, 'utf-8').trim()); if (pid) { try { process.kill(pid); success = true; } catch (killError) { console.error(`Failed to kill server process (PID: ${pid}):`, killError); } } } // If graceful shutdown failed or no PID file exists, force kill the process on the port if (!success) { success = await forceKillProcessOnPort(port); } // Clean up PID and PORT files if (fs.existsSync(PID_FILE)) { fs.unlinkSync(PID_FILE); } if (fs.existsSync(PORT_FILE)) { fs.unlinkSync(PORT_FILE); } // Double check the port is actually free const stillRunning = await isServerRunning(port); if (stillRunning) { console.error(`Failed to free up port ${port} despite attempts`); return false; } return success; } catch (error) { console.error('Error stopping server:', error); return false; } } /** * Get server status information */ export async function getServerStatus(): Promise<{ running: boolean; pid?: number; port?: number }> { try { // Get the port from PORT_FILE let port: number | undefined; if (fs.existsSync(PORT_FILE)) { port = parseInt(fs.readFileSync(PORT_FILE, 'utf-8').trim()); } const running = await isServerRunning(port); // Get PID if available let pid: number | undefined; if (running && fs.existsSync(PID_FILE)) { try { pid = parseInt(fs.readFileSync(PID_FILE, 'utf-8').trim()); } catch (error) { console.error('Error reading PID file:', error); } } return { running, pid, port }; } catch (error) { console.error('Error getting server status:', error); return { running: false }; } }

Latest Blog Posts

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/priyankark/phonepi-mcp'

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