/** * Server Manager * Manages development servers (any language/framework) - start, stop, restart, and monitor * Supports multiple runner types: native (spawn), docker, docker-compose * Persists state to .cdp-tools/servers.json for recovery and auto-run * Logs to .cdp-tools/logs// for cross-MCP access (native runner only) */ import * as net from 'net'; import { type RunnerType } from './runners/index.js'; export interface ServerStatus { id: string; command: string; cwd: string; pid: number; containerId?: string; startedAt: Date; uptime: string; port?: number; running: boolean; autoRun: boolean; runnerType: RunnerType; global: boolean; } export interface StartServerOptions { command: string; cwd: string; id: string; autoRun?: boolean; env?: Record; port?: number; /** Runner type - auto-detected from command if not specified */ runner?: RunnerType; /** If true, auto-add detected port to monitoredPorts with default level 'block' */ monitorPort?: boolean; /** If true, store server state in global ~/.cdp-tools/ instead of project directory */ global?: boolean; } export interface LogStats { serverId: string; newStdout: number; newStderr: number; } export type MonitoringLevel = 'inform' | 'error' | 'block'; export interface PersistedMonitoredPort { port: number; level: MonitoringLevel; description?: string; interval?: number; } export interface MonitoredPort { port: number; level: MonitoringLevel; description?: string; interval?: number; status: 'up' | 'down' | 'connecting'; socket: net.Socket | null; failedAt?: Date; acknowledged: boolean; reconnectTimer?: ReturnType; } export interface MonitoredPortStatus { port: number; level: MonitoringLevel; description?: string; interval?: number; status: 'up' | 'down' | 'connecting'; failedAt?: Date; acknowledged: boolean; } export interface PortFailureInfo { port: number; level: MonitoringLevel; description?: string; failedAt: Date; } /** Function type for getting interval for a monitoring level */ export type GetIntervalForLevel = (level: MonitoringLevel) => number; export type PendingStartupReason = 'timeout' | 'died'; export interface PendingStartup { serverId: string; startedAt: Date; timeoutAt: Date; acknowledged: boolean; reason?: PendingStartupReason; } export interface PersistedPendingStartup { serverId: string; startedAt: string; timeoutAt: string; acknowledged: boolean; reason?: PendingStartupReason; } export interface PendingStartupFailureInfo { serverId: string; startedAt: Date; reason: PendingStartupReason; } /** * Port Monitor - monitors ports using persistent TCP connections */ export declare class PortMonitor { private ports; private onFailureCallback?; private getIntervalForLevel; constructor(getIntervalForLevel?: GetIntervalForLevel); onFailure(callback: (port: number, level: MonitoringLevel) => void): void; startMonitoring(port: number, level: MonitoringLevel, description?: string, interval?: number): Promise; private connectToPort; private scheduleReconnect; stopMonitoring(port: number): Promise; acknowledgeFailure(port: number): Promise; getFailedPorts(): PortFailureInfo[]; getFailedPortsByLevel(level: MonitoringLevel): PortFailureInfo[]; hasBlockingFailures(): boolean; isMonitoring(port: number): boolean; getStatus(port?: number): MonitoredPortStatus[]; getPersistedState(): PersistedMonitoredPort[]; restoreFromState(ports: PersistedMonitoredPort[]): Promise; stopAll(): Promise; } export declare class ServerManager { private servers; private portMonitor; private pendingStartups; /** Mutex to serialize saveState calls - prevents concurrent file writes */ private saveMutex; /** * Get the port monitor instance (lazy-initialized) * Must be called after configManager.load() for config values to be used */ getPortMonitor(): PortMonitor; private getServersFilePath; /** * Initialize - load state and recover/start auto-run servers * Loads from both local (project) and global (~/.cdp-tools/) storage */ initialize(): Promise<{ recovered: string[]; started: string[]; failed: string[]; monitoredPorts: number[]; }>; /** * Resume port detection for a server after MCP restart * @param serverId The server to resume detection for * @param remainingMs Time remaining until timeout */ private resumePortDetection; private loadState; /** * Save state to disk - serialized through mutex to prevent concurrent writes */ saveState(): Promise; /** * Internal save implementation - uses atomic writes to prevent corruption */ private doSaveState; private getRunnerCommand; private getRunnerCwd; private isPortInUse; /** * Find PIDs of processes listening on a port using lsof */ private findProcessesOnPort; /** * Get the working directory of a process by PID */ private getProcessCwd; /** * Kill orphan processes holding a port, but only if they're in the expected directory * Returns: { killed: PIDs killed, foreign: PIDs that are from a different directory } */ private killOrphanProcessesOnPort; private waitForPortRelease; /** * Start a server */ startServer(options: StartServerOptions): Promise<{ id: string; pid: number; runnerType: RunnerType; containerId?: string; }>; /** * Background port detection with timeout management * @param serverId The server to detect port for * @param timeoutMs How long to wait before triggering blocking (default 30s) */ private detectPortInBackgroundWithTimeout; /** * @deprecated Use detectPortInBackgroundWithTimeout instead */ private detectPortInBackground; /** * Get pending startup failures that should trigger blocking * Returns only startups that have timed out or died and are not acknowledged */ getPendingStartupFailures(): PendingStartupFailureInfo[]; /** * Check if a server has a pending startup (regardless of state) */ hasPendingStartup(serverId: string): boolean; /** * Get pending startup status for a server */ getPendingStartup(serverId: string): PendingStartup | undefined; /** * Acknowledge a pending startup failure * Clears the blocking state and starts background health monitoring */ acknowledgeStartup(serverId: string): Promise; /** * Monitor server health after acknowledgment * Checks every 5 seconds for: * - Port detection (sets up port monitoring when found) * - Server death (re-triggers blocking) * Runs until port is found, server dies, or server is removed */ private monitorServerHealth; /** * Extend the startup timeout by another 30 seconds * Resets the acknowledged flag and reason, resumes detection */ extendStartupTimeout(serverId: string): Promise; /** * Remove pending startup entry (called when server is stopped or removed) */ private removePendingStartup; /** * Stop a server */ stopServer(serverId: string): Promise; /** * Restart a server * Reloads config from persisted state to pick up any manual edits (e.g., port changes) */ restartServer(serverId: string): Promise<{ id: string; pid: number; runnerType: RunnerType; containerId?: string; }>; /** * Set autoRun flag */ setAutoRun(serverId: string, autoRun: boolean): Promise; /** * Get status of servers */ getStatus(serverId?: string): Promise; /** * Get log stats (for native runner only) */ getLogStats(): LogStats[]; /** * Get logs from a server */ getLogs(serverId: string, options?: { type?: 'stdout' | 'stderr' | 'all'; lines?: number; delta?: boolean; }): Promise; /** * Clear logs (native runner only) */ clearLogs(serverId: string): Promise<{ logDir: string; stdoutPath: string; stderrPath: string; }>; /** * Get log access info (file paths for native, commands for docker) */ getLogAccess(serverId: string): { type: 'file'; logDir: string; stdoutPath: string; stderrPath: string; } | { type: 'command'; command: string; } | null; /** * Stop all servers */ stopAll(): Promise; /** * Remove a server from config */ removeServer(serverId: string): Promise; /** * Get running server IDs */ getRunningServerIds(): Promise; /** * Cleanup - refresh status of all servers (no longer removes stopped servers) * Stopped servers remain in config and can be manually restarted */ cleanup(): Promise; /** * Reload a server's config from persisted state (for picking up manual edits) */ private reloadServerConfig; } //# sourceMappingURL=server-manager.d.ts.map