Skip to main content
Glama

π“‚€π“’π“‹Ήπ”Έβ„•π•Œπ”Ήπ•€π•Šπ“‹Ήπ“’π“‚€ - Intelligent Guidance for

by Hive-Academy
task-operations.service.tsβ€’10 kB
import { Injectable, Inject } from '@nestjs/common'; import { Tool } from '@rekog/mcp-nest'; import { Subtask } from 'generated/prisma'; import { ZodSchema } from 'zod'; import { BaseMcpService, McpResponse, } from '../../domains/workflow-rules/utils/mcp-response.utils'; import { PrismaService } from '../../prisma/prisma.service'; import { ITaskRepository } from './repositories/interfaces/task.repository.interface'; import { CreateTaskData, TaskWithRelations, UpdateTaskData, } from './repositories/types/task.types'; import { TaskOperationsInput, TaskOperationsInputSchema, } from './schemas/task-operations.schema'; // Type-safe interfaces for service operations export interface TaskOperationResult { success: boolean; data?: TaskWithRelations | TaskWithRelations[] | TaskListResult; error?: { message: string; code: string; operation: string; }; metadata?: { operation: string; id: string | number; responseTime: number; }; } export interface TaskListResult { tasks: TaskWithRelations[]; count: number; filters: { status?: string; priority?: string; slug?: string; }; } /** * Task Operations MCP Service * * Provides core task lifecycle management as MCP tool. * Exposes task CRUD operations through MCP protocol. * * SOLID Principles Applied: * - Single Responsibility: Focused on task CRUD operations only * - Open/Closed: Extensible through input schema without modifying core logic * - Liskov Substitution: Consistent interface for all task operations * - Interface Segregation: Clean separation of concerns with focused methods * - Dependency Inversion: Depends on PrismaService abstraction */ @Injectable() export class TaskOperationsService extends BaseMcpService { constructor( @Inject('ITaskRepository') private readonly taskRepository: ITaskRepository, private readonly prisma: PrismaService, // Keep for workflow execution updates ) { super(); } /** * Execute task operation as MCP tool * Returns MCP-formatted response */ @Tool({ name: 'task_operations', description: 'Execute task lifecycle operations (create, update, get, list) with comprehensive task management capabilities.', parameters: TaskOperationsInputSchema as ZodSchema<TaskOperationsInput>, }) async executeTaskOperation(input: TaskOperationsInput): Promise<McpResponse> { const startTime = performance.now(); try { let result: any; switch (input.operation) { case 'create_task': result = await this.createTask(input); break; case 'update_task': result = await this.updateTask(input); break; case 'get_task': result = await this.getTask(input); break; default: // This should never happen due to Zod validation, but TypeScript needs exhaustive checking throw new Error( `Unknown operation: ${String((input as any).operation)}`, ); } const responseTime = performance.now() - startTime; // Return MCP-formatted response return this.buildResponse({ success: true, data: result, metadata: { operation: input.operation, id: input.taskId ?? 'unknown', responseTime: Math.round(responseTime), }, }); } catch (error: any) { return this.buildResponse({ success: false, error: { message: error.message, code: 'TASK_OPERATION_FAILED', operation: input.operation, }, }); } } private async createTask( input: TaskOperationsInput, ): Promise<{ id: number; name: string }> { const { taskData, description, codebaseAnalysis, executionId } = input; if (!taskData?.name) { throw new Error('Task name is required for creation'); } // Prepare CreateTaskData for repository const createData: CreateTaskData = { name: taskData.name, status: taskData.status || 'not-started', priority: taskData.priority || 'Medium', gitBranch: taskData.gitBranch, owner: 'product-manager', currentMode: 'product-manager', taskDescription: description ? { description: description.description || '', businessRequirements: description.businessRequirements || '', technicalRequirements: description.technicalRequirements || '', acceptanceCriteria: description.acceptanceCriteria || [], } : undefined, codebaseAnalysis: codebaseAnalysis ? { architectureFindings: codebaseAnalysis.architectureFindings || {}, problemsIdentified: codebaseAnalysis.problemsIdentified || {}, implementationContext: codebaseAnalysis.implementationContext || {}, qualityAssessment: codebaseAnalysis.qualityAssessment || {}, filesCovered: codebaseAnalysis.filesCovered || [], technologyStack: codebaseAnalysis.technologyStack || {}, analyzedBy: codebaseAnalysis.analyzedBy || 'system', } : undefined, }; // Create task with repository transaction support const result = await this.prisma.$transaction(async (tx) => { // Create task using repository with transaction const task = await this.taskRepository.createWithTransaction( createData, tx, ); // CRITICAL: Link task to workflow execution if executionId provided if (executionId) { await tx.workflowExecution.update({ where: { id: executionId }, data: { taskId: task.id }, }); } return task; }); return { id: result.id, name: result.name }; } private async updateTask( input: TaskOperationsInput, ): Promise<{ id: number; name: string }> { const { taskId, taskData, description, codebaseAnalysis } = input; if (!taskId) { throw new Error('Task ID is required for updates'); } // Prepare UpdateTaskData for repository const updateData: UpdateTaskData = { ...(taskData?.name && { name: taskData.name }), ...(taskData?.status && { status: taskData.status }), ...(taskData?.priority && { priority: taskData.priority }), ...(taskData?.gitBranch && { gitBranch: taskData.gitBranch }), ...(description && { taskDescription: { description: description.description || '', businessRequirements: description.businessRequirements || '', technicalRequirements: description.technicalRequirements || '', acceptanceCriteria: description.acceptanceCriteria || [], }, }), ...(codebaseAnalysis && { codebaseAnalysis: { architectureFindings: codebaseAnalysis.architectureFindings || {}, problemsIdentified: codebaseAnalysis.problemsIdentified || {}, implementationContext: codebaseAnalysis.implementationContext || {}, qualityAssessment: codebaseAnalysis.qualityAssessment || {}, filesCovered: codebaseAnalysis.filesCovered || [], technologyStack: codebaseAnalysis.technologyStack || {}, analyzedBy: codebaseAnalysis.analyzedBy || 'system', }, }), }; // Update task using repository with transaction support const result = await this.prisma.$transaction(async (tx) => { return await this.taskRepository.updateWithTransaction( taskId, updateData, tx, ); }); return { id: result.id, name: result.name }; } private async getTask( input: TaskOperationsInput, ): Promise<TaskWithRelations> { const { taskId, slug, includeDescription, includeAnalysis, includeResearch, includeSubtasks, includeCodeReviews, } = input; if (!taskId && !slug) { throw new Error('Either Task ID or Task Slug is required for retrieval'); } const includeOptions = { taskDescription: includeDescription || false, codebaseAnalysis: includeAnalysis || false, researchReports: includeResearch || false, subtasks: includeSubtasks || false, codeReviews: includeCodeReviews || false, }; let task: TaskWithRelations | null = null; if (slug) { task = await this.taskRepository.findBySlug(slug, includeOptions); } else if (taskId) { task = await this.taskRepository.findById(taskId, includeOptions); } if (!task) { throw new Error(`Task not found: ${slug || taskId}`); } // If subtasks are included, generate summary and return TaskWithSubtasks if (includeSubtasks && task.subtasks) { const subtaskSummary = this.generateSubtaskSummary(task.subtasks); return { ...task, subtaskSummary, } as TaskWithRelations; } return task; } /** * Generate subtask summary for TaskWithSubtasks interface */ private generateSubtaskSummary(subtasks: Subtask[]) { const total = subtasks.length; const completed = subtasks.filter((s) => s.status === 'completed').length; const inProgress = subtasks.filter( (s) => s.status === 'in-progress', ).length; const notStarted = subtasks.filter( (s) => s.status === 'not-started', ).length; // Group by batch const batchMap = new Map<string, { batchTitle: string; count: number }>(); subtasks.forEach((subtask) => { const batchId = subtask.batchId || 'no-batch'; const batchTitle = subtask.batchTitle || 'Untitled Batch'; if (!batchMap.has(batchId)) { batchMap.set(batchId, { batchTitle, count: 0 }); } batchMap.get(batchId)!.count++; }); const batches = Array.from(batchMap.entries()).map(([batchId, info]) => ({ batchId, batchTitle: info.batchTitle, count: info.count, })); return { total, completed, inProgress, notStarted, batches, }; } }

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/Hive-Academy/Anubis-MCP'

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