Skip to main content
Glama

HiveFlow MCP Server

Official
by hiveflowai
index.js28.8 kB
#!/usr/bin/env node const { Server } = require('@modelcontextprotocol/sdk/server/index.js'); const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js'); const { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListResourceTemplatesRequestSchema, } = require('@modelcontextprotocol/sdk/types.js'); const axios = require('axios'); const { Command } = require('commander'); class HiveFlowMCPServer { constructor(config) { this.config = config; // Crear servidor MCP this.server = new Server({ name: 'hiveflow-mcp-server', version: '1.0.0' }, { capabilities: { tools: {}, resources: {} } }); // Configurar cliente HTTP para HiveFlow this.hiveflowClient = axios.create({ baseURL: this.config.apiUrl, headers: { 'Authorization': `ApiKey ${this.config.apiKey}`, 'Content-Type': 'application/json' }, timeout: 30000 }); this.setupHandlers(); } setupHandlers() { // Registrar herramientas disponibles this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'create_flow', description: 'Crea un nuevo flujo de trabajo en HiveFlow', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'Nombre del flujo' }, description: { type: 'string', description: 'Descripción del flujo' }, nodes: { type: 'array', description: 'Nodos del flujo (opcional)', items: { type: 'object' } } }, required: ['name', 'description'] } }, { name: 'list_flows', description: 'Lista todos los flujos de trabajo del usuario', inputSchema: { type: 'object', properties: { status: { type: 'string', enum: ['active', 'paused', 'stopped', 'draft'], description: 'Filtrar por estado del flujo (opcional)' }, limit: { type: 'number', description: 'Límite de resultados (opcional)', default: 50 } } } }, { name: 'get_flow', description: 'Obtiene detalles de un flujo específico', inputSchema: { type: 'object', properties: { flowId: { type: 'string', description: 'ID del flujo' } }, required: ['flowId'] } }, { name: 'execute_flow', description: 'Ejecuta un flujo de trabajo específico', inputSchema: { type: 'object', properties: { flowId: { type: 'string', description: 'ID del flujo a ejecutar' }, inputs: { type: 'object', description: 'Inputs opcionales para el flujo' } }, required: ['flowId'] } }, { name: 'pause_flow', description: 'Pausa un flujo activo', inputSchema: { type: 'object', properties: { flowId: { type: 'string', description: 'ID del flujo a pausar' } }, required: ['flowId'] } }, { name: 'resume_flow', description: 'Reanuda un flujo pausado', inputSchema: { type: 'object', properties: { flowId: { type: 'string', description: 'ID del flujo a reanudar' } }, required: ['flowId'] } }, { name: 'list_mcp_servers', description: 'Lista los servidores MCP configurados en HiveFlow', inputSchema: { type: 'object', properties: {} } }, { name: 'create_mcp_server', description: 'Registra un nuevo servidor MCP en HiveFlow', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'Nombre único del servidor MCP' }, command: { type: 'string', description: 'Comando para ejecutar el servidor' }, args: { type: 'array', items: { type: 'string' }, description: 'Argumentos del comando' }, description: { type: 'string', description: 'Descripción del servidor' } }, required: ['name', 'command'] } }, { name: 'get_flow_executions', description: 'Obtiene el historial de ejecuciones de un flujo', inputSchema: { type: 'object', properties: { flowId: { type: 'string', description: 'ID del flujo' }, limit: { type: 'number', description: 'Límite de resultados', default: 10 } }, required: ['flowId'] } } ] }; }); // Implementar ejecución de herramientas this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case 'create_flow': return await this.createFlow(args); case 'list_flows': return await this.listFlows(args); case 'get_flow': return await this.getFlow(args); case 'execute_flow': return await this.executeFlow(args); case 'pause_flow': return await this.pauseFlow(args); case 'resume_flow': return await this.resumeFlow(args); case 'list_mcp_servers': return await this.listMCPServers(); case 'create_mcp_server': return await this.createMCPServer(args); case 'get_flow_executions': return await this.getFlowExecutions(args); default: throw new Error(`Herramienta desconocida: ${name}`); } } catch (error) { return { content: [ { type: 'text', text: `❌ Error ejecutando ${name}: ${error.response?.data?.error || error.message}` } ], isError: true }; } }); // Registrar recursos disponibles this.server.setRequestHandler(ListResourcesRequestSchema, async () => { return { resources: [ { uri: 'hiveflow://flows', name: 'HiveFlow Flows', description: 'List of all workflow flows', mimeType: 'application/json' }, { uri: 'hiveflow://mcp-servers', name: 'MCP Servers', description: 'List of configured MCP servers', mimeType: 'application/json' }, { uri: 'hiveflow://executions', name: 'Flow Executions', description: 'Flow execution history', mimeType: 'application/json' } ] }; }); // Registrar templates de recursos this.server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => { return { resourceTemplates: [ { uriTemplate: 'hiveflow://flows/{flowId}', name: 'Specific Flow', description: 'Get details of a specific flow by its ID', mimeType: 'application/json' }, { uriTemplate: 'hiveflow://flows/{flowId}/executions', name: 'Flow Executions', description: 'Get execution history of a specific flow', mimeType: 'application/json' } ] }; }); // Implementar lectura de recursos this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => { try { const { uri } = request.params; console.error(`🔍 [DEBUG] Solicitando recurso: ${uri}`); // Función de emergencia que SIEMPRE devuelve algo válido const emergencyResponse = (errorMsg = 'Error desconocido') => { return { contents: [ { uri: uri || 'unknown', mimeType: 'application/json', text: JSON.stringify({ error: errorMsg, timestamp: new Date().toISOString(), uri: uri }, null, 2) } ] }; }; // Función helper para asegurar que siempre devolvemos un resultado válido const createValidResponse = (data, errorMsg = null) => { console.error(`🔍 [DEBUG] createValidResponse llamada con:`, { hasData: !!data, dataType: typeof data, errorMsg: errorMsg, uri: uri }); let text; try { if (errorMsg) { text = JSON.stringify({ error: errorMsg, uri: uri, timestamp: new Date().toISOString(), data: data || null }, null, 2); } else { text = JSON.stringify(data || [], null, 2); } console.error(`🔍 [DEBUG] JSON.stringify resultado:`, { textType: typeof text, textLength: text?.length, textPreview: text?.substring(0, 100) }); } catch (jsonError) { console.error(`🔍 [DEBUG] Error en JSON.stringify:`, jsonError); text = JSON.stringify({ error: 'Error al serializar datos: ' + jsonError.message, uri: uri, timestamp: new Date().toISOString() }, null, 2); } // Asegurar que text nunca sea undefined o null if (typeof text !== 'string' || text.length === 0) { console.error(`🔍 [DEBUG] Text inválido, usando fallback`); text = JSON.stringify({ error: 'Respuesta vacía', uri: uri, timestamp: new Date().toISOString() }, null, 2); } const result = { contents: [ { uri, mimeType: 'application/json', text: text } ] }; console.error(`🔍 [DEBUG] Respuesta final creada:`, { hasContents: !!result.contents, contentsLength: result.contents?.length, hasFirstContent: !!result.contents?.[0], hasText: !!result.contents?.[0]?.text, textLength: result.contents?.[0]?.text?.length, textType: typeof result.contents?.[0]?.text, uri: result.contents?.[0]?.uri, mimeType: result.contents?.[0]?.mimeType }); // Validación final antes de devolver if (!result.contents || !result.contents[0] || typeof result.contents[0].text !== 'string') { console.error(`🔍 [DEBUG] ERROR: Resultado inválido detectado!`, result); // En lugar de lanzar error, crear un resultado de emergencia válido return { contents: [ { uri: uri || 'unknown', mimeType: 'application/json', text: JSON.stringify({ error: 'Error interno: no se pudo generar respuesta válida', timestamp: new Date().toISOString(), debug: 'createValidResponse falló en validación final' }, null, 2) } ] }; } return result; }; try { switch (uri) { case 'hiveflow://flows': console.error(`🔍 [DEBUG] Obteniendo flujos de: ${this.hiveflowClient.defaults.baseURL}/api/flows`); try { const flowsResponse = await this.hiveflowClient.get('/api/flows'); console.error(`🔍 [DEBUG] Respuesta flujos:`, { status: flowsResponse.status, hasData: !!flowsResponse.data, dataKeys: flowsResponse.data ? Object.keys(flowsResponse.data) : [], success: flowsResponse.data?.success, dataLength: flowsResponse.data?.data?.length }); // Verificar que la respuesta sea exitosa y tenga datos if (flowsResponse.data && flowsResponse.data.success) { const flows = flowsResponse.data.data || []; return createValidResponse(flows); } else { return createValidResponse([], 'No se pudieron obtener los flujos: ' + (flowsResponse.data?.message || 'Error desconocido')); } } catch (apiError) { console.error(`🔍 [DEBUG] Error de API en flujos:`, { message: apiError.message, status: apiError.response?.status, statusText: apiError.response?.statusText, code: apiError.code }); let errorMessage = 'Error de conexión con el backend'; if (apiError.code === 'ECONNREFUSED') { errorMessage = 'Backend no disponible en ' + this.hiveflowClient.defaults.baseURL + '. Asegúrate de que esté corriendo.'; } else if (apiError.response?.data?.message) { errorMessage = apiError.response.data.message; } return createValidResponse([], errorMessage); } case 'hiveflow://mcp-servers': console.error(`🔍 [DEBUG] Obteniendo servidores MCP...`); try { const serversResponse = await this.hiveflowClient.get('/api/mcp/servers'); if (serversResponse.data && serversResponse.data.success) { const servers = serversResponse.data.servers || []; return createValidResponse(servers); } else { return createValidResponse([], 'No se pudieron obtener los servidores MCP: ' + (serversResponse.data?.message || 'Error desconocido')); } } catch (apiError) { let errorMessage = 'Error de conexión con el backend'; if (apiError.code === 'ECONNREFUSED') { errorMessage = 'Backend no disponible. Asegúrate de que esté corriendo en ' + this.hiveflowClient.defaults.baseURL; } return createValidResponse([], errorMessage); } case 'hiveflow://executions': console.error(`🔍 [DEBUG] Obteniendo ejecuciones...`); try { // Primero obtener todos los flujos const flowsResponse = await this.hiveflowClient.get('/api/flows'); if (flowsResponse.data && flowsResponse.data.success) { const flows = flowsResponse.data.data || []; let allExecutions = []; // Para cada flujo, obtener sus procesos/ejecuciones for (const flow of flows.slice(0, 5)) { // Limitamos a 5 flujos para evitar demasiadas peticiones try { const processesResponse = await this.hiveflowClient.get(`/api/flows/${flow._id}/processes?limit=10`); if (processesResponse.data && processesResponse.data.success) { const processes = processesResponse.data.processes || []; processes.forEach(process => { allExecutions.push({ flowId: flow._id, flowName: flow.name, processId: process.processId, status: process.status, startTime: process.startTime, endTime: process.endTime, duration: process.duration }); }); } } catch (processError) { console.error(`🔍 [DEBUG] Error obteniendo procesos para flujo ${flow._id}:`, processError.message); } } return createValidResponse(allExecutions); } else { return createValidResponse([], 'No se pudieron obtener los flujos para buscar ejecuciones: ' + (flowsResponse.data?.message || 'Error desconocido')); } } catch (apiError) { let errorMessage = 'Error de conexión con el backend'; if (apiError.code === 'ECONNREFUSED') { errorMessage = 'Backend no disponible. Asegúrate de que esté corriendo en ' + this.hiveflowClient.defaults.baseURL; } return createValidResponse([], errorMessage); } default: console.error(`🔍 [DEBUG] Recurso no encontrado: ${uri}`); // Manejar templates de recursos con parámetros if (uri.startsWith('hiveflow://flows/') && uri.includes('/executions')) { // Patrón: hiveflow://flows/{flowId}/executions const flowId = uri.replace('hiveflow://flows/', '').replace('/executions', ''); console.error(`🔍 [DEBUG] Obteniendo ejecuciones para flujo específico: ${flowId}`); try { const response = await this.hiveflowClient.get(`/api/flows/${flowId}/processes`, { params: { limit: 20 } }); if (response.data && response.data.success) { const processes = response.data.processes || []; return createValidResponse(processes); } else { return createValidResponse([], `No se pudieron obtener las ejecuciones del flujo ${flowId}: ` + (response.data?.message || 'Error desconocido')); } } catch (apiError) { let errorMessage = 'Error de conexión con el backend'; if (apiError.code === 'ECONNREFUSED') { errorMessage = 'Backend no disponible. Asegúrate de que esté corriendo en ' + this.hiveflowClient.defaults.baseURL; } return createValidResponse([], errorMessage); } } else if (uri.startsWith('hiveflow://flows/') && !uri.includes('/executions')) { // Patrón: hiveflow://flows/{flowId} const flowId = uri.replace('hiveflow://flows/', ''); console.error(`🔍 [DEBUG] Obteniendo flujo específico: ${flowId}`); try { const response = await this.hiveflowClient.get(`/api/flows/${flowId}`); if (response.data && response.data.success) { const flow = response.data.data || response.data.flow; return createValidResponse(flow); } else { return createValidResponse(null, `No se pudo obtener el flujo ${flowId}: ` + (response.data?.message || 'Error desconocido')); } } catch (apiError) { console.error(`🔍 [DEBUG] Error de API obteniendo flujo ${flowId}:`, { message: apiError.message, status: apiError.response?.status, statusText: apiError.response?.statusText, responseData: apiError.response?.data }); let errorMessage = 'Error de conexión con el backend'; if (apiError.code === 'ECONNREFUSED') { errorMessage = 'Backend no disponible. Asegúrate de que esté corriendo en ' + this.hiveflowClient.defaults.baseURL; } else if (apiError.response?.status === 404) { errorMessage = `Flujo con ID ${flowId} no encontrado`; } else if (apiError.response?.data?.message) { errorMessage = apiError.response.data.message; } return createValidResponse(null, errorMessage); } } else { return createValidResponse({ availableResources: [ 'hiveflow://flows', 'hiveflow://mcp-servers', 'hiveflow://executions' ], availableTemplates: [ 'hiveflow://flows/{flowId}', 'hiveflow://flows/{flowId}/executions' ] }, `Recurso no encontrado: ${uri}`); } } } catch (error) { console.error(`🔍 [DEBUG] Error general en recursos:`, { message: error.message, stack: error.stack }); const errorResult = createValidResponse(null, 'Error interno del servidor MCP: ' + error.message); console.error(`🔍 [DEBUG] Devolviendo resultado de error:`, errorResult); return errorResult; } } catch (criticalError) { console.error(`🔍 [DEBUG] ERROR CRÍTICO EN HANDLER:`, criticalError); // Respuesta de emergencia absoluta return { contents: [ { uri: request?.params?.uri || 'unknown', mimeType: 'application/json', text: JSON.stringify({ error: 'Error crítico en el servidor MCP', message: criticalError.message, timestamp: new Date().toISOString() }, null, 2) } ] }; } }); } // Métodos de implementación de herramientas async createFlow(args) { const response = await this.hiveflowClient.post('/api/flows', { name: args.name, description: args.description, nodes: args.nodes || [], edges: [], status: 'draft' }); const flow = response.data.data; return { content: [ { type: 'text', text: `✅ Flujo "${args.name}" creado exitosamente.\nID: ${flow._id}\nEstado: ${flow.status}` } ] }; } async listFlows(args) { const params = {}; if (args.status) params.status = args.status; if (args.limit) params.limit = args.limit; const response = await this.hiveflowClient.get('/api/flows', { params }); const flows = response.data.data || []; const flowsList = flows.map(flow => `• ${flow.name} (${flow._id}) - Estado: ${flow.status || 'draft'}` ).join('\n'); return { content: [ { type: 'text', text: `📋 Flujos encontrados (${flows.length}):\n\n${flowsList || 'No hay flujos disponibles'}` } ] }; } async getFlow(args) { const response = await this.hiveflowClient.get(`/api/flows/${args.flowId}`); const flow = response.data.flow; return { content: [ { type: 'text', text: `📊 Detalles del flujo "${flow.name}":\n• ID: ${flow._id}\n• Estado: ${flow.status || 'draft'}\n• Nodos: ${flow.nodes?.length || 0}\n• Descripción: ${flow.description || 'Sin descripción'}\n• Última actualización: ${flow.updatedAt || 'N/A'}` } ] }; } async executeFlow(args) { const response = await this.hiveflowClient.post(`/api/flows/${args.flowId}/execute`, { inputs: args.inputs || {} }); return { content: [ { type: 'text', text: `🚀 Flujo ejecutado exitosamente.\nExecution ID: ${response.data.executionId || 'N/A'}\nEstado: ${response.data.status || 'iniciado'}` } ] }; } async pauseFlow(args) { const response = await this.hiveflowClient.post(`/api/flows/${args.flowId}/pause`); return { content: [ { type: 'text', text: `⏸️ Flujo pausado exitosamente.\nEstado: ${response.data.status || 'pausado'}` } ] }; } async resumeFlow(args) { const response = await this.hiveflowClient.post(`/api/flows/${args.flowId}/resume`); return { content: [ { type: 'text', text: `▶️ Flujo reanudado exitosamente.\nEstado: ${response.data.status || 'activo'}` } ] }; } async listMCPServers() { const response = await this.hiveflowClient.get('/api/mcp/servers'); const servers = response.data.servers || []; const serversList = servers.map(server => `• ${server.name} - Estado: ${server.status} (${server.isConnected ? 'Conectado' : 'Desconectado'})` ).join('\n'); return { content: [ { type: 'text', text: `🔌 Servidores MCP (${servers.length}):\n\n${serversList || 'No hay servidores MCP configurados'}` } ] }; } async createMCPServer(args) { const response = await this.hiveflowClient.post('/api/mcp/servers', { name: args.name, command: args.command, args: args.args || [], description: args.description || '' }); return { content: [ { type: 'text', text: `✅ Servidor MCP "${args.name}" registrado exitosamente.\nComando: ${args.command}\nEstado: registrado` } ] }; } async getFlowExecutions(args) { try { const response = await this.hiveflowClient.get(`/api/flows/${args.flowId}/processes`, { params: { limit: args.limit || 10 } }); if (response.data && response.data.success) { const processes = response.data.processes || []; const executionsList = processes.map(process => `• ${process.processId || process._id} - Estado: ${process.status} - ${process.startTime} ${process.endTime ? `(${process.duration}ms)` : '(en progreso)'}` ).join('\n'); return { content: [ { type: 'text', text: `📈 Ejecuciones del flujo (${processes.length}):\n\n${executionsList || 'No hay ejecuciones'}` } ] }; } else { return { content: [ { type: 'text', text: `❌ Error: ${response.data?.message || 'No se pudieron obtener las ejecuciones'}` } ] }; } } catch (error) { return { content: [ { type: 'text', text: `❌ Error de conexión: ${error.message}` } ] }; } } async start() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('🚀 HiveFlow MCP Server iniciado'); } } // CLI principal const program = new Command(); program .name('hiveflow-mcp') .description('HiveFlow MCP Server - Connect your AI assistant to HiveFlow') .version('1.0.0'); program .option('--api-url <url>', 'HiveFlow API URL', process.env.HIVEFLOW_API_URL || 'http://localhost:3001') .option('--api-key <key>', 'HiveFlow API Key', process.env.HIVEFLOW_API_KEY) .option('--instance-id <id>', 'HiveFlow Instance ID (for multi-tenant)', process.env.HIVEFLOW_INSTANCE_ID) .action(async (options) => { if (!options.apiKey) { console.error('❌ Error: HIVEFLOW_API_KEY is required'); console.error('💡 Set the environment variable or use --api-key flag'); process.exit(1); } const config = { apiUrl: options.apiUrl, apiKey: options.apiKey, instanceId: options.instanceId }; const server = new HiveFlowMCPServer(config); await server.start(); }); if (require.main === module) { program.parse(); }

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/hiveflowai/hiveflow-mcp-server'

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