Skip to main content
Glama
pending-manager.ts5.81 kB
/** * PendingPermissionsManager - Tracks pending permission approval requests * * Manages permission requests waiting for dashboard approval with timeout handling. * Each pending request is tracked by a unique ID and resolves via Promise when * dashboard responds or timeout occurs. */ import { EventEmitter } from "events"; import { getChildLogger } from "../utils/logger.js"; const logger = getChildLogger("permissions:pending"); export interface PendingPermissionRequest { permissionId: string; sessionId: string; teamName: string; toolName: string; toolInput: Record<string, unknown>; reason?: string; createdAt: Date; } export interface PendingPermissionResponse { approved: boolean; reason?: string; } interface PendingEntry { request: PendingPermissionRequest; resolve: (response: PendingPermissionResponse) => void; reject: (error: Error) => void; timeoutTimer: NodeJS.Timeout; } /** * Manages pending permission requests with timeout and Promise-based resolution */ export class PendingPermissionsManager extends EventEmitter { private pending = new Map<string, PendingEntry>(); private permissionCounter = 0; private defaultTimeout: number; constructor(defaultTimeoutMs = 30000) { super(); this.defaultTimeout = defaultTimeoutMs; } /** * Generate unique permission ID */ private generatePermissionId(): string { return `perm_${Date.now()}_${++this.permissionCounter}`; } /** * Create new pending permission request * Returns a Promise that resolves when dashboard responds or timeout occurs */ async createPendingPermission( sessionId: string, teamName: string, toolName: string, toolInput: Record<string, unknown>, reason?: string, timeoutMs?: number, ): Promise<PendingPermissionResponse> { const permissionId = this.generatePermissionId(); const timeout = timeoutMs ?? this.defaultTimeout; const request: PendingPermissionRequest = { permissionId, sessionId, teamName, toolName, toolInput, reason, createdAt: new Date(), }; logger.info( { permissionId, sessionId, teamName, toolName, timeout, }, "Creating pending permission request", ); // Create Promise that will be resolved by dashboard response or timeout const promise = new Promise<PendingPermissionResponse>((resolve, reject) => { // Set timeout timer const timeoutTimer = setTimeout(() => { this.handleTimeout(permissionId); }, timeout); // Store entry this.pending.set(permissionId, { request, resolve, reject, timeoutTimer, }); }); // Emit event for WebSocket broadcast this.emit("permission:created", request); return promise; } /** * Resolve pending permission with dashboard response */ resolvePendingPermission( permissionId: string, approved: boolean, reason?: string, ): boolean { const entry = this.pending.get(permissionId); if (!entry) { logger.warn({ permissionId }, "Permission not found for resolution"); return false; } logger.info( { permissionId, approved, teamName: entry.request.teamName, }, "Resolving pending permission", ); // Clear timeout clearTimeout(entry.timeoutTimer); // Resolve promise entry.resolve({ approved, reason }); // Remove from pending this.pending.delete(permissionId); // Emit event this.emit("permission:resolved", { permissionId, approved, reason, }); return true; } /** * Handle permission timeout */ private handleTimeout(permissionId: string): void { const entry = this.pending.get(permissionId); if (!entry) { return; } logger.warn( { permissionId, teamName: entry.request.teamName, toolName: entry.request.toolName, }, "Permission request timed out", ); // Resolve with denial entry.resolve({ approved: false, reason: "Permission request timed out - no dashboard response received", }); // Remove from pending this.pending.delete(permissionId); // Emit event this.emit("permission:timeout", { permissionId, request: entry.request, }); } /** * Get all pending permission requests */ getPendingRequests(): PendingPermissionRequest[] { return Array.from(this.pending.values()).map((entry) => entry.request); } /** * Get specific pending request by ID */ getPendingRequest(permissionId: string): PendingPermissionRequest | undefined { return this.pending.get(permissionId)?.request; } /** * Cancel specific pending request */ cancelPendingPermission(permissionId: string): boolean { const entry = this.pending.get(permissionId); if (!entry) { return false; } logger.info({ permissionId }, "Canceling pending permission"); clearTimeout(entry.timeoutTimer); entry.resolve({ approved: false, reason: "Permission request canceled", }); this.pending.delete(permissionId); return true; } /** * Clear all pending permissions (e.g., on shutdown) */ clearAll(): void { logger.info( { count: this.pending.size }, "Clearing all pending permissions", ); for (const [permissionId, entry] of this.pending.entries()) { clearTimeout(entry.timeoutTimer); entry.resolve({ approved: false, reason: "Server shutting down", }); } this.pending.clear(); } /** * Get count of pending permissions */ get pendingCount(): number { return this.pending.size; } }

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/jenova-marie/iris-mcp'

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