feat: add n8n workflow templates as MCP tools
- Add 4 new MCP tools for workflow templates - Integrate with n8n.io API to fetch community templates - Filter templates to last 6 months only - Store templates in SQLite with full workflow JSON - Manual fetch system (not part of regular rebuild) - Support search by nodes, keywords, and task categories - Add fetch:templates and test:templates npm scripts - Update to v2.4.1 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
149
src/templates/template-fetcher.ts
Normal file
149
src/templates/template-fetcher.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import axios from 'axios';
|
||||
import { logger } from '../utils/logger';
|
||||
|
||||
export interface TemplateNode {
|
||||
id: number;
|
||||
name: string;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
export interface TemplateUser {
|
||||
id: number;
|
||||
name: string;
|
||||
username: string;
|
||||
verified: boolean;
|
||||
}
|
||||
|
||||
export interface TemplateWorkflow {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
totalViews: number;
|
||||
createdAt: string;
|
||||
user: TemplateUser;
|
||||
nodes: TemplateNode[];
|
||||
}
|
||||
|
||||
export interface TemplateDetail {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
views: number;
|
||||
createdAt: string;
|
||||
workflow: {
|
||||
nodes: any[];
|
||||
connections: any;
|
||||
settings?: any;
|
||||
};
|
||||
}
|
||||
|
||||
export class TemplateFetcher {
|
||||
private readonly baseUrl = 'https://api.n8n.io/api/templates';
|
||||
private readonly pageSize = 100;
|
||||
|
||||
async fetchTemplates(progressCallback?: (current: number, total: number) => void): Promise<TemplateWorkflow[]> {
|
||||
const sixMonthsAgo = new Date();
|
||||
sixMonthsAgo.setMonth(sixMonthsAgo.getMonth() - 6);
|
||||
|
||||
const allTemplates: TemplateWorkflow[] = [];
|
||||
let page = 1;
|
||||
let hasMore = true;
|
||||
|
||||
logger.info('Starting template fetch from n8n.io API');
|
||||
|
||||
while (hasMore) {
|
||||
try {
|
||||
const response = await axios.get(`${this.baseUrl}/search`, {
|
||||
params: {
|
||||
page,
|
||||
rows: this.pageSize,
|
||||
sort_by: 'last-updated'
|
||||
}
|
||||
});
|
||||
|
||||
const { workflows, totalWorkflows } = response.data;
|
||||
|
||||
// Filter templates by date
|
||||
const recentTemplates = workflows.filter((w: TemplateWorkflow) => {
|
||||
const createdDate = new Date(w.createdAt);
|
||||
return createdDate >= sixMonthsAgo;
|
||||
});
|
||||
|
||||
// If we hit templates older than 6 months, stop fetching
|
||||
if (recentTemplates.length < workflows.length) {
|
||||
hasMore = false;
|
||||
logger.info(`Reached templates older than 6 months at page ${page}`);
|
||||
}
|
||||
|
||||
allTemplates.push(...recentTemplates);
|
||||
|
||||
if (progressCallback) {
|
||||
progressCallback(allTemplates.length, Math.min(totalWorkflows, allTemplates.length + 500));
|
||||
}
|
||||
|
||||
// Check if there are more pages
|
||||
if (workflows.length < this.pageSize || allTemplates.length >= totalWorkflows) {
|
||||
hasMore = false;
|
||||
}
|
||||
|
||||
page++;
|
||||
|
||||
// Rate limiting - be nice to the API
|
||||
if (hasMore) {
|
||||
await this.sleep(500); // 500ms between requests
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`Error fetching templates page ${page}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(`Fetched ${allTemplates.length} templates from last 6 months`);
|
||||
return allTemplates;
|
||||
}
|
||||
|
||||
async fetchTemplateDetail(workflowId: number): Promise<TemplateDetail> {
|
||||
try {
|
||||
const response = await axios.get(`${this.baseUrl}/workflows/${workflowId}`);
|
||||
return response.data.workflow;
|
||||
} catch (error) {
|
||||
logger.error(`Error fetching template detail for ${workflowId}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async fetchAllTemplateDetails(
|
||||
workflows: TemplateWorkflow[],
|
||||
progressCallback?: (current: number, total: number) => void
|
||||
): Promise<Map<number, TemplateDetail>> {
|
||||
const details = new Map<number, TemplateDetail>();
|
||||
|
||||
logger.info(`Fetching details for ${workflows.length} templates`);
|
||||
|
||||
for (let i = 0; i < workflows.length; i++) {
|
||||
const workflow = workflows[i];
|
||||
|
||||
try {
|
||||
const detail = await this.fetchTemplateDetail(workflow.id);
|
||||
details.set(workflow.id, detail);
|
||||
|
||||
if (progressCallback) {
|
||||
progressCallback(i + 1, workflows.length);
|
||||
}
|
||||
|
||||
// Rate limiting
|
||||
await this.sleep(200); // 200ms between requests
|
||||
} catch (error) {
|
||||
logger.error(`Failed to fetch details for workflow ${workflow.id}:`, error);
|
||||
// Continue with other templates
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(`Successfully fetched ${details.size} template details`);
|
||||
return details;
|
||||
}
|
||||
|
||||
private sleep(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user