UNPKG

n8n-mcp

Version:

Integration between n8n workflow automation and Model Context Protocol (MCP)

233 lines 10.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TemplateRepository = void 0; const logger_1 = require("../utils/logger"); const template_sanitizer_1 = require("../utils/template-sanitizer"); class TemplateRepository { constructor(db) { this.db = db; this.hasFTS5Support = false; this.sanitizer = new template_sanitizer_1.TemplateSanitizer(); this.initializeFTS5(); } initializeFTS5() { this.hasFTS5Support = this.db.checkFTS5Support(); if (this.hasFTS5Support) { try { const ftsExists = this.db.prepare(` SELECT name FROM sqlite_master WHERE type='table' AND name='templates_fts' `).get(); if (ftsExists) { logger_1.logger.info('FTS5 table already exists for templates'); try { const testCount = this.db.prepare('SELECT COUNT(*) as count FROM templates_fts').get(); logger_1.logger.info(`FTS5 enabled with ${testCount.count} indexed entries`); } catch (testError) { logger_1.logger.warn('FTS5 table exists but query failed:', testError); this.hasFTS5Support = false; return; } } else { logger_1.logger.info('Creating FTS5 virtual table for templates...'); this.db.exec(` CREATE VIRTUAL TABLE IF NOT EXISTS templates_fts USING fts5( name, description, content=templates ); `); this.db.exec(` CREATE TRIGGER IF NOT EXISTS templates_ai AFTER INSERT ON templates BEGIN INSERT INTO templates_fts(rowid, name, description) VALUES (new.id, new.name, new.description); END; `); this.db.exec(` CREATE TRIGGER IF NOT EXISTS templates_au AFTER UPDATE ON templates BEGIN UPDATE templates_fts SET name = new.name, description = new.description WHERE rowid = new.id; END; `); this.db.exec(` CREATE TRIGGER IF NOT EXISTS templates_ad AFTER DELETE ON templates BEGIN DELETE FROM templates_fts WHERE rowid = old.id; END; `); logger_1.logger.info('FTS5 support enabled for template search'); } } catch (error) { logger_1.logger.warn('Failed to initialize FTS5 for templates:', { message: error.message, code: error.code, stack: error.stack }); this.hasFTS5Support = false; } } else { logger_1.logger.info('FTS5 not available, using LIKE search for templates'); } } saveTemplate(workflow, detail, categories = []) { const stmt = this.db.prepare(` INSERT OR REPLACE INTO templates ( id, workflow_id, name, description, author_name, author_username, author_verified, nodes_used, workflow_json, categories, views, created_at, updated_at, url ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `); const nodeTypes = detail.workflow.nodes.map(n => n.type); const url = `https://n8n.io/workflows/${workflow.id}`; const { sanitized: sanitizedWorkflow, wasModified } = this.sanitizer.sanitizeWorkflow(detail.workflow); if (wasModified) { const detectedTokens = this.sanitizer.detectTokens(detail.workflow); logger_1.logger.warn(`Sanitized API tokens in template ${workflow.id}: ${workflow.name}`, { templateId: workflow.id, templateName: workflow.name, tokensFound: detectedTokens.length, tokenPreviews: detectedTokens.map(t => t.substring(0, 20) + '...') }); } stmt.run(workflow.id, workflow.id, workflow.name, workflow.description || '', workflow.user.name, workflow.user.username, workflow.user.verified ? 1 : 0, JSON.stringify(nodeTypes), JSON.stringify(sanitizedWorkflow), JSON.stringify(categories), workflow.totalViews || 0, workflow.createdAt, workflow.createdAt, url); } getTemplatesByNodes(nodeTypes, limit = 10) { const conditions = nodeTypes.map(() => "nodes_used LIKE ?").join(" OR "); const query = ` SELECT * FROM templates WHERE ${conditions} ORDER BY views DESC, created_at DESC LIMIT ? `; const params = [...nodeTypes.map(n => `%"${n}"%`), limit]; return this.db.prepare(query).all(...params); } getTemplate(templateId) { const row = this.db.prepare(` SELECT * FROM templates WHERE id = ? `).get(templateId); return row || null; } searchTemplates(query, limit = 20) { logger_1.logger.debug(`Searching templates for: "${query}" (FTS5: ${this.hasFTS5Support})`); if (!this.hasFTS5Support) { logger_1.logger.debug('Using LIKE search (FTS5 not available)'); return this.searchTemplatesLIKE(query, limit); } try { const ftsQuery = query.split(' ').map(term => { const escaped = term.replace(/"/g, '""'); return `"${escaped}"`; }).join(' OR '); logger_1.logger.debug(`FTS5 query: ${ftsQuery}`); const results = this.db.prepare(` SELECT t.* FROM templates t JOIN templates_fts ON t.id = templates_fts.rowid WHERE templates_fts MATCH ? ORDER BY rank, t.views DESC LIMIT ? `).all(ftsQuery, limit); logger_1.logger.debug(`FTS5 search returned ${results.length} results`); return results; } catch (error) { logger_1.logger.warn('FTS5 template search failed, using LIKE fallback:', { message: error.message, query: query, ftsQuery: query.split(' ').map(term => `"${term}"`).join(' OR ') }); return this.searchTemplatesLIKE(query, limit); } } searchTemplatesLIKE(query, limit = 20) { const likeQuery = `%${query}%`; logger_1.logger.debug(`Using LIKE search with pattern: ${likeQuery}`); const results = this.db.prepare(` SELECT * FROM templates WHERE name LIKE ? OR description LIKE ? ORDER BY views DESC, created_at DESC LIMIT ? `).all(likeQuery, likeQuery, limit); logger_1.logger.debug(`LIKE search returned ${results.length} results`); return results; } getTemplatesForTask(task) { const taskNodeMap = { 'ai_automation': ['@n8n/n8n-nodes-langchain.openAi', '@n8n/n8n-nodes-langchain.agent', 'n8n-nodes-base.openAi'], 'data_sync': ['n8n-nodes-base.googleSheets', 'n8n-nodes-base.postgres', 'n8n-nodes-base.mysql'], 'webhook_processing': ['n8n-nodes-base.webhook', 'n8n-nodes-base.httpRequest'], 'email_automation': ['n8n-nodes-base.gmail', 'n8n-nodes-base.emailSend', 'n8n-nodes-base.emailReadImap'], 'slack_integration': ['n8n-nodes-base.slack', 'n8n-nodes-base.slackTrigger'], 'data_transformation': ['n8n-nodes-base.code', 'n8n-nodes-base.set', 'n8n-nodes-base.merge'], 'file_processing': ['n8n-nodes-base.readBinaryFile', 'n8n-nodes-base.writeBinaryFile', 'n8n-nodes-base.googleDrive'], 'scheduling': ['n8n-nodes-base.scheduleTrigger', 'n8n-nodes-base.cron'], 'api_integration': ['n8n-nodes-base.httpRequest', 'n8n-nodes-base.graphql'], 'database_operations': ['n8n-nodes-base.postgres', 'n8n-nodes-base.mysql', 'n8n-nodes-base.mongodb'] }; const nodes = taskNodeMap[task]; if (!nodes) { return []; } return this.getTemplatesByNodes(nodes, 10); } getAllTemplates(limit = 10) { return this.db.prepare(` SELECT * FROM templates ORDER BY views DESC, created_at DESC LIMIT ? `).all(limit); } getTemplateCount() { const result = this.db.prepare('SELECT COUNT(*) as count FROM templates').get(); return result.count; } getTemplateStats() { const count = this.getTemplateCount(); const avgViews = this.db.prepare('SELECT AVG(views) as avg FROM templates').get(); const topNodes = this.db.prepare(` SELECT nodes_used FROM templates ORDER BY views DESC LIMIT 100 `).all(); const nodeCount = {}; topNodes.forEach(t => { const nodes = JSON.parse(t.nodes_used); nodes.forEach((n) => { nodeCount[n] = (nodeCount[n] || 0) + 1; }); }); const topUsedNodes = Object.entries(nodeCount) .sort(([, a], [, b]) => b - a) .slice(0, 10) .map(([node, count]) => ({ node, count })); return { totalTemplates: count, averageViews: Math.round(avgViews.avg || 0), topUsedNodes }; } clearTemplates() { this.db.exec('DELETE FROM templates'); logger_1.logger.info('Cleared all templates from database'); } rebuildTemplateFTS() { if (!this.hasFTS5Support) { return; } try { this.db.exec('DELETE FROM templates_fts'); this.db.exec(` INSERT INTO templates_fts(rowid, name, description) SELECT id, name, description FROM templates `); const count = this.getTemplateCount(); logger_1.logger.info(`Rebuilt FTS5 index for ${count} templates`); } catch (error) { logger_1.logger.warn('Failed to rebuild template FTS5 index:', error); } } } exports.TemplateRepository = TemplateRepository; //# sourceMappingURL=template-repository.js.map