Skip to main content
Glama
approval.tsโ€ข6.73 kB
import { logger } from '../utils/logger.js'; import { SafeLogger } from '../utils/safe-logger.js'; import { randomUUID } from 'crypto'; export interface ApprovalRequest { id: string; timestamp: number; duckName: string; mcpServer: string; toolName: string; arguments: Record<string, unknown>; status: 'pending' | 'approved' | 'denied' | 'expired'; approvedBy?: string; deniedReason?: string; expiresAt: number; } export type ApprovalStatus = 'pending' | 'approved' | 'denied' | 'expired'; export class ApprovalService { private pendingApprovals: Map<string, ApprovalRequest> = new Map(); private approvalTimeout: number; private cleanupInterval: NodeJS.Timeout | null = null; private approvedToolsForSession: Set<string> = new Set(); constructor(approvalTimeoutSeconds: number = 300) { this.approvalTimeout = approvalTimeoutSeconds * 1000; // Convert to milliseconds this.startCleanupTimer(); } createApprovalRequest( duckName: string, mcpServer: string, toolName: string, args: Record<string, unknown> ): ApprovalRequest { const id = randomUUID(); const now = Date.now(); const request: ApprovalRequest = { id, timestamp: now, duckName, mcpServer, toolName, arguments: args, status: 'pending', expiresAt: now + this.approvalTimeout, }; this.pendingApprovals.set(id, request); const safeMessage = SafeLogger.createApprovalMessage(duckName, mcpServer, toolName, args); logger.info(`Created approval request ${id} for ${duckName} to call ${mcpServer}:${toolName}`); SafeLogger.debug(`Approval request details:`, { id, duckName, mcpServer, toolName, safeMessage }); return request; } getApprovalRequest(id: string): ApprovalRequest | undefined { const request = this.pendingApprovals.get(id); // Check if expired if (request && Date.now() > request.expiresAt && request.status === 'pending') { request.status = 'expired'; logger.info(`Approval request ${id} has expired`); } return request; } getApprovalStatus(id: string): ApprovalStatus | undefined { const request = this.getApprovalRequest(id); return request?.status; } approveRequest(id: string, approvedBy: string = 'user'): boolean { const request = this.getApprovalRequest(id); if (!request) { logger.warn(`Approval request ${id} not found`); return false; } if (request.status !== 'pending') { logger.warn(`Approval request ${id} is not pending (status: ${request.status})`); return false; } if (Date.now() > request.expiresAt) { request.status = 'expired'; logger.warn(`Approval request ${id} has expired`); return false; } request.status = 'approved'; request.approvedBy = approvedBy; // Mark tool as approved for this session const sessionKey = this.createSessionKey(request.duckName, request.mcpServer, request.toolName); this.approvedToolsForSession.add(sessionKey); logger.info(`Approval request ${id} approved by ${approvedBy} - tool ${sessionKey} now approved for session`); return true; } denyRequest(id: string, reason?: string): boolean { const request = this.getApprovalRequest(id); if (!request) { logger.warn(`Approval request ${id} not found`); return false; } if (request.status !== 'pending') { logger.warn(`Approval request ${id} is not pending (status: ${request.status})`); return false; } request.status = 'denied'; request.deniedReason = reason; logger.info(`Approval request ${id} denied${reason ? `: ${reason}` : ''}`); return true; } getPendingApprovals(): ApprovalRequest[] { // Clean up expired requests first this.cleanupExpired(); return Array.from(this.pendingApprovals.values()) .filter(request => request.status === 'pending'); } getAllApprovals(): ApprovalRequest[] { // Clean up expired requests first this.cleanupExpired(); return Array.from(this.pendingApprovals.values()); } getApprovalsByDuck(duckName: string): ApprovalRequest[] { return Array.from(this.pendingApprovals.values()) .filter(request => request.duckName === duckName); } cleanupExpired(): number { const now = Date.now(); let cleanedUp = 0; for (const [id, request] of this.pendingApprovals.entries()) { if (now > request.expiresAt && request.status === 'pending') { request.status = 'expired'; logger.debug(`Marked approval request ${id} as expired`); cleanedUp++; } } return cleanedUp; } private startCleanupTimer(): void { // Clean up expired requests every minute this.cleanupInterval = setInterval(() => { const cleaned = this.cleanupExpired(); if (cleaned > 0) { logger.debug(`Cleaned up ${cleaned} expired approval requests`); } }, 60000); } shutdown(): void { if (this.cleanupInterval) { clearInterval(this.cleanupInterval); this.cleanupInterval = null; } } // Session-based approval methods private createSessionKey(duckName: string, mcpServer: string, toolName: string): string { return `${duckName}:${mcpServer}:${toolName}`; } isToolApprovedForSession(duckName: string, mcpServer: string, toolName: string): boolean { const sessionKey = this.createSessionKey(duckName, mcpServer, toolName); return this.approvedToolsForSession.has(sessionKey); } markToolAsApprovedForSession(duckName: string, mcpServer: string, toolName: string): void { const sessionKey = this.createSessionKey(duckName, mcpServer, toolName); this.approvedToolsForSession.add(sessionKey); logger.info(`Tool ${sessionKey} marked as approved for session`); } clearSessionApprovals(): void { const count = this.approvedToolsForSession.size; this.approvedToolsForSession.clear(); logger.info(`Cleared ${count} session approvals`); } getSessionApprovals(): string[] { return Array.from(this.approvedToolsForSession); } // For debugging/admin purposes getStats(): { total: number; pending: number; approved: number; denied: number; expired: number; } { this.cleanupExpired(); const all = Array.from(this.pendingApprovals.values()); return { total: all.length, pending: all.filter(r => r.status === 'pending').length, approved: all.filter(r => r.status === 'approved').length, denied: all.filter(r => r.status === 'denied').length, expired: all.filter(r => r.status === 'expired').length, }; } }

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/nesquikm/mcp-rubber-duck'

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