Skip to main content
Glama

ABSD DevOps MCP Server

by anthonybir
session.ts4.37 kB
import { spawn as spawnPty } from 'node-pty'; import type { IPty } from 'node-pty'; import type { ProcessSession, SessionInfo, SessionState } from '../../types/session.js'; import type { Logger } from '../../utils/logger.js'; export class SessionManager { private sessions: Map<number, ProcessSession> = new Map(); private cleanupInterval: NodeJS.Timeout | null = null; constructor( private logger: Logger, private sessionTimeout: number = 30 * 60 * 1000 // 30 minutes default ) { // Start cleanup interval (every 5 minutes) this.startCleanupInterval(); } /** * Create a new terminal session */ create( command: string = process.env.SHELL || 'zsh', args: string[] = [], cwd: string = process.env.HOME || process.cwd() ): ProcessSession { try { const ptyProcess = spawnPty(command, args, { name: 'xterm-256color', cols: 120, rows: 30, cwd, env: process.env as Record<string, string>, }); const session: ProcessSession = { pid: ptyProcess.pid, ptyProcess, outputBuffer: [], createdAt: new Date(), lastActivity: new Date(), shell: command, cwd, state: 'idle', }; // Capture output ptyProcess.onData((data) => { session.outputBuffer.push(data); session.lastActivity = new Date(); session.state = 'running'; }); // Handle process exit ptyProcess.onExit(({ exitCode }) => { this.logger.info({ pid: session.pid, exitCode, }, 'Process exited'); session.state = 'terminated'; }); this.sessions.set(ptyProcess.pid, session); this.logger.info({ pid: ptyProcess.pid, shell: command, cwd, }, 'Session created'); return session; } catch (error) { this.logger.error({ error, command, cwd }, 'Failed to create session'); throw error; } } /** * Get a session by PID */ get(pid: number): ProcessSession | undefined { return this.sessions.get(pid); } /** * Get all active sessions */ listAll(): SessionInfo[] { const now = Date.now(); return Array.from(this.sessions.values()).map(session => ({ pid: session.pid, shell: session.shell, cwd: session.cwd, state: session.state, uptime: now - session.createdAt.getTime(), lastActivity: now - session.lastActivity.getTime(), outputLines: session.outputBuffer.length, })); } /** * Terminate a session by PID */ terminate(pid: number): boolean { const session = this.sessions.get(pid); if (!session) { return false; } try { session.ptyProcess.kill(); session.state = 'terminated'; this.sessions.delete(pid); this.logger.info({ pid }, 'Session terminated'); return true; } catch (error) { this.logger.error({ error, pid }, 'Failed to terminate session'); return false; } } /** * Clean up stale sessions (inactive beyond timeout) */ cleanupStale(): number { const now = Date.now(); let cleaned = 0; for (const [pid, session] of this.sessions) { const idle = now - session.lastActivity.getTime(); if (idle > this.sessionTimeout || session.state === 'terminated') { this.terminate(pid); cleaned++; } } if (cleaned > 0) { this.logger.info({ cleaned }, 'Cleaned up stale sessions'); } return cleaned; } /** * Start automatic cleanup interval */ private startCleanupInterval(): void { if (this.cleanupInterval) { return; } // Run cleanup every 5 minutes this.cleanupInterval = setInterval(() => { this.cleanupStale(); }, 5 * 60 * 1000); // Don't prevent process from exiting this.cleanupInterval.unref(); } /** * Stop cleanup interval and terminate all sessions */ shutdown(): void { if (this.cleanupInterval) { clearInterval(this.cleanupInterval); this.cleanupInterval = null; } // Terminate all sessions for (const pid of this.sessions.keys()) { this.terminate(pid); } this.logger.info('SessionManager shut down'); } /** * Get session count */ get count(): number { return this.sessions.size; } }

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/anthonybir/ABSD_MCP'

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