UNPKG

@venly/wallet-mcp

Version:

Production-ready MCP server enabling AI agents to perform Web3 wallet operations through Venly's blockchain infrastructure. Supports 14+ networks including Ethereum, Polygon, Arbitrum, and stablecoin operations.

223 lines 8.71 kB
/** * Persistent Workflow Storage * Stores workflow templates and executions to survive container restarts */ import fs from 'fs/promises'; import fsSync from 'fs'; import path from 'path'; import winston from 'winston'; export class WorkflowStorage { storageDir; templatesFile; executionsFile; logger; constructor(storageDir = '/app/data') { this.storageDir = storageDir; this.templatesFile = path.join(storageDir, 'workflow-templates.json'); this.executionsFile = path.join(storageDir, 'workflow-executions.json'); this.logger = winston.createLogger({ level: 'info', format: winston.format.combine(winston.format.timestamp(), winston.format.json()), defaultMeta: { service: 'workflow-storage' }, transports: [ new winston.transports.Console({ format: winston.format.simple() }) ] }); // Initialize storage synchronously this.initializeStorageSync(); } initializeStorageSync() { try { this.logger.info('Initializing workflow storage', { storageDir: this.storageDir, templatesFile: this.templatesFile, executionsFile: this.executionsFile, processUid: process.getuid ? process.getuid() : 'unknown', processGid: process.getgid ? process.getgid() : 'unknown' }); // Create storage directory if it doesn't exist if (!fsSync.existsSync(this.storageDir)) { this.logger.info('Creating storage directory', { storageDir: this.storageDir }); fsSync.mkdirSync(this.storageDir, { recursive: true }); } else { this.logger.info('Storage directory already exists', { storageDir: this.storageDir }); } // Check directory permissions try { fsSync.accessSync(this.storageDir, fsSync.constants.R_OK | fsSync.constants.W_OK); this.logger.info('Storage directory is readable and writable'); } catch (permError) { this.logger.error('Storage directory permission issue', { storageDir: this.storageDir, error: permError }); } // Initialize templates file if it doesn't exist if (!fsSync.existsSync(this.templatesFile)) { this.logger.info('Creating templates file', { templatesFile: this.templatesFile }); fsSync.writeFileSync(this.templatesFile, JSON.stringify({})); } else { this.logger.info('Templates file already exists', { templatesFile: this.templatesFile }); } // Initialize executions file if it doesn't exist if (!fsSync.existsSync(this.executionsFile)) { this.logger.info('Creating executions file', { executionsFile: this.executionsFile }); fsSync.writeFileSync(this.executionsFile, JSON.stringify({})); } else { this.logger.info('Executions file already exists', { executionsFile: this.executionsFile }); } this.logger.info('Workflow storage initialized successfully', { storageDir: this.storageDir, templatesFile: this.templatesFile, executionsFile: this.executionsFile }); } catch (error) { this.logger.error('Failed to initialize workflow storage', { error: error instanceof Error ? { message: error.message, stack: error.stack, code: error.code, errno: error.errno, path: error.path } : error, storageDir: this.storageDir, templatesFile: this.templatesFile, executionsFile: this.executionsFile }); // Don't throw the error, just log it and continue with in-memory storage this.logger.warn('Continuing with in-memory storage only due to initialization failure'); } } // Template storage methods async storeTemplate(template) { try { const templates = await this.loadTemplates(); templates[template.templateId] = template; await fs.writeFile(this.templatesFile, JSON.stringify(templates, null, 2)); this.logger.info('Template stored successfully', { templateId: template.templateId, name: template.name }); } catch (error) { this.logger.error('Failed to store template', { templateId: template.templateId, error }); throw error; } } async getTemplate(templateId) { try { const templates = await this.loadTemplates(); return templates[templateId]; } catch (error) { this.logger.error('Failed to get template', { templateId, error }); return undefined; } } async getAllTemplates() { try { return await this.loadTemplates(); } catch (error) { this.logger.error('Failed to get all templates', { error }); return {}; } } async deleteTemplate(templateId) { try { const templates = await this.loadTemplates(); delete templates[templateId]; await fs.writeFile(this.templatesFile, JSON.stringify(templates, null, 2)); this.logger.info('Template deleted successfully', { templateId }); } catch (error) { this.logger.error('Failed to delete template', { templateId, error }); throw error; } } async loadTemplates() { try { const data = await fs.readFile(this.templatesFile, 'utf-8'); return JSON.parse(data); } catch (error) { this.logger.warn('Failed to load templates, returning empty object', { error }); return {}; } } // Execution storage methods async storeExecution(execution) { try { const executions = await this.loadExecutions(); executions[execution.executionId] = execution; await fs.writeFile(this.executionsFile, JSON.stringify(executions, null, 2)); this.logger.debug('Execution stored successfully', { executionId: execution.executionId, status: execution.status }); } catch (error) { this.logger.error('Failed to store execution', { executionId: execution.executionId, error }); throw error; } } async getExecution(executionId) { try { const executions = await this.loadExecutions(); return executions[executionId]; } catch (error) { this.logger.error('Failed to get execution', { executionId, error }); return undefined; } } async updateExecution(_executionId, execution) { await this.storeExecution(execution); } async loadExecutions() { try { const data = await fs.readFile(this.executionsFile, 'utf-8'); return JSON.parse(data); } catch (error) { this.logger.warn('Failed to load executions, returning empty object', { error }); return {}; } } // Cleanup old executions (optional) async cleanupOldExecutions(maxAge = 7 * 24 * 60 * 60 * 1000) { try { const executions = await this.loadExecutions(); const cutoffTime = Date.now() - maxAge; let cleaned = 0; for (const [executionId, execution] of Object.entries(executions)) { const executionTime = new Date(execution.startedAt).getTime(); if (executionTime < cutoffTime) { delete executions[executionId]; cleaned++; } } if (cleaned > 0) { await fs.writeFile(this.executionsFile, JSON.stringify(executions, null, 2)); this.logger.info('Cleaned up old executions', { cleaned }); } } catch (error) { this.logger.error('Failed to cleanup old executions', { error }); } } } //# sourceMappingURL=WorkflowStorage.js.map