UNPKG

vibe-coder-mcp

Version:

Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.

393 lines (392 loc) 15.7 kB
import express from 'express'; import cors from 'cors'; import logger from '../../logger.js'; import { AgentRegistry } from '../../tools/agent-registry/index.js'; import { AgentTaskQueue } from '../../tools/agent-tasks/index.js'; import { AgentResponseProcessor } from '../../tools/agent-response/index.js'; class HTTPAgentAPIServer { static instance; app; server; port = 3001; static getInstance() { if (!HTTPAgentAPIServer.instance) { HTTPAgentAPIServer.instance = new HTTPAgentAPIServer(); } return HTTPAgentAPIServer.instance; } constructor() { this.app = express(); this.setupMiddleware(); this.setupRoutes(); } setupMiddleware() { this.app.use(cors({ origin: true, credentials: true, methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization', 'X-Agent-ID', 'X-Session-ID'] })); this.app.use(express.json({ limit: '10mb' })); this.app.use(express.urlencoded({ extended: true })); this.app.use((req, res, next) => { logger.debug({ method: req.method, url: req.url, userAgent: req.get('User-Agent'), ip: req.ip }, 'HTTP API request'); next(); }); this.app.use((error, req, res, _next) => { logger.error({ err: error, url: req.url, method: req.method }, 'HTTP API error'); res.status(500).json({ success: false, error: 'Internal server error', message: error.message }); }); } setupRoutes() { this.app.get('/health', (req, res) => { res.json({ success: true, status: 'healthy', timestamp: new Date().toISOString(), uptime: process.uptime() }); }); this.app.post('/agents/register', this.handleAgentRegistration.bind(this)); this.app.get('/agents/:agentId/tasks', this.handleGetTasks.bind(this)); this.app.post('/agents/:agentId/tasks/:taskId/response', this.handleTaskResponse.bind(this)); this.app.get('/agents/:agentId/status', this.handleGetAgentStatus.bind(this)); this.app.post('/tasks/deliver', this.handleTaskDelivery.bind(this)); this.app.post('/agents/:agentId/heartbeat', this.handleHeartbeat.bind(this)); this.app.use('*', (req, res) => { res.status(404).json({ success: false, error: 'Endpoint not found', path: req.originalUrl }); }); } async handleAgentRegistration(req, res) { try { const registration = req.body; if (!registration.agentId || !registration.capabilities || !registration.httpEndpoint) { res.status(400).json({ success: false, error: 'Missing required fields: agentId, capabilities, httpEndpoint' }); return; } const sessionId = `http-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; const agentRegistry = AgentRegistry.getInstance(); await agentRegistry.registerAgent({ agentId: registration.agentId, capabilities: registration.capabilities, transportType: 'http', sessionId, maxConcurrentTasks: registration.maxConcurrentTasks || 1, pollingInterval: registration.pollingInterval || 5000, httpEndpoint: registration.httpEndpoint, httpAuthToken: registration.httpAuthToken }); res.json({ success: true, message: 'Agent registered successfully', agentId: registration.agentId, sessionId, transportType: 'http', pollingEndpoint: `/agents/${registration.agentId}/tasks`, responseEndpoint: `/agents/${registration.agentId}/tasks/{taskId}/response` }); logger.info({ agentId: registration.agentId, httpEndpoint: registration.httpEndpoint }, 'Agent registered via HTTP API'); } catch (error) { logger.error({ err: error }, 'Failed to register agent via HTTP API'); res.status(500).json({ success: false, error: 'Registration failed', message: error instanceof Error ? error.message : 'Unknown error' }); } } async handleGetTasks(req, res) { try { const { agentId } = req.params; const maxTasks = parseInt(req.query.maxTasks) || 1; const agentRegistry = AgentRegistry.getInstance(); const agent = await agentRegistry.getAgent(agentId); if (!agent) { res.status(404).json({ success: false, error: 'Agent not found' }); return; } const taskQueue = AgentTaskQueue.getInstance(); const tasks = await taskQueue.getTasks(agentId, maxTasks); res.json({ success: true, agentId, tasks: tasks.map(task => ({ taskId: task.taskId, sentinelPayload: task.sentinelPayload, priority: task.priority, assignedAt: task.assignedAt, deadline: task.deadline, metadata: task.metadata })), remainingInQueue: await taskQueue.getQueueLength(agentId) }); logger.debug({ agentId, tasksRetrieved: tasks.length }, 'Tasks retrieved via HTTP API'); } catch (error) { logger.error({ err: error, agentId: req.params.agentId }, 'Failed to get tasks via HTTP API'); res.status(500).json({ success: false, error: 'Failed to retrieve tasks', message: error instanceof Error ? error.message : 'Unknown error' }); } } async handleTaskResponse(req, res) { try { const { agentId, taskId } = req.params; const responseData = req.body; if (!responseData.status || !responseData.response) { res.status(400).json({ success: false, error: 'Missing required fields: status, response' }); return; } const responseProcessor = AgentResponseProcessor.getInstance(); await responseProcessor.processResponse({ agentId, taskId, status: responseData.status, response: responseData.response, completionDetails: responseData.completionDetails, receivedAt: Date.now() }); res.json({ success: true, message: 'Task response processed successfully', agentId, taskId, status: responseData.status, processedAt: new Date().toISOString() }); logger.info({ agentId, taskId, status: responseData.status }, 'Task response received via HTTP API'); } catch (error) { logger.error({ err: error, agentId: req.params.agentId, taskId: req.params.taskId }, 'Failed to process task response via HTTP API'); res.status(500).json({ success: false, error: 'Failed to process task response', message: error instanceof Error ? error.message : 'Unknown error' }); } } async handleGetAgentStatus(req, res) { try { const { agentId } = req.params; const agentRegistry = AgentRegistry.getInstance(); const agent = await agentRegistry.getAgent(agentId); if (!agent) { res.status(404).json({ success: false, error: 'Agent not found' }); return; } const taskQueue = AgentTaskQueue.getInstance(); const queueLength = await taskQueue.getQueueLength(agentId); res.json({ success: true, agentId, status: agent.status, capabilities: agent.capabilities, transportType: agent.transportType, maxConcurrentTasks: agent.maxConcurrentTasks, currentTasks: agent.currentTasks?.length || 0, queueLength, lastSeen: agent.lastSeen, registeredAt: agent.registeredAt }); } catch (error) { logger.error({ err: error, agentId: req.params.agentId }, 'Failed to get agent status via HTTP API'); res.status(500).json({ success: false, error: 'Failed to get agent status', message: error instanceof Error ? error.message : 'Unknown error' }); } } async handleTaskDelivery(req, res) { try { const taskRequest = req.body; if (!taskRequest.agentId || !taskRequest.taskId || !taskRequest.taskPayload) { res.status(400).json({ success: false, error: 'Missing required fields: agentId, taskId, taskPayload' }); return; } const agentRegistry = AgentRegistry.getInstance(); const agent = await agentRegistry.getAgent(taskRequest.agentId); if (!agent || agent.transportType !== 'http') { res.status(404).json({ success: false, error: 'HTTP agent not found' }); return; } const delivered = await this.deliverTaskToAgent(agent, taskRequest); if (delivered) { res.json({ success: true, message: 'Task delivered successfully', agentId: taskRequest.agentId, taskId: taskRequest.taskId, deliveredAt: new Date().toISOString() }); } else { res.status(500).json({ success: false, error: 'Failed to deliver task to agent endpoint' }); } } catch (error) { logger.error({ err: error }, 'Failed to deliver task via HTTP API'); res.status(500).json({ success: false, error: 'Task delivery failed', message: error instanceof Error ? error.message : 'Unknown error' }); } } async handleHeartbeat(req, res) { try { const { agentId } = req.params; const agentRegistry = AgentRegistry.getInstance(); await agentRegistry.updateAgentStatus(agentId, 'online'); res.json({ success: true, message: 'Heartbeat received', agentId, timestamp: new Date().toISOString() }); } catch (error) { logger.error({ err: error, agentId: req.params.agentId }, 'Failed to process heartbeat via HTTP API'); res.status(500).json({ success: false, error: 'Heartbeat processing failed', message: error instanceof Error ? error.message : 'Unknown error' }); } } async deliverTaskToAgent(agent, taskRequest) { try { if (!agent.httpEndpoint) { return false; } const headers = { 'Content-Type': 'application/json' }; if (agent.httpAuthToken) { headers['Authorization'] = `Bearer ${agent.httpAuthToken}`; } const response = await fetch(agent.httpEndpoint, { method: 'POST', headers, body: JSON.stringify({ taskId: taskRequest.taskId, taskPayload: taskRequest.taskPayload, priority: taskRequest.priority || 'normal', deadline: taskRequest.deadline, assignedAt: Date.now() }) }); const success = response.ok; logger.info({ agentId: agent.agentId, taskId: taskRequest.taskId, httpEndpoint: agent.httpEndpoint, success }, 'Task delivery attempt to agent HTTP endpoint'); return success; } catch (error) { logger.error({ err: error, agentId: agent.agentId }, 'Failed to deliver task to agent HTTP endpoint'); return false; } } async start(port) { try { if (!port || port <= 0 || port > 65535) { throw new Error(`Invalid port provided: ${port}. Port should be pre-allocated by Transport Manager.`); } this.port = port; logger.debug({ port }, 'Starting HTTP Agent API server with pre-allocated port'); await new Promise((resolve, reject) => { this.server = this.app.listen(port, (err) => { if (err) { if (err.message.includes('EADDRINUSE')) { const enhancedError = new Error(`Port ${port} is already in use. This should not happen with pre-allocated ports. ` + `Transport Manager port allocation may have failed.`); enhancedError.name = 'PortAllocationError'; reject(enhancedError); } else { reject(err); } } else { resolve(); } }); }); logger.info({ port, note: 'Using pre-allocated port from Transport Manager' }, 'HTTP Agent API server started successfully'); } catch (error) { logger.error({ err: error, port, context: 'HTTP Agent API server startup with pre-allocated port' }, 'Failed to start HTTP Agent API server'); if (error instanceof Error) { error.message = `HTTP Agent API server startup failed on pre-allocated port ${port}: ${error.message}`; } throw error; } } async stop() { try { if (this.server) { await new Promise((resolve) => { this.server.close(() => resolve()); }); } logger.info('HTTP Agent API server stopped'); } catch (error) { logger.error({ err: error }, 'Error stopping HTTP Agent API server'); throw error; } } getPort() { return this.port; } } export const httpAgentAPI = HTTPAgentAPIServer.getInstance(); export { HTTPAgentAPIServer };