Skip to main content
Glama

n8n-workflow-builder-mcp

by ifmelate
storage.js7.84 kB
/** * Workflow Storage Module * * Provides filesystem-based storage capabilities for n8n workflows, * allowing workflows to be saved and retrieved from specified locations. */ const fs = require('fs').promises; const path = require('path'); const { v4: uuidv4 } = require('uuid'); const { logger } = require('../utils/logger'); const config = require('../../config/default'); // Default storage directory for workflows from config for backward compatibility only const DEFAULT_WORKFLOWS_DIR = path.resolve(process.cwd(), config.storage.workflowsPath); /** * Ensure a directory exists * * @param {string} dirPath - Directory path to ensure exists */ const ensureDirectoryExists = async (dirPath) => { try { await fs.mkdir(dirPath, { recursive: true }); return true; } catch (error) { logger.error('Failed to create directory', { path: dirPath, error: error.message }); throw error; } }; // Ensure the default directory exists for backward compatibility ensureDirectoryExists(DEFAULT_WORKFLOWS_DIR).catch(err => { logger.error('Critical error ensuring directory', { error: err.message }); }); /** * Workflow Storage Service */ const workflowStorage = { /** * Save a workflow to the filesystem * * @param {string} workflowId - Unique ID for the workflow * @param {Object} workflowData - Workflow data to save * @param {string} filePath - Required filepath specified by LLM * @returns {Promise<Object>} Result with success status and path */ async saveWorkflow(workflowId, workflowData, filePath) { try { if (!filePath) { throw new Error('filePath is required - workflow must be saved at the location specified by the LLM'); } // Make sure the path is valid - use absolute path or resolve relative path const targetPath = path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath); // Ensure the parent directory exists await ensureDirectoryExists(path.dirname(targetPath)); // Add timestamp if not present if (!workflowData.updatedAt) { workflowData.updatedAt = new Date().toISOString(); } // Write the workflow file await fs.writeFile(targetPath, JSON.stringify(workflowData, null, 2)); logger.info('Workflow saved successfully', { workflowId, path: targetPath }); return { success: true, path: targetPath }; } catch (error) { logger.error('Error saving workflow', { workflowId, error: error.message }); throw new Error(`Failed to save workflow: ${error.message}`); } }, /** * Load a workflow from a specific filepath * * @param {string} workflowIdOrPath - The complete filepath to the workflow * @returns {Promise<Object>} Workflow data or null if not found */ async loadWorkflow(workflowIdOrPath) { try { // Determine the file path - treat as a path by default let filePath = workflowIdOrPath; // For backward compatibility, check if it looks like just an ID if (!workflowIdOrPath.includes('/') && !workflowIdOrPath.includes('\\') && !workflowIdOrPath.endsWith('.json')) { logger.warn('Using workflow ID instead of filepath is deprecated, use complete filepath instead', { id: workflowIdOrPath }); filePath = path.join(DEFAULT_WORKFLOWS_DIR, `${workflowIdOrPath}.json`); } // Make sure the path is valid - use absolute path or resolve relative path const targetPath = path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath); const data = await fs.readFile(targetPath, 'utf8'); const workflow = JSON.parse(data); logger.info('Workflow loaded successfully', { path: targetPath }); return workflow; } catch (error) { if (error.code === 'ENOENT') { logger.warn('Workflow not found', { path: workflowIdOrPath }); return null; } logger.error('Error loading workflow', { path: workflowIdOrPath, error: error.message }); throw new Error(`Failed to load workflow: ${error.message}`); } }, /** * List workflows from a specific directory * * @param {string} [directoryPath] - Directory to list workflows from * @param {Object} [options] - List options like limit and offset * @returns {Promise<Array>} Array of workflow summary objects */ async listWorkflows(directoryPath = DEFAULT_WORKFLOWS_DIR, options = {}) { try { // Ensure the directory exists await ensureDirectoryExists(directoryPath); const { limit = 100, offset = 0 } = options; const files = await fs.readdir(directoryPath); const jsonFiles = files.filter(file => file.endsWith('.json')); // Apply pagination const paginatedFiles = jsonFiles.slice(offset, offset + limit); // Load each workflow file and extract summary information const workflowPromises = paginatedFiles.map(async (file) => { const fullPath = path.join(directoryPath, file); try { const data = await fs.readFile(fullPath, 'utf8'); const workflow = JSON.parse(data); return { id: workflow.id || path.basename(file, '.json'), name: workflow.name || 'Unnamed Workflow', description: workflow.description || '', updatedAt: workflow.updatedAt || null, path: fullPath }; } catch (error) { logger.error('Error reading workflow file', { path: fullPath, error: error.message }); return null; } }); // Wait for all files to be processed const workflows = await Promise.all(workflowPromises); // Filter out any null entries from failed reads return workflows.filter(workflow => workflow !== null); } catch (error) { logger.error('Error listing workflows', { directory: directoryPath, error: error.message }); throw new Error(`Failed to list workflows: ${error.message}`); } }, /** * Delete a workflow by ID or filepath * * @param {string} workflowIdOrPath - Workflow ID or complete filepath * @returns {Promise<Object>} Result with success status */ async deleteWorkflow(workflowIdOrPath) { let filePath; // Check if the input is a full path or just an ID if (workflowIdOrPath.includes('/') || workflowIdOrPath.includes('\\')) { filePath = workflowIdOrPath; } else { filePath = path.join(DEFAULT_WORKFLOWS_DIR, `${workflowIdOrPath}.json`); } try { await fs.unlink(filePath); logger.info('Workflow deleted successfully', { path: filePath }); return { success: true, path: filePath }; } catch (error) { if (error.code === 'ENOENT') { logger.warn('Workflow not found for deletion', { path: filePath }); return { success: false, error: 'Workflow not found' }; } logger.error('Error deleting workflow', { path: filePath, error: error.message }); throw new Error(`Failed to delete workflow: ${error.message}`); } } }; module.exports = { workflowStorage, DEFAULT_WORKFLOWS_DIR, ensureDirectoryExists };

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/ifmelate/n8n-workflow-builder-mcp'

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