Skip to main content
Glama
NextJSIntegrationTool.ts16.9 kB
import { MCPTool } from 'mcp-framework'; import * as fs from 'fs'; import * as path from 'path'; import axios from 'axios'; import { z } from 'zod'; import { generateTypeScriptTypes } from './utils/typeGenerator.js'; interface NextJSIntegrationOptions { workflowId: string; workflowName: string; apiBasePath: string; outputDir: string; generateOpenAPI: boolean; generateTypes: boolean; includeWebhooks: boolean; } interface Endpoint { nodeId: string; nodeName: string; method: string; path: string; inputSchema: any; outputSchema: any; } interface WorkflowNode { id: string; name: string; type: string; parameters?: { httpMethod?: string; method?: string; path?: string; [key: string]: any; }; [key: string]: any; } interface Workflow { id: string; name: string; nodes: WorkflowNode[]; [key: string]: any; } // Interface simplifiée pour OpenAPI interface OpenAPIDocument { openapi: string; info: { title: string; version: string; description?: string; }; paths: Record<string, any>; components?: { schemas?: Record<string, any>; securitySchemes?: Record<string, any>; }; security?: Array<Record<string, any>>; } // Interface simplifiée pour les opérations OpenAPI interface PathItemObject { [method: string]: any; } // Interface simplifiée pour les opérations OpenAPI interface OperationObject { summary?: string; description?: string; tags?: string[]; requestBody?: any; responses?: Record<string, any>; } /** * Tool pour générer des intégrations NextJS pour les workflows n8n */ class NextJSIntegrationTool extends MCPTool<NextJSIntegrationOptions> { private n8nApiUrl: string; private n8nApiKey: string; /** * Nom de l'outil */ name = 'nextjs_integration'; /** * Description de l'outil */ description = 'Génère des intégrations NextJS pour les workflows n8n'; /** * Schéma de validation des paramètres */ schema = { workflowId: { type: z.string(), description: 'ID du workflow n8n à intégrer' }, workflowName: { type: z.string(), description: 'Nom du workflow n8n à intégrer' }, apiBasePath: { type: z.string(), description: 'Chemin de base de l\'API pour les endpoints générés' }, outputDir: { type: z.string(), description: 'Répertoire de sortie pour les fichiers générés' }, generateOpenAPI: { type: z.boolean(), description: 'Générer la documentation OpenAPI' }, generateTypes: { type: z.boolean(), description: 'Générer les types TypeScript' }, includeWebhooks: { type: z.boolean(), description: 'Inclure les webhooks dans la génération' } }; constructor() { super(); // Récupérer les variables d'environnement this.n8nApiUrl = process.env.N8N_API_URL || 'http://localhost:5678/api/v1'; this.n8nApiKey = process.env.N8N_API_KEY || ''; } /** * Exécute l'outil */ async execute(options: NextJSIntegrationOptions): Promise<any> { try { console.log(`Génération de l'intégration NextJS pour le workflow ${options.workflowName} (${options.workflowId})`); // Créer le répertoire de sortie s'il n'existe pas if (!fs.existsSync(options.outputDir)) { fs.mkdirSync(options.outputDir, { recursive: true }); } // Récupérer les détails du workflow const workflow = await this.getWorkflow(options.workflowId); if (!workflow) { throw new Error(`Workflow ${options.workflowId} non trouvé`); } // Générer les endpoints REST const endpoints = this.generateEndpoints(workflow, options); // Générer la documentation OpenAPI si demandé if (options.generateOpenAPI) { const openApiSpec = this.generateOpenAPISpec(workflow, endpoints, options); this.saveOpenAPISpec(openApiSpec, options.outputDir); } // Générer les types TypeScript si demandé if (options.generateTypes) { const types = this.generateTypeScriptInterfaces(workflow, endpoints); this.saveTypeScriptTypes(types, options.outputDir); } // Générer les fichiers d'intégration NextJS this.generateNextJSIntegration(workflow, endpoints, options); return { success: true, message: `Intégration NextJS générée avec succès dans ${options.outputDir}`, endpoints: endpoints.map(e => e.path) }; } catch (error: unknown) { console.error('Erreur lors de la génération de l\'intégration NextJS:', error); const errorMessage = error instanceof Error ? error.message : String(error); return { success: false, message: `Erreur: ${errorMessage}` }; } } /** * Récupère les détails d'un workflow depuis l'API n8n */ private async getWorkflow(workflowId: string): Promise<Workflow> { try { const response = await axios.get(`${this.n8nApiUrl}/workflows/${workflowId}`, { headers: { 'X-N8N-API-KEY': this.n8nApiKey } }); return response.data; } catch (error: unknown) { console.error(`Erreur lors de la récupération du workflow ${workflowId}:`, error); const errorMessage = error instanceof Error ? error.message : String(error); throw new Error(`Impossible de récupérer le workflow: ${errorMessage}`); } } /** * Génère les endpoints REST pour un workflow */ private generateEndpoints(workflow: Workflow, options: NextJSIntegrationOptions): Endpoint[] { const endpoints: Endpoint[] = []; // Analyser le workflow pour identifier les points d'entrée const webhookNodes = workflow.nodes.filter(node => node.type === 'n8n-nodes-base.webhook' || node.type === 'n8n-nodes-base.httpRequest' || (options.includeWebhooks && node.type.includes('Trigger')) ); // Générer un endpoint pour chaque nœud webhook webhookNodes.forEach(node => { const method = this.getNodeMethod(node); const path = this.getNodePath(node, options.apiBasePath); // Extraire les paramètres d'entrée et de sortie const inputSchema = this.extractInputSchema(node); const outputSchema = this.extractOutputSchema(node); endpoints.push({ nodeId: node.id, nodeName: node.name, method, path, inputSchema, outputSchema }); }); return endpoints; } /** * Détermine la méthode HTTP pour un nœud */ private getNodeMethod(node: WorkflowNode): string { if (node.type === 'n8n-nodes-base.webhook') { return (node.parameters?.httpMethod || 'GET').toUpperCase(); } if (node.type === 'n8n-nodes-base.httpRequest') { return (node.parameters?.method || 'GET').toUpperCase(); } // Par défaut, utiliser POST pour les autres types de nœuds return 'POST'; } /** * Détermine le chemin de l'endpoint pour un nœud */ private getNodePath(node: WorkflowNode, basePath: string): string { if (node.type === 'n8n-nodes-base.webhook' && node.parameters?.path) { return `${basePath}/${node.parameters.path.replace(/^\//, '')}`; } // Pour les autres types de nœuds, utiliser le nom du nœud return `${basePath}/${this.slugify(node.name)}`; } /** * Extrait le schéma d'entrée d'un nœud */ private extractInputSchema(node: WorkflowNode): any { // Logique pour extraire le schéma d'entrée return { type: 'object', properties: {} }; } /** * Extrait le schéma de sortie d'un nœud */ private extractOutputSchema(node: WorkflowNode): any { // Logique pour extraire le schéma de sortie return { type: 'object', properties: { success: { type: 'boolean' }, data: { type: 'object' } } }; } /** * Génère la spécification OpenAPI pour un workflow */ private generateOpenAPISpec(workflow: Workflow, endpoints: Endpoint[], options: NextJSIntegrationOptions): OpenAPIDocument { // Créer la structure de base de la spécification OpenAPI const openApiSpec: OpenAPIDocument = { openapi: '3.0.0', info: { title: `API pour ${workflow.name}`, version: '1.0.0', description: `API générée automatiquement pour le workflow n8n "${workflow.name}"` }, paths: {}, components: { schemas: {}, securitySchemes: { ApiKeyAuth: { type: 'apiKey', in: 'header', name: 'X-API-KEY' } } }, security: [ { ApiKeyAuth: [] } ] }; // Ajouter les endpoints à la spécification endpoints.forEach(endpoint => { const pathItem: PathItemObject = {}; // Créer l'objet d'opération pour la méthode HTTP const operation: OperationObject = { summary: `Endpoint pour ${endpoint.nodeName}`, description: `Exécute le nœud "${endpoint.nodeName}" du workflow "${workflow.name}"`, tags: [workflow.name], requestBody: { content: { 'application/json': { schema: endpoint.inputSchema } } }, responses: { '200': { description: 'Succès', content: { 'application/json': { schema: endpoint.outputSchema } } }, '400': { description: 'Requête invalide' }, '500': { description: 'Erreur serveur' } } }; // Ajouter l'opération à l'objet de chemin pathItem[endpoint.method.toLowerCase()] = operation; // Ajouter le chemin à la spécification openApiSpec.paths[endpoint.path] = pathItem; }); return openApiSpec; } /** * Sauvegarde la spécification OpenAPI dans un fichier */ private saveOpenAPISpec(openApiSpec: OpenAPIDocument, outputDir: string): void { const filePath = path.join(outputDir, 'openapi.json'); fs.writeFileSync(filePath, JSON.stringify(openApiSpec, null, 2)); console.log(`Spécification OpenAPI sauvegardée dans ${filePath}`); } /** * Génère les interfaces TypeScript pour un workflow */ private generateTypeScriptInterfaces(workflow: Workflow, endpoints: Endpoint[]): string { let types = '// Types générés automatiquement pour le workflow ' + workflow.name + '\n\n'; // Générer les types pour chaque endpoint endpoints.forEach(endpoint => { const inputTypeName = this.pascalCase(endpoint.nodeName) + 'Input'; const outputTypeName = this.pascalCase(endpoint.nodeName) + 'Output'; // Générer le type d'entrée types += `export interface ${inputTypeName} {\n`; types += generateTypeScriptTypes(endpoint.inputSchema); types += '}\n\n'; // Générer le type de sortie types += `export interface ${outputTypeName} {\n`; types += generateTypeScriptTypes(endpoint.outputSchema); types += '}\n\n'; }); return types; } /** * Sauvegarde les types TypeScript dans un fichier */ private saveTypeScriptTypes(types: string, outputDir: string): void { const filePath = path.join(outputDir, 'types.ts'); fs.writeFileSync(filePath, types); console.log(`Types TypeScript sauvegardés dans ${filePath}`); } /** * Génère les fichiers d'intégration NextJS */ private generateNextJSIntegration(workflow: Workflow, endpoints: Endpoint[], options: NextJSIntegrationOptions): void { // Créer le répertoire api s'il n'existe pas const apiDir = path.join(options.outputDir, 'api'); if (!fs.existsSync(apiDir)) { fs.mkdirSync(apiDir, { recursive: true }); } // Générer les fichiers de route pour chaque endpoint endpoints.forEach(endpoint => { this.generateApiRoute(endpoint, workflow, options); }); // Générer le client API this.generateApiClient(workflow, endpoints, options); } /** * Génère un fichier de route API NextJS pour un endpoint */ private generateApiRoute(endpoint: Endpoint, workflow: Workflow, options: NextJSIntegrationOptions): void { // Extraire le chemin relatif de l'endpoint const relativePath = endpoint.path.replace(options.apiBasePath, '').replace(/^\//, ''); // Créer le répertoire pour la route const routeDir = path.join(options.outputDir, 'api', relativePath); if (!fs.existsSync(routeDir)) { fs.mkdirSync(routeDir, { recursive: true }); } // Générer le contenu du fichier de route const routeContent = ` import { NextApiRequest, NextApiResponse } from 'next'; import axios from 'axios'; // Endpoint pour ${endpoint.nodeName} export default async function handler(req: NextApiRequest, res: NextApiResponse) { // Vérifier la méthode HTTP if (req.method !== '${endpoint.method}') { return res.status(405).json({ error: 'Méthode non autorisée' }); } try { // Récupérer les données de la requête const data = req.method === 'GET' ? req.query : req.body; // Appeler le workflow n8n const response = await axios.post( process.env.N8N_API_URL + '/workflows/${workflow.id}/execute', { data, workflowData: { id: '${workflow.id}', name: '${workflow.name}' } }, { headers: { 'X-N8N-API-KEY': process.env.N8N_API_KEY || '' } } ); // Renvoyer la réponse return res.status(200).json(response.data); } catch (error) { console.error('Erreur lors de l\\'exécution du workflow:', error); const errorMessage = error instanceof Error ? error.message : String(error); return res.status(500).json({ error: errorMessage }); } } `; // Écrire le fichier de route const routeFilePath = path.join(routeDir, 'index.ts'); fs.writeFileSync(routeFilePath, routeContent); console.log(`Route API générée dans ${routeFilePath}`); } /** * Génère un client API pour le workflow */ private generateApiClient(workflow: Workflow, endpoints: Endpoint[], options: NextJSIntegrationOptions): void { // Générer le contenu du client API let clientContent = ` // Client API généré automatiquement pour le workflow ${workflow.name} import axios, { AxiosInstance, AxiosRequestConfig } from 'axios'; export class ${this.pascalCase(workflow.name)}Client { private client: AxiosInstance; constructor(baseURL: string, apiKey?: string) { this.client = axios.create({ baseURL, headers: apiKey ? { 'X-API-KEY': apiKey } : {} }); } `; // Ajouter les méthodes pour chaque endpoint endpoints.forEach(endpoint => { const methodName = this.camelCase(endpoint.nodeName); const inputTypeName = this.pascalCase(endpoint.nodeName) + 'Input'; const outputTypeName = this.pascalCase(endpoint.nodeName) + 'Output'; clientContent += ` /** * ${endpoint.nodeName} * @param data Données d'entrée pour l'endpoint * @param options Options de requête Axios * @returns Résultat de l'exécution du workflow */ async ${methodName}(data: ${inputTypeName}, options?: AxiosRequestConfig): Promise<${outputTypeName}> { const response = await this.client.${endpoint.method.toLowerCase()}('${endpoint.path}', ${endpoint.method === 'GET' ? '{ params: data, ...options }' : 'data, options'}); return response.data; } `; }); clientContent += '}\n'; // Écrire le fichier client const clientFilePath = path.join(options.outputDir, 'api-client.ts'); fs.writeFileSync(clientFilePath, clientContent); console.log(`Client API généré dans ${clientFilePath}`); } /** * Convertit une chaîne en format slug (minuscules, tirets) */ private slugify(text: string): string { return text .toString() .toLowerCase() .replace(/\s+/g, '-') .replace(/[^\w\-]+/g, '') .replace(/\-\-+/g, '-') .replace(/^-+/, '') .replace(/-+$/, ''); } /** * Convertit une chaîne en format camelCase */ private camelCase(text: string): string { return text .replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) => index === 0 ? word.toLowerCase() : word.toUpperCase() ) .replace(/\s+/g, ''); } /** * Convertit une chaîne en format PascalCase */ private pascalCase(text: string): string { return text .replace(/(?:^\w|[A-Z]|\b\w)/g, (word) => word.toUpperCase()) .replace(/\s+/g, ''); } } export default NextJSIntegrationTool;

Latest Blog Posts

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/lowprofix/n8n-mcp-server'

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