Merge pull request #283 from czlonkowski/update/n8n-and-templates-20251007

Update n8n to v1.114.3 and optimize template fetching (v2.17.2)
This commit is contained in:
Romuald Członkowski
2025-10-07 15:07:43 +02:00
committed by GitHub
9 changed files with 355 additions and 563 deletions

View File

@@ -5,7 +5,7 @@
[![npm version](https://img.shields.io/npm/v/n8n-mcp.svg)](https://www.npmjs.com/package/n8n-mcp)
[![codecov](https://codecov.io/gh/czlonkowski/n8n-mcp/graph/badge.svg?token=YOUR_TOKEN)](https://codecov.io/gh/czlonkowski/n8n-mcp)
[![Tests](https://img.shields.io/badge/tests-3336%20passing-brightgreen.svg)](https://github.com/czlonkowski/n8n-mcp/actions)
[![n8n version](https://img.shields.io/badge/n8n-^1.113.3-orange.svg)](https://github.com/n8n-io/n8n)
[![n8n version](https://img.shields.io/badge/n8n-^1.114.3-orange.svg)](https://github.com/n8n-io/n8n)
[![Docker](https://img.shields.io/badge/docker-ghcr.io%2Fczlonkowski%2Fn8n--mcp-green.svg)](https://github.com/czlonkowski/n8n-mcp/pkgs/container/n8n-mcp)
[![Deploy on Railway](https://railway.com/button.svg)](https://railway.com/deploy/n8n-mcp?referralCode=n8n-mcp)

Binary file not shown.

826
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "n8n-mcp",
"version": "2.17.1",
"version": "2.17.2",
"description": "Integration between n8n workflow automation and Model Context Protocol (MCP)",
"main": "dist/index.js",
"bin": {
@@ -132,15 +132,15 @@
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.13.2",
"@n8n/n8n-nodes-langchain": "^1.112.2",
"@n8n/n8n-nodes-langchain": "^1.113.1",
"@supabase/supabase-js": "^2.57.4",
"dotenv": "^16.5.0",
"express": "^5.1.0",
"express-rate-limit": "^7.1.5",
"lru-cache": "^11.2.1",
"n8n": "^1.113.3",
"n8n-core": "^1.112.1",
"n8n-workflow": "^1.110.0",
"n8n": "^1.114.3",
"n8n-core": "^1.113.1",
"n8n-workflow": "^1.111.0",
"openai": "^4.77.0",
"sql.js": "^1.13.0",
"uuid": "^10.0.0",

View File

@@ -417,12 +417,28 @@ async function generateTemplateMetadata(db: any, service: TemplateService) {
} catch (error) {
console.warn(`Failed to parse workflow for template ${t.id}:`, error);
}
// Parse nodes_used safely
let nodes: string[] = [];
try {
if (t.nodes_used) {
nodes = JSON.parse(t.nodes_used);
// Ensure it's an array
if (!Array.isArray(nodes)) {
console.warn(`Template ${t.id} has invalid nodes_used (not an array), using empty array`);
nodes = [];
}
}
} catch (error) {
console.warn(`Failed to parse nodes_used for template ${t.id}:`, error);
nodes = [];
}
return {
templateId: t.id,
name: t.name,
description: t.description,
nodes: JSON.parse(t.nodes_used),
nodes: nodes,
workflow
};
});

View File

@@ -45,19 +45,22 @@ export class TemplateFetcher {
* Fetch all templates and filter to last 12 months
* This fetches ALL pages first, then applies date filter locally
*/
async fetchTemplates(progressCallback?: (current: number, total: number) => void): Promise<TemplateWorkflow[]> {
async fetchTemplates(progressCallback?: (current: number, total: number) => void, sinceDate?: Date): Promise<TemplateWorkflow[]> {
const allTemplates = await this.fetchAllTemplates(progressCallback);
// Apply date filter locally after fetching all
const oneYearAgo = new Date();
oneYearAgo.setMonth(oneYearAgo.getMonth() - 12);
// Use provided date or default to 12 months ago
const cutoffDate = sinceDate || (() => {
const oneYearAgo = new Date();
oneYearAgo.setMonth(oneYearAgo.getMonth() - 12);
return oneYearAgo;
})();
const recentTemplates = allTemplates.filter((w: TemplateWorkflow) => {
const createdDate = new Date(w.createdAt);
return createdDate >= oneYearAgo;
return createdDate >= cutoffDate;
});
logger.info(`Filtered to ${recentTemplates.length} templates from last 12 months (out of ${allTemplates.length} total)`);
logger.info(`Filtered to ${recentTemplates.length} templates since ${cutoffDate.toISOString().split('T')[0]} (out of ${allTemplates.length} total)`);
return recentTemplates;
}

View File

@@ -442,7 +442,19 @@ export class TemplateRepository {
const rows = this.db.prepare('SELECT id FROM templates').all() as { id: number }[];
return new Set(rows.map(r => r.id));
}
/**
* Get the most recent template creation date
* Used in update mode to fetch only newer templates
*/
getMostRecentTemplateDate(): Date | null {
const result = this.db.prepare('SELECT MAX(created_at) as max_date FROM templates').get() as { max_date: string | null } | undefined;
if (!result || !result.max_date) {
return null;
}
return new Date(result.max_date);
}
/**
* Check if a template exists in the database
*/

View File

@@ -319,22 +319,38 @@ export class TemplateService {
// Get existing template IDs if in update mode
let existingIds: Set<number> = new Set();
let sinceDate: Date | undefined;
if (mode === 'update') {
existingIds = this.repository.getExistingTemplateIds();
logger.info(`Update mode: Found ${existingIds.size} existing templates in database`);
// Get most recent template date and fetch only templates from last 2 weeks
const mostRecentDate = this.repository.getMostRecentTemplateDate();
if (mostRecentDate) {
// Fetch templates from 2 weeks before the most recent template
sinceDate = new Date(mostRecentDate);
sinceDate.setDate(sinceDate.getDate() - 14);
logger.info(`Update mode: Fetching templates since ${sinceDate.toISOString().split('T')[0]} (2 weeks before most recent)`);
} else {
// No templates yet, fetch from last 2 weeks
sinceDate = new Date();
sinceDate.setDate(sinceDate.getDate() - 14);
logger.info(`Update mode: No existing templates, fetching from last 2 weeks`);
}
} else {
// Clear existing templates in rebuild mode
this.repository.clearTemplates();
logger.info('Rebuild mode: Cleared existing templates');
}
// Fetch template list
logger.info(`Fetching template list from n8n.io (mode: ${mode})`);
const templates = await fetcher.fetchTemplates((current, total) => {
progressCallback?.('Fetching template list', current, total);
});
}, sinceDate);
logger.info(`Found ${templates.length} templates from last 12 months`);
logger.info(`Found ${templates.length} templates matching date criteria`);
// Filter to only new templates if in update mode
let templatesToFetch = templates;

View File

@@ -79,6 +79,7 @@ describe('TemplateService', () => {
getTemplateCount: vi.fn(),
getTemplateStats: vi.fn(),
getExistingTemplateIds: vi.fn(),
getMostRecentTemplateDate: vi.fn(),
clearTemplates: vi.fn(),
saveTemplate: vi.fn(),
rebuildTemplateFTS: vi.fn(),
@@ -471,6 +472,7 @@ describe('TemplateService', () => {
}));
mockRepository.getExistingTemplateIds = vi.fn().mockReturnValue(new Set([1, 2]));
mockRepository.getMostRecentTemplateDate = vi.fn().mockReturnValue(new Date('2025-09-01'));
mockRepository.saveTemplate = vi.fn();
mockRepository.rebuildTemplateFTS = vi.fn();
@@ -498,6 +500,7 @@ describe('TemplateService', () => {
}));
mockRepository.getExistingTemplateIds = vi.fn().mockReturnValue(new Set([1, 2]));
mockRepository.getMostRecentTemplateDate = vi.fn().mockReturnValue(new Date('2025-09-01'));
mockRepository.saveTemplate = vi.fn();
mockRepository.rebuildTemplateFTS = vi.fn();