Skip to main content
Glama

Task Manager MCP Server

by jhawkins11
dbUtils.ts12.4 kB
import { databaseService, HistoryEntry } from '../services/databaseService' import crypto from 'crypto' // Types interface Task { id: string title?: string description?: string status: 'pending' | 'in_progress' | 'completed' | 'decomposed' completed: boolean effort?: 'low' | 'medium' | 'high' feature_id?: string parent_task_id?: string created_at: number updated_at: number fromReview?: boolean } interface TaskUpdate { title?: string description?: string effort?: 'low' | 'medium' | 'high' parent_task_id?: string fromReview?: boolean } interface PlanningState { questionId: string featureId: string prompt: string partialResponse: string planningType: 'feature_planning' | 'plan_adjustment' } /** * Adds a new entry to the feature history * @param featureId The unique ID of the feature * @param role The role of the entry ('user', 'model', 'tool_call', 'tool_response') * @param content The content of the entry */ export async function addHistoryEntry( featureId: string, role: 'user' | 'model' | 'tool_call' | 'tool_response', content: any ): Promise<void> { try { // Convert timestamp to number if not already const timestamp = Math.floor(Date.now() / 1000) // Prepare history entry const entry = { timestamp, role, content, feature_id: featureId, } // Connect to database await databaseService.connect() // Add entry await databaseService.addHistoryEntry(entry) // Close connection await databaseService.close() } catch (error) { console.error( `[TaskServer] Error adding history entry to database: ${error}` ) // Re-throw the error so the caller is aware throw error } } /** * Gets all tasks for a feature * @param featureId The unique ID of the feature * @returns Array of tasks */ export async function getAllTasksForFeature( featureId: string ): Promise<Task[]> { try { await databaseService.connect() const tasks = await databaseService.getTasksByFeatureId(featureId) await databaseService.close() return tasks } catch (error) { console.error( `[TaskServer] Error getting tasks for feature ${featureId}: ${error}` ) throw error } } /** * Gets a task by ID * @param taskId The unique ID of the task * @returns The task or null if not found */ export async function getTaskById(taskId: string): Promise<Task | null> { try { await databaseService.connect() const task = await databaseService.getTaskById(taskId) await databaseService.close() return task } catch (error) { console.error(`[TaskServer] Error getting task ${taskId}: ${error}`) throw error } } /** * Creates a new task * @param featureId The feature ID the task belongs to * @param description The task description * @param options Optional task properties (title, effort, parentTaskId) * @returns The created task */ export async function createTask( featureId: string, description: string, options: { title?: string effort?: 'low' | 'medium' | 'high' parentTaskId?: string fromReview?: boolean } = {} ): Promise<Task> { try { const now = Math.floor(Date.now() / 1000) const newTask: Task = { id: crypto.randomUUID(), description, title: options.title || description, status: 'pending', completed: false, effort: options.effort, feature_id: featureId, parent_task_id: options.parentTaskId, created_at: now, updated_at: now, fromReview: options.fromReview, } await databaseService.connect() await databaseService.addTask(newTask) await databaseService.close() return newTask } catch (error) { console.error( `[TaskServer] Error creating task for feature ${featureId}: ${error}` ) throw error } } /** * Updates a task's status * @param taskId The unique ID of the task * @param status The new status * @param completed Optional completed flag * @returns True if successful, false otherwise */ export async function updateTaskStatus( taskId: string, status: 'pending' | 'in_progress' | 'completed' | 'decomposed', completed?: boolean ): Promise<boolean> { try { await databaseService.connect() const result = await databaseService.updateTaskStatus( taskId, status, completed ) await databaseService.close() return result } catch (error) { console.error( `[TaskServer] Error updating task status for ${taskId}: ${error}` ) throw error } } /** * Updates a task's details * @param taskId The unique ID of the task * @param updates The properties to update * @returns True if successful, false otherwise */ export async function updateTaskDetails( taskId: string, updates: TaskUpdate ): Promise<boolean> { try { await databaseService.connect() const result = await databaseService.updateTaskDetails(taskId, updates) await databaseService.close() return result } catch (error) { console.error( `[TaskServer] Error updating task details for ${taskId}: ${error}` ) throw error } } /** * Deletes a task * @param taskId The unique ID of the task * @returns True if successful, false otherwise */ export async function deleteTask(taskId: string): Promise<boolean> { try { await databaseService.connect() const result = await databaseService.deleteTask(taskId) await databaseService.close() return result } catch (error) { console.error(`[TaskServer] Error deleting task ${taskId}: ${error}`) throw error } } /** * Gets history entries for a feature * @param featureId The unique ID of the feature * @param limit Maximum number of entries to retrieve * @returns Array of history entries */ export async function getHistoryForFeature( featureId: string, limit: number = 100 ): Promise<HistoryEntry[]> { try { await databaseService.connect() const history = await databaseService.getHistoryByFeatureId( featureId, limit ) await databaseService.close() return history } catch (error) { console.error( `[TaskServer] Error getting history for feature ${featureId}: ${error}` ) throw error } } /** * Stores intermediate planning state * @param featureId The feature ID being planned * @param prompt The original prompt * @param partialResponse The LLM's partial response * @param planningType The type of planning operation * @returns The generated question ID */ export async function addPlanningState( featureId: string, prompt: string, partialResponse: string, planningType: 'feature_planning' | 'plan_adjustment' ): Promise<string> { try { const questionId = crypto.randomUUID() const now = Math.floor(Date.now() / 1000) await databaseService.connect() await databaseService.runAsync( `INSERT INTO planning_states ( question_id, feature_id, prompt, partial_response, planning_type, created_at ) VALUES (?, ?, ?, ?, ?, ?)`, [questionId, featureId, prompt, partialResponse, planningType, now] ) await databaseService.close() return questionId } catch (error) { console.error(`[TaskServer] Error storing planning state: ${error}`) // Generate a questionId even in error case to avoid breaking the flow return crypto.randomUUID() } } /** * Gets planning state by question ID * @param questionId The question ID * @returns The planning state or null if not found */ export async function getPlanningStateByQuestionId( questionId: string ): Promise<PlanningState | null> { try { if (!questionId) { return null } await databaseService.connect() const row = await databaseService.get( `SELECT question_id, feature_id, prompt, partial_response, planning_type FROM planning_states WHERE question_id = ?`, [questionId] ) await databaseService.close() if (!row) { return null } return { questionId: row.question_id, featureId: row.feature_id, prompt: row.prompt, partialResponse: row.partial_response, planningType: row.planning_type, } } catch (error) { console.error( `[TaskServer] Error getting planning state for question ${questionId}: ${error}` ) // Re-throw error to distinguish DB errors from 'not found' throw error } } /** * Gets planning state by feature ID * @param featureId The feature ID * @returns The most recent planning state for the feature or null if not found */ export async function getPlanningStateByFeatureId( featureId: string ): Promise<PlanningState | null> { try { if (!featureId) { return null } await databaseService.connect() const row = await databaseService.get( `SELECT question_id, feature_id, prompt, partial_response, planning_type FROM planning_states WHERE feature_id = ? ORDER BY created_at DESC LIMIT 1`, [featureId] ) await databaseService.close() if (!row) { return null } return { questionId: row.question_id, featureId: row.feature_id, prompt: row.prompt, partialResponse: row.partial_response, planningType: row.planning_type, } } catch (error) { console.error( `[TaskServer] Error getting planning state for feature ${featureId}: ${error}` ) // Re-throw error to distinguish DB errors from 'not found' throw error } } /** * Clears planning state * @param questionId The question ID * @returns True if successful, false otherwise */ export async function clearPlanningState(questionId: string): Promise<boolean> { try { if (!questionId) { return false } await databaseService.connect() const result = await databaseService.runAsync( `DELETE FROM planning_states WHERE question_id = ?`, [questionId] ) await databaseService.close() return result.changes > 0 } catch (error) { console.error( `[TaskServer] Error clearing planning state for question ${questionId}: ${error}` ) return false } } /** * Clears all planning states for a feature * @param featureId The feature ID * @returns Number of states cleared */ export async function clearPlanningStatesForFeature( featureId: string ): Promise<number> { try { if (!featureId) { return 0 } await databaseService.connect() const result = await databaseService.runAsync( `DELETE FROM planning_states WHERE feature_id = ?`, [featureId] ) await databaseService.close() return result.changes || 0 } catch (error) { console.error( `[TaskServer] Error clearing planning states for feature ${featureId}: ${error}` ) return 0 } } // Utility to get project_path from feature record export async function getProjectPathForFeature( featureId: string ): Promise<string | undefined> { try { await databaseService.connect() // First try to get it from the feature record const feature = await databaseService.getFeatureById(featureId) if (feature && feature.project_path) { await databaseService.close() return feature.project_path } // Fallback to the old method if needed const history = await getHistoryForFeature(featureId, 50) // limit to 50 for efficiency const firstToolCall = history.find( (entry: any) => entry.role === 'tool_call' && entry.content && entry.content.tool === 'plan_feature' && entry.content.params && entry.content.params.project_path ) // If we found it in history but not in the feature record, update the feature record for next time const projectPath = JSON.parse(firstToolCall?.content || '{}')?.params ?.project_path if (projectPath && feature) { try { await databaseService.runAsync( 'UPDATE features SET project_path = ? WHERE id = ?', [projectPath, featureId] ) } catch (updateError) { console.error( `[getProjectPathForFeature] Error updating project_path: ${updateError}` ) } } await databaseService.close() return projectPath } catch (e) { await databaseService.close() return undefined } }

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/jhawkins11/task-manager-mcp'

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