UNPKG

agent-hub-mcp

Version:

Universal AI agent coordination platform based on Model Context Protocol (MCP)

1,485 lines (1,467 loc) 78.2 kB
#!/usr/bin/env node // src/index.ts import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; // src/agents/service.ts var AgentService = class { constructor(storage2, featuresService2, messageService2) { this.storage = storage2; this.featuresService = featuresService2; this.messageService = messageService2; } async getAllAgents() { return this.storage.getAgents(); } async getHubStatus() { const allAgents = await this.getAllAgents(); const fiveMinutesAgo = Date.now() - 5 * 60 * 1e3; const activeAgents = allAgents.filter((agent) => agent.lastSeen > fiveMinutesAgo); const inactiveAgents = allAgents.filter((agent) => agent.lastSeen <= fiveMinutesAgo); const allFeatures = await this.featuresService.getFeatures(); const activeFeatures = allFeatures.filter((feature) => feature.status === "active"); const priorityCounts = { critical: allFeatures.filter((f) => f.priority === "critical").length, high: allFeatures.filter((f) => f.priority === "high").length, normal: allFeatures.filter((f) => f.priority === "normal").length, low: allFeatures.filter((f) => f.priority === "low").length }; const allMessages = await this.messageService.getAllMessages(); const unreadMessages = allMessages.filter((message) => !message.read); const oneHourAgo = Date.now() - 60 * 60 * 1e3; const recentMessages = allMessages.filter((message) => message.timestamp > oneHourAgo); return { agents: { total: allAgents.length, active: activeAgents, inactive: inactiveAgents }, features: { total: allFeatures.length, active: activeFeatures, byPriority: priorityCounts }, messages: { totalUnread: unreadMessages.length, recentActivity: recentMessages.length } }; } }; // src/features/service.ts import { createId } from "@paralleldrive/cuid2"; // src/types/features.types.ts var FeaturePriority = /* @__PURE__ */ ((FeaturePriority2) => { FeaturePriority2["CRITICAL"] = "critical"; FeaturePriority2["HIGH"] = "high"; FeaturePriority2["NORMAL"] = "normal"; FeaturePriority2["LOW"] = "low"; return FeaturePriority2; })(FeaturePriority || {}); var FeatureStatus = /* @__PURE__ */ ((FeatureStatus2) => { FeatureStatus2["PLANNING"] = "planning"; FeatureStatus2["ACTIVE"] = "active"; FeatureStatus2["COMPLETED"] = "completed"; FeatureStatus2["ON_HOLD"] = "on-hold"; FeatureStatus2["CANCELLED"] = "cancelled"; return FeatureStatus2; })(FeatureStatus || {}); var PRIORITY_ORDER = { ["critical" /* CRITICAL */]: 0, ["high" /* HIGH */]: 1, ["normal" /* NORMAL */]: 2, ["low" /* LOW */]: 3 }; // src/types/messages.types.ts var MessagePriority = /* @__PURE__ */ ((MessagePriority2) => { MessagePriority2["URGENT"] = "urgent"; MessagePriority2["NORMAL"] = "normal"; MessagePriority2["LOW"] = "low"; return MessagePriority2; })(MessagePriority || {}); var MessageType = /* @__PURE__ */ ((MessageType2) => { MessageType2["CONTEXT"] = "context"; MessageType2["TASK"] = "task"; MessageType2["QUESTION"] = "question"; MessageType2["COMPLETION"] = "completion"; MessageType2["ERROR"] = "error"; return MessageType2; })(MessageType || {}); // src/features/service.ts var FeaturesService = class { constructor(storage2) { this.storage = storage2; } async createFeature(input, createdBy) { const now = Date.now(); const feature = { id: input.name.toLowerCase().replace(/[^\da-z]+/g, "-"), name: input.name.toLowerCase().replace(/[^\da-z]+/g, "-"), title: input.title, description: input.description, status: "planning" /* PLANNING */, createdBy, priority: input.priority ?? "normal" /* NORMAL */, estimatedAgents: input.estimatedAgents, assignedAgents: [], createdAt: now, updatedAt: now }; await this.storage.createFeature(feature); return feature; } async getFeatures(filters) { return this.storage.getFeatures(filters); } async getFeature(featureId) { return this.storage.getFeature(featureId); } async updateFeature(featureId, updates) { await this.storage.updateFeature(featureId, { ...updates, updatedAt: Date.now() }); } async approveFeature(featureId) { await this.updateFeature(featureId, { status: "active" /* ACTIVE */ }); } async createTask(featureId, input, createdBy) { const feature = await this.storage.getFeature(featureId); if (!feature) { throw new Error(`Feature not found: ${featureId}`); } const now = Date.now(); const taskId = createId(); const task = { id: taskId, title: input.title, description: input.description, status: "planning" /* PLANNING */, createdBy, createdAt: now, updatedAt: now }; const delegations = input.delegations.map((del, index) => ({ id: `${taskId}-del-${index + 1}`, parentTaskId: taskId, agent: del.agent, scope: del.scope, status: "pending" /* PENDING */, subtaskIds: [], createdBy, createdAt: now, updatedAt: now })); await this.storage.createTask(featureId, task); for (const delegation of delegations) { await this.storage.createDelegation(featureId, delegation); } const allAgents = [...feature.assignedAgents || [], ...delegations.map((d) => d.agent)]; const uniqueAgents = [...new Set(allAgents)]; await this.updateFeature(featureId, { assignedAgents: uniqueAgents }); return { task, delegations }; } async acceptDelegation(featureId, delegationId, agentId) { const delegation = await this.storage.getDelegation(featureId, delegationId); if (!delegation) { throw new Error(`Delegation not found: ${delegationId} in feature ${featureId}`); } if (delegation.agent !== agentId) { throw new Error(`Delegation ${delegationId} is not assigned to agent ${agentId}`); } if (delegation.status !== "pending" /* PENDING */) { throw new Error(`Delegation ${delegationId} has already been ${delegation.status}`); } await this.storage.updateDelegation(featureId, delegationId, { status: "accepted" /* ACCEPTED */, acceptedAt: Date.now() }); } async createSubtask(featureId, delegationId, input, createdBy) { const delegation = await this.storage.getDelegation(featureId, delegationId); if (!delegation) { throw new Error(`Delegation not found: ${delegationId} in feature ${featureId}`); } if (delegation.agent !== createdBy) { throw new Error( `Only assigned agent ${delegation.agent} can create subtasks for delegation ${delegationId}` ); } const now = Date.now(); const subtaskId = createId(); const subtask = { id: subtaskId, delegationId, parentTaskId: delegation.parentTaskId, title: input.title, description: input.description, status: "todo" /* TODO */, createdBy, dependsOn: input.dependsOn || [], createdAt: now, updatedAt: now }; await this.storage.createSubtask(featureId, subtask); const updatedSubtaskIds = [...delegation.subtaskIds, subtaskId]; await this.storage.updateDelegation(featureId, delegationId, { subtaskIds: updatedSubtaskIds }); if (delegation.status === "accepted" /* ACCEPTED */ || delegation.status === "pending" /* PENDING */) { await this.storage.updateDelegation(featureId, delegationId, { status: "in-progress" /* IN_PROGRESS */ }); } return subtask; } async updateSubtask(featureId, subtaskId, updates, updatedBy) { const subtask = await this.storage.getSubtask(featureId, subtaskId); if (!subtask) { throw new Error(`Subtask not found: ${subtaskId} in feature ${featureId}`); } if (subtask.createdBy !== updatedBy) { throw new Error(`Only the creator ${subtask.createdBy} can update subtask ${subtaskId}`); } await this.storage.updateSubtask(featureId, subtaskId, { ...updates, updatedAt: Date.now() }); if (updates.status === "completed" /* COMPLETED */) { await this.checkDelegationCompletion(featureId, subtask.delegationId); } } async checkDelegationCompletion(featureId, delegationId) { const delegation = await this.storage.getDelegation(featureId, delegationId); if (!delegation) { return; } const subtasks = await this.storage.getSubtasks(featureId, delegationId); const allCompleted = subtasks.length > 0 && subtasks.every((s) => s.status === "completed" /* COMPLETED */); if (allCompleted && delegation.status !== "completed" /* COMPLETED */) { await this.storage.updateDelegation(featureId, delegationId, { status: "completed" /* COMPLETED */, completedAt: Date.now() }); await this.checkTaskCompletion(featureId, delegation.parentTaskId); } } async checkTaskCompletion(featureId, taskId) { const task = await this.storage.getTask(featureId, taskId); if (!task) { return; } const delegations = await this.storage.getDelegations(featureId); const taskDelegations = delegations.filter((d) => d.parentTaskId === taskId); const allCompleted = taskDelegations.length > 0 && taskDelegations.every((d) => d.status === "completed" /* COMPLETED */); if (allCompleted && task.status !== "completed" /* COMPLETED */) { await this.storage.updateTask(featureId, taskId, { status: "completed" /* COMPLETED */ }); await this.checkFeatureCompletion(featureId); } } async checkFeatureCompletion(featureId) { const tasks = await this.storage.getTasksInFeature(featureId); const allCompleted = tasks.length > 0 && tasks.every((t) => t.status === "completed" /* COMPLETED */); if (allCompleted) { await this.updateFeature(featureId, { status: "completed" /* COMPLETED */ }); } } async getAgentWorkload(agentId) { return this.storage.getAgentWorkload(agentId); } async getFeatureData(featureId) { return this.storage.getFeatureData(featureId); } async getFeatureOverview(featureId) { if (featureId) { const data = await this.getFeatureData(featureId); if (!data) { throw new Error(`Feature not found: ${featureId}`); } return data; } return this.getFeatures(); } // Utility methods for common queries async getActiveFeatures() { return this.getFeatures({ status: "active" /* ACTIVE */ }); } async getAgentFeatures(agentId) { return this.getFeatures({ agent: agentId }); } async getFeaturesByPriority(priority) { return this.getFeatures({ priority }); } async getAgentDelegations(agentId, featureId) { return this.storage.getDelegations(featureId, agentId); } async getAgentSubtasks(agentId, featureId) { const subtasks = await this.storage.getSubtasks(featureId); return subtasks.filter((s) => s.createdBy === agentId); } // Feature lifecycle management async pauseFeature(featureId) { await this.updateFeature(featureId, { status: "on-hold" /* ON_HOLD */ }); } async resumeFeature(featureId) { await this.updateFeature(featureId, { status: "active" /* ACTIVE */ }); } async cancelFeature(featureId) { await this.updateFeature(featureId, { status: "cancelled" /* CANCELLED */ }); } // Dependency management async getSubtaskDependencies(featureId, subtaskId) { const subtask = await this.storage.getSubtask(featureId, subtaskId); if (!subtask || !subtask.dependsOn.length) { return []; } const dependencies = []; for (const depId of subtask.dependsOn) { const dep = await this.storage.getSubtask(featureId, depId); if (dep) { dependencies.push(dep); } } return dependencies; } async canStartSubtask(featureId, subtaskId) { const dependencies = await this.getSubtaskDependencies(featureId, subtaskId); return dependencies.every((dep) => dep.status === "completed" /* COMPLETED */); } // Statistics and monitoring async getFeatureStats(featureId) { if (featureId) { const data = await this.getFeatureData(featureId); if (!data) { return null; } return { feature: data.feature, tasksTotal: data.tasks.length, tasksCompleted: data.tasks.filter((t) => t.status === "completed" /* COMPLETED */).length, delegationsTotal: data.delegations.length, delegationsCompleted: data.delegations.filter((d) => d.status === "completed" /* COMPLETED */).length, subtasksTotal: data.subtasks.length, subtasksCompleted: data.subtasks.filter((s) => s.status === "completed" /* COMPLETED */).length, agents: [...new Set(data.delegations.map((d) => d.agent))] }; } const features = await this.getFeatures(); const stats = { active: features.filter((f) => f.status === "active" /* ACTIVE */).length, completed: features.filter((f) => f.status === "completed" /* COMPLETED */).length, planning: features.filter((f) => f.status === "planning" /* PLANNING */).length, onHold: features.filter((f) => f.status === "on-hold" /* ON_HOLD */).length, cancelled: features.filter((f) => f.status === "cancelled" /* CANCELLED */).length, byPriority: {}, byAgent: {} }; for (const feature of features) { stats.byPriority[feature.priority] = (stats.byPriority[feature.priority] || 0) + 1; for (const agent of feature.assignedAgents || []) { stats.byAgent[agent] = (stats.byAgent[agent] || 0) + 1; } } return stats; } }; // src/messaging/service.ts import { createId as createId2 } from "@paralleldrive/cuid2"; var MessageService = class { constructor(storage2) { this.storage = storage2; } async sendMessage(from, to, type, content, options = {}) { const message = { id: createId2(), from, to, type, content, metadata: options.metadata, timestamp: Date.now(), read: false, threadId: options.threadId, priority: options.priority ?? "normal" /* NORMAL */ }; await this.storage.saveMessage(message); return message.id; } async getAllMessages(options = {}) { return this.storage.getMessages({ type: options.type, since: options.since }); } async getMessages(agentId, options = {}) { const messages = await this.storage.getMessages({ agent: agentId, type: options.type, since: options.since }); const unreadMessages = messages.filter( (m) => !m.read && (m.to === agentId || m.to === "all" && m.from !== agentId) ); if (options.markAsRead !== false) { const markAsReadPromises = unreadMessages.map(async (message) => { try { await this.storage.markMessageAsRead(message.id); } catch (error) { console.error(`Failed to mark message ${message.id} as read:`, error); } }); await Promise.allSettled(markAsReadPromises); } return { count: unreadMessages.length, messages: unreadMessages }; } async getMessageById(messageId) { return this.storage.getMessage(messageId); } }; // src/servers/mcp.ts import { Server } from "@modelcontextprotocol/sdk/server"; import { CallToolRequestSchema, InitializeRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js"; // src/tools/definitions.ts var TOOLS = [ { name: "register_agent", description: "Register an agent with the hub", inputSchema: { type: "object", properties: { id: { type: "string", description: "Agent identifier (optional - will be generated from project path if not provided)" }, projectPath: { type: "string", description: "Agent working directory" }, role: { type: "string", description: "Agent role description" }, capabilities: { type: "array", items: { type: "string" }, description: "Agent capabilities", default: [] }, collaboratesWith: { type: "array", items: { type: "string" }, description: "Expected collaborators", default: [] } }, required: ["projectPath", "role"] } }, { name: "send_message", description: "Send a message to another agent or broadcast to all agents", inputSchema: { type: "object", properties: { from: { type: "string", description: "Source agent identifier" }, to: { type: "string", description: 'Target agent identifier or "all" for broadcast' }, type: { type: "string", enum: Object.values(MessageType), description: "Message type" }, content: { type: "string", description: "Message content" }, metadata: { type: "object", description: "Additional structured data" }, priority: { type: "string", enum: Object.values(MessagePriority), description: "Message priority", default: "normal" /* NORMAL */ }, threadId: { type: "string", description: "Optional conversation thread ID" } }, required: ["from", "to", "type", "content"] } }, { name: "get_messages", description: "Retrieve messages for an agent", inputSchema: { type: "object", properties: { agent: { type: "string", description: "Agent identifier to get messages for" }, markAsRead: { type: "boolean", description: "Mark retrieved messages as read", default: true }, type: { type: "string", enum: Object.values(MessageType), description: "Filter by message type" }, since: { type: "number", description: "Get messages since timestamp" } }, required: ["agent"] } }, { name: "get_hub_status", description: "Get overview of hub activity, agents, and collaboration opportunities", inputSchema: { type: "object", properties: {} } }, { name: "create_feature", description: "Create a new feature for multi-agent collaboration", inputSchema: { type: "object", properties: { name: { type: "string", description: "Feature name (will be converted to kebab-case ID)" }, title: { type: "string", description: "Human-readable feature title" }, description: { type: "string", description: "Detailed feature requirements and context" }, priority: { type: "string", enum: Object.values(FeaturePriority), description: "Feature priority level", default: "normal" /* NORMAL */ }, estimatedAgents: { type: "array", items: { type: "string" }, description: "Agents expected to be needed for this feature", default: [] }, createdBy: { type: "string", description: "Agent creating this feature" } }, required: ["name", "title", "description", "createdBy"] } }, { name: "create_task", description: "Create a task within a feature with agent delegations", inputSchema: { type: "object", properties: { featureId: { type: "string", description: "Feature ID to create task in" }, title: { type: "string", description: "Task title" }, description: { type: "string", description: "Detailed task requirements" }, delegations: { type: "array", items: { type: "object", properties: { agent: { type: "string", description: "Agent ID to delegate to" }, scope: { type: "string", description: "What this agent should accomplish" } }, required: ["agent", "scope"] }, description: "Agent delegations for this task" }, createdBy: { type: "string", description: "Agent creating this task" } }, required: ["featureId", "title", "description", "delegations", "createdBy"] } }, { name: "create_subtask", description: "Create implementation subtasks within a delegation", inputSchema: { type: "object", properties: { featureId: { type: "string", description: "Feature ID" }, delegationId: { type: "string", description: "Delegation ID to create subtasks for" }, subtasks: { type: "array", items: { type: "object", properties: { title: { type: "string", description: "Subtask title" }, description: { type: "string", description: "Subtask description" }, dependsOn: { type: "array", items: { type: "string" }, description: "Subtask IDs this depends on", default: [] } }, required: ["title"] }, description: "Subtasks to create" }, createdBy: { type: "string", description: "Agent creating these subtasks" } }, required: ["featureId", "delegationId", "subtasks", "createdBy"] } }, { name: "get_features", description: "Get list of features with optional filtering", inputSchema: { type: "object", properties: { status: { type: "string", enum: Object.values(FeatureStatus), description: "Filter by feature status" }, priority: { type: "string", enum: Object.values(FeaturePriority), description: "Filter by feature priority" }, agent: { type: "string", description: "Filter features assigned to this agent" }, createdBy: { type: "string", description: "Filter features created by this agent" } } } }, { name: "get_feature", description: "Get complete feature data including tasks, delegations, and subtasks", inputSchema: { type: "object", properties: { featureId: { type: "string", description: "Feature ID to retrieve" } }, required: ["featureId"] } }, { name: "accept_delegation", description: "Accept a delegation assigned to an agent", inputSchema: { type: "object", properties: { featureId: { type: "string", description: "Feature ID" }, delegationId: { type: "string", description: "Delegation ID to accept" }, agentId: { type: "string", description: "Agent accepting the delegation" } }, required: ["featureId", "delegationId", "agentId"] } }, { name: "update_subtask", description: "Update subtask status and provide output/context", inputSchema: { type: "object", properties: { featureId: { type: "string", description: "Feature ID" }, subtaskId: { type: "string", description: "Subtask ID to update" }, status: { type: "string", enum: ["todo", "in-progress", "completed", "blocked"], description: "New subtask status" }, output: { type: "string", description: "Output or context for other agents" }, blockedReason: { type: "string", description: "Reason if status is blocked" }, updatedBy: { type: "string", description: "Agent updating this subtask" } }, required: ["featureId", "subtaskId", "updatedBy"] } }, { name: "sync", description: "Comprehensive sync with the hub - get messages, workload, and status in one call", inputSchema: { type: "object", properties: { agentId: { type: "string", description: "Agent ID to sync for" }, markAsRead: { type: "boolean", description: "Mark retrieved messages as read", default: true } }, required: ["agentId"] } } ]; // src/tools/handlers.ts import path from "path"; // src/validation/schema.ts import Ajv from "ajv"; var ajv = new Ajv({ allErrors: true, removeAdditional: false, useDefaults: true, coerceTypes: false }); var validators = /* @__PURE__ */ new Map(); for (const tool of TOOLS) { const validator = ajv.compile(tool.inputSchema); validators.set(tool.name, validator); } function validateToolInput(toolName, arguments_) { const validator = validators.get(toolName); if (!validator) { throw new Error(`No validator found for tool: ${toolName}`); } const valid = validator(arguments_); if (!valid) { const errors = validator.errors ?? []; const errorMessages = errors.map((error) => { const instancePath = error.instancePath ?? "root"; return `${instancePath}: ${error.message}`; }); throw new Error(`Validation failed for tool '${toolName}': ${errorMessages.join(", ")}`); } return arguments_; } // src/validation/security.ts import { realpathSync } from "fs"; import { resolve } from "path"; function validateIdentifier(value, fieldName) { return validateString(value, fieldName, { required: true, maxLength: 100, pattern: /^[\w-]+$/ }); } function validateMessagePriority(value) { if (!value) { return void 0; } if (typeof value !== "string") { throw new TypeError("Priority must be a string"); } const validPriorities = Object.values(MessagePriority); if (!validPriorities.includes(value)) { throw new Error(`Invalid priority: ${value}`); } return value; } function validateMessageType(value) { if (!value || typeof value !== "string") { throw new Error("Invalid message type"); } const validTypes = Object.values(MessageType); if (!validTypes.includes(value)) { throw new Error(`Invalid message type: ${value}`); } return value; } function validateMetadata(value) { if (!value) { return void 0; } if (typeof value !== "object" || Array.isArray(value)) { throw new TypeError("Metadata must be an object"); } const metadata = value; const keys = Object.keys(metadata); if (keys.length > 20) { throw new Error("Metadata cannot have more than 20 properties"); } const sanitized = {}; for (const [key, value_] of Object.entries(metadata)) { if (key === "__proto__" || key === "constructor" || key === "prototype") { continue; } if (!/^[\w-]+$/.test(key)) { throw new Error(`Invalid metadata key: ${key}`); } sanitized[key] = JSON.parse(JSON.stringify(value_)); } return sanitized; } function validateProjectPath(path3) { if (!path3 || typeof path3 !== "string") { throw new Error("Invalid project path"); } if (path3.includes("..") || path3.includes("~")) { throw new Error("Invalid project path: directory traversal detected"); } const allowedPrefixes = [ "/Users/", "/home/", "/var/www/", "/opt/", "/workspace/", "/tmp/", process.cwd() // Current working directory ]; const resolvedPath = resolve(path3); let realPath; try { realPath = realpathSync(resolvedPath); } catch { realPath = resolvedPath; } const pathsToValidate = [resolvedPath, realPath]; for (const pathToCheck of pathsToValidate) { const isAllowed = allowedPrefixes.some((prefix) => pathToCheck.startsWith(prefix)); if (!isAllowed) { throw new Error(`Project path must be in an allowed directory. Resolved to: ${pathToCheck}`); } } return realPath; } function validateString(value, fieldName, options = {}) { const { maxLength = 1e3, minLength = 1, pattern, required = true } = options; if (value === void 0 || value === null) { if (required) { throw new Error(`${fieldName} is required`); } return ""; } if (typeof value !== "string") { throw new TypeError(`${fieldName} must be a string`); } const trimmed = value.trim(); if (required && trimmed.length < minLength) { throw new Error(`${fieldName} must be at least ${minLength} characters`); } if (trimmed.length > maxLength) { throw new Error(`${fieldName} must not exceed ${maxLength} characters`); } if (pattern && !pattern.test(trimmed)) { throw new Error(`${fieldName} contains invalid characters`); } const dangerousPatterns = [ /<script/i, /javascript:/i, /on\w+\s*=/i, // onclick, onload, etc. /__proto__/, /constructor\[/, /prototype\[/ ]; for (const dangerous of dangerousPatterns) { if (dangerous.test(trimmed)) { throw new Error(`${fieldName} contains potentially malicious content`); } } return trimmed; } // src/agents/detection.ts async function createAgentFromProjectPath(agentId, projectPath) { const validatedPath = validateProjectPath(projectPath); const { capabilities, role } = await detectProjectCapabilities(validatedPath); return { id: agentId, projectPath: validatedPath, role, capabilities, status: "active", lastSeen: Date.now(), collaboratesWith: [] }; } async function detectProjectCapabilities(projectPath) { let role = "Development agent"; const capabilities = []; try { const fs2 = await import("fs/promises"); const path3 = await import("path"); try { const packageJsonPath = path3.resolve(path3.join(projectPath, "package.json")); if (!packageJsonPath.startsWith(path3.resolve(projectPath))) { throw new Error("Invalid package.json path"); } const packageJson = JSON.parse(await fs2.readFile(packageJsonPath, "utf-8")); const deps = { ...packageJson.dependencies, ...packageJson.devDependencies }; if (deps.react) { capabilities.push("react", "frontend"); } if (deps.vue) { capabilities.push("vue", "frontend"); } if (deps.express || deps.fastify) { capabilities.push("api", "backend"); } if (deps.typescript) { capabilities.push("typescript"); } if (deps.jest || deps.vitest) { capabilities.push("testing"); } } catch { } try { const files = await fs2.readdir(projectPath); if (files.includes("src") && files.includes("public")) { role = "Frontend development agent"; } else if (files.includes("src") && capabilities.includes("api")) { role = "Backend development agent"; } else if (files.some((f) => f.endsWith(".py"))) { role = "Python development agent"; capabilities.push("python"); } else if (files.some((f) => f.endsWith(".go"))) { role = "Go development agent"; capabilities.push("go"); } else if (files.some((f) => f.endsWith(".rs"))) { role = "Rust development agent"; capabilities.push("rust"); } } catch { } } catch { } return { role, capabilities }; } // src/features/handlers.ts var FeaturesHandler = class { service; constructor(storage2) { this.service = new FeaturesService(storage2); } async handleFeatureTool(name, arguments_) { switch (name) { case "create_feature": return this.createFeature(validateToolInput("create_feature", arguments_)); case "create_task": return this.createTask(validateToolInput("create_task", arguments_)); case "create_subtask": return this.createSubtask(validateToolInput("create_subtask", arguments_)); case "get_features": return this.getFeatures(validateToolInput("get_features", arguments_)); case "get_feature": return this.getFeature(validateToolInput("get_feature", arguments_)); case "accept_delegation": return this.acceptDelegation(validateToolInput("accept_delegation", arguments_)); case "update_subtask": return this.updateSubtask(validateToolInput("update_subtask", arguments_)); default: throw new Error(`Unknown feature tool: ${name}`); } } async createFeature(arguments_) { try { const feature = await this.service.createFeature( { ...arguments_, priority: arguments_.priority ?? "normal" /* NORMAL */, estimatedAgents: arguments_.estimatedAgents || [] }, arguments_.createdBy ); return { success: true, feature, message: `Feature "${feature.title}" created successfully with ID: ${feature.id}` }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : "Unknown error" }; } } async createTask(arguments_) { try { const result = await this.service.createTask( arguments_.featureId, arguments_, arguments_.createdBy ); return { success: true, task: result.task, delegations: result.delegations, message: `Task "${result.task.title}" created with ${result.delegations.length} delegations` }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : "Unknown error" }; } } async createSubtask(arguments_) { try { const subtasks = []; for (const subtaskData of arguments_.subtasks) { const subtask = await this.service.createSubtask( arguments_.featureId, arguments_.delegationId, subtaskData, arguments_.createdBy ); subtasks.push(subtask); } return { success: true, subtasks, message: `Created ${subtasks.length} subtask(s) for delegation ${arguments_.delegationId}` }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : "Unknown error" }; } } async getFeatures(arguments_) { try { const filters = { status: arguments_.status, priority: arguments_.priority, agent: arguments_.agent, createdBy: arguments_.createdBy }; Object.keys(filters).forEach((key) => { if (filters[key] === void 0) { delete filters[key]; } }); const features = await this.service.getFeatures( Object.keys(filters).length > 0 ? filters : void 0 ); return { success: true, features, count: features.length, summary: { byStatus: features.reduce( (acc, f) => { acc[f.status] = (acc[f.status] || 0) + 1; return acc; }, {} ), byPriority: features.reduce( (acc, f) => { acc[f.priority] = (acc[f.priority] || 0) + 1; return acc; }, {} ) } }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : "Unknown error" }; } } async getFeature(arguments_) { try { const featureData = await this.service.getFeatureData(arguments_.featureId); if (!featureData) { return { success: false, error: `Feature not found: ${arguments_.featureId}` }; } return { success: true, ...featureData, summary: { tasksTotal: featureData.tasks.length, tasksCompleted: featureData.tasks.filter((t) => t.status === "completed").length, delegationsTotal: featureData.delegations.length, delegationsCompleted: featureData.delegations.filter((d) => d.status === "completed").length, subtasksTotal: featureData.subtasks.length, subtasksCompleted: featureData.subtasks.filter((s) => s.status === "completed").length, uniqueAgents: [...new Set(featureData.delegations.map((d) => d.agent))] } }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : "Unknown error" }; } } async acceptDelegation(arguments_) { try { await this.service.acceptDelegation( arguments_.featureId, arguments_.delegationId, arguments_.agentId ); return { success: true, message: `Delegation ${arguments_.delegationId} accepted by agent ${arguments_.agentId}` }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : "Unknown error" }; } } async updateSubtask(arguments_) { try { const updates = { status: arguments_.status, output: arguments_.output, blockedReason: arguments_.blockedReason }; Object.keys(updates).forEach((key) => { if (updates[key] === void 0) { delete updates[key]; } }); await this.service.updateSubtask( arguments_.featureId, arguments_.subtaskId, updates, arguments_.updatedBy ); return { success: true, message: `Subtask ${arguments_.subtaskId} updated successfully` }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : "Unknown error" }; } } // Utility methods for common operations async getFeatureStats() { try { const stats = await this.service.getFeatureStats(); return { success: true, stats }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : "Unknown error" }; } } async approveFeature(featureId) { try { await this.service.approveFeature(featureId); return { success: true, message: `Feature ${featureId} approved and activated` }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : "Unknown error" }; } } }; // src/messaging/handlers.ts function createMessageHandlers(messageService2, storage2, sendNotificationToAgent, sendResourceNotification) { return { async send_message(arguments_) { const from = validateIdentifier(arguments_.from, "from"); const to = validateIdentifier(arguments_.to, "to"); const type = validateMessageType(arguments_.type); const content = validateString(arguments_.content, "content", { maxLength: 1e4 }); const metadata = validateMetadata(arguments_.metadata); const priority = validateMessagePriority(arguments_.priority); const threadId = arguments_.threadId ? validateIdentifier(arguments_.threadId, "threadId") : void 0; const messageId = await messageService2.sendMessage(from, to, type, content, { metadata, priority, threadId }); if (to !== "all") { const message = await messageService2.getMessageById(messageId); if (message) { await sendNotificationToAgent(to, "new_message", { message }); if (sendResourceNotification) { await sendResourceNotification(to, `agent-hub://messages/${to}`); } } } else { const agents = await storage2.getAgents(); const message = await messageService2.getMessageById(messageId); if (message) { for (const agent of agents) { if (agent.id !== arguments_.from) { await sendNotificationToAgent(agent.id, "new_message", { message }); if (sendResourceNotification) { await sendResourceNotification(agent.id, `agent-hub://messages/${agent.id}`); } } } } } return { success: true, messageId }; }, async get_messages(arguments_) { const agent = validateIdentifier(arguments_.agent, "agent"); const type = arguments_.type ? validateMessageType(arguments_.type) : void 0; const since = arguments_.since; const { markAsRead } = arguments_; const result = await messageService2.getMessages(agent, { type, since, markAsRead }); return result; } }; } // src/tools/handlers.ts function createToolHandlers(services) { const messageHandlers = createMessageHandlers( services.messageService, services.storage, services.sendNotificationToAgent, services.sendResourceNotification ); const featuresHandler = new FeaturesHandler(services.storage); return { async send_message(arguments_) { const validatedArguments = validateToolInput("send_message", arguments_); return messageHandlers.send_message(validatedArguments); }, async get_messages(arguments_) { const validatedArguments = validateToolInput("get_messages", arguments_); return messageHandlers.get_messages(validatedArguments); }, async register_agent(arguments_) { const validatedArguments = validateToolInput("register_agent", arguments_); const currentSession = services.getCurrentSession(); let agent; let isExistingAgent = false; const { projectPath } = validatedArguments; const proposedAgentId = validatedArguments.id ? validatedArguments.id : path.basename(projectPath); if (validatedArguments.id) { const existingAgentById = await services.storage.findAgentById(proposedAgentId); if (existingAgentById && existingAgentById.projectPath !== projectPath) { return { success: false, error: "AGENT_ID_CONFLICT", message: `\u274C Agent ID '${proposedAgentId}' is already registered with a different project path (${existingAgentById.projectPath}). Cannot register with ${projectPath}.`, existingAgent: { id: existingAgentById.id, projectPath: existingAgentById.projectPath, role: existingAgentById.role } }; } } const existingAgent = await services.storage.findAgentByProjectPath(projectPath); if (existingAgent) { isExistingAgent = true; agent = existingAgent; agent.lastSeen = Date.now(); agent.status = "active"; if (validatedArguments.role) { agent.role = validatedArguments.role; } if (validatedArguments.capabilities) { agent.capabilities = [ .../* @__PURE__ */ new Set([...agent.capabilities, ...validatedArguments.capabilities]) ]; } if (validatedArguments.collaboratesWith) { agent.collaboratesWith = validatedArguments.collaboratesWith; } } else { const agentId = validatedArguments.id ? validatedArguments.id : path.basename(projectPath); if (projectPath && projectPath !== "unknown") { agent = await createAgentFromProjectPath(agentId, projectPath); if (validatedArguments.capabilities) { agent.capabilities = [ .../* @__PURE__ */ new Set([...agent.capabilities, ...validatedArguments.capabilities]) ]; } if (validatedArguments.role) { agent.role = validatedArguments.role; } if (validatedArguments.collaboratesWith) { agent.collaboratesWith = validatedArguments.collaboratesWith; } } else { agent = { id: agentId, projectPath, role: validatedArguments.role, capabilities: validatedArguments.capabilities ?? [], status: "active", lastSeen: Date.now(), collaboratesWith: validatedArguments.collaboratesWith ?? [] }; } } agent.status = "active"; if (currentSession) { currentSession.agent = agent; } await services.storage.saveAgent(agent); if (isExistingAgent) { await services.broadcastNotification("agent_rejoined", { agent }); } else { await services.broadcastNotification("agent_joined", { agent }); } const actionVerb = isExistingAgent ? "reconnected" : "registered"; return { success: true, agent, message: `\u2705 Agent ${actionVerb} successfully! ${isExistingAgent ? "Welcome back" : "Welcome"} ${agent.id} (${agent.role}).`, detectedCapabilities: agent.capabilities, collaborationReady: true, reconnected: isExistingAgent }; }, async get_hub_status(arguments_) { validateToolInput("get_hub_status", arguments_); return services.agentService.getHubStatus(); }, // Features system tools async create_feature(arguments_) { const result = await featuresHandler.handleFeatureTool("create_feature", arguments_); if (result.success) { await services.broadcastNotification("feature_created", { feature: result.feature }); } return result; }, async create_task(arguments_) { const result = await featuresHandler.handleFeatureTool("create_task", arguments_); if (result.success) { await services.broadcastNotification("task_created", { featureId: arguments_.featureId, task: result.task, delegations: result.delegations }); } return result; }, async create_subtask(arguments_) { return featuresHandler.handleFeatureTool("create_subtask", arguments_); }, async get_features(arguments_) { return featuresHandler.handleFeatureTool("get_features", arguments_); }, async get_feature(arguments_) { return featuresHandler.handleFeatureTool("get_feature", arguments_); }, async accept_delegation(arguments_) { const result = await featuresHandler.handleFeatureTool("accept_delegation", arguments_); if (result.success) { await services.broadcastNotification("delegation_accepted", { featureId: arguments_.featureId, delegationId: arguments_.delegationId, agentId: arguments_.agentId }); } return result; }, async update_subtask(arguments_) { const result = await featuresHandler.handleFeatureTool("update_subtask", arguments_); if (result.success) { await services.broadcastNotification("subtask_updated", { featureId: arguments_.featureId, subtaskId: arguments_.subtaskId, status: arguments_.status }); } return result; }, async sync(arguments_) { const validatedArguments = validateToolInput("sync", arguments_); const { agentId } = validatedArguments; const markAsRead = validatedArguments.markAsRead !== false; try { const [messagesResult, workloadRaw, hubStatusResult] = await Promise.all([ messageHandlers.get_messages({ agent: agentId, markAsRead }), services.storage.getAgentWorkload(agentId), services.agentService.getHubStatus() ]); const workloadResult = { success: true, activeFeatures: workloadRaw.activeFeatures, summary: { totalFeatures: workloadRaw.activeFeatures.length, totalDelegations: workloadRaw.activeFeatures.reduce( (sum, f) => sum + f.myDelegations.length, 0 ), featuresByPriority: workloadRaw.activeFeatures.reduce( (acc, f) => { acc[f.feature.priority] = (acc[f.feature.priority] || 0) + 1; return acc; }, {} ) } }; return { success: true, timestamp: Date.now(), messages: messagesResult, workload: workloadResult, hubStatus: hubStatusResult }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : "Unknown error", timestamp: Date.now() }; } } }; } // src/servers/mcp.ts function createMcpServer(deps) { const server = new Server( { name: "agent-hub-mcp", version: "0.2.0" }, { capabilities: { tools: {}, resources: { subscribe: true, listChanged: true } } } ); async function updateAgentLastSeen() { const currentSession = deps.getCurrentSession(); if (currentSession?.agent) { await deps.storage.updateAgent(currentSession.agent.id, { lastSeen: Date.now(), status: "active" // Ensure they're marked active when making requests }); } } const toolHandlerServices = { storage: deps.storage, messageService: deps.messageService, agentService: deps.agentService, getCurrentSession: deps.getCurrentSession, broadcastNotification: deps.broadcastNotification, sendNotificationToAgent: deps.sendNotificationToAgent, sendResourceNotification: deps.sendResourceNotification }; const toolHandlers = createToolHandlers(toolHandlerServices); server.setRequestHandler(InitializeRequestSchema, async (request) => { const agents = await deps.storage.getAgents(); const activeAgents = agents.filter((a) => Date.now() - a.lastSeen < 5 * 60 * 1e3); const totalMessages = await deps.storage.getMessages({}); const unreadCount = totalMessages.filter((m) => !m.read).length; return { protocolVersion: request.params.protocolVersion, capabilities: { tools: {}, resources: { subscribe: true, listChanged: true } }, serverInfo: { name: "agent-hub-mcp", version: "0.2.0", activeAgents: activeAgents.length, totalMessages: unreadCount, collaborationHints: activeAgents.map((a) => ({ id: a.id, role: a.role, capabilities: a.capabilities })) }, instructions: `\u{1F7E2} CONNECTED TO AGENT-HUB | Registration Required \u26A0\uFE0F REGIST