updateTask
Modify task details such as description, priority, or dependencies within a project using the project ID and task ID. Returns updated task information upon successful completion.
Instructions
Updates specific details of an existing task within a project. Requires the project ID and task ID. Allows updating description, priority, and/or dependencies. At least one optional field (description, priority, dependencies) must be provided. Returns the full details of the updated task upon success.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| dependencies | No | Optional. The complete list of task IDs (UUIDs) that this task depends on. Replaces the existing list entirely. Max 50 dependencies. | |
| description | No | Optional. The new textual description for the task (1-1024 characters). | |
| priority | No | Optional. The new priority level for the task ('high', 'medium', or 'low'). | |
| project_id | Yes | The unique identifier (UUID) of the project containing the task to update. This project must exist. | |
| task_id | Yes | The unique identifier (UUID) of the task to update. This task must exist within the specified project. |
Implementation Reference
- src/tools/updateTaskTool.ts:17-54 (handler)The MCP tool handler processRequest: processes args, invokes TaskService.updateTask, returns JSON of updated task or throws appropriate McpError.const processRequest = async (args: UpdateTaskArgs): Promise<{ content: { type: 'text', text: string }[] }> => { logger.info(`[${TOOL_NAME}] Received request with args:`, { ...args, dependencies: args.dependencies ? `[${args.dependencies.length} items]` : undefined }); // Avoid logging potentially large arrays try { // Call the service method to update the task // The service method now returns FullTaskData const updatedTask: FullTaskData = await taskService.updateTask({ project_id: args.project_id, task_id: args.task_id, description: args.description, priority: args.priority, dependencies: args.dependencies, }); // Format the successful response logger.info(`[${TOOL_NAME}] Successfully updated task ${args.task_id} in project ${args.project_id}`); return { content: [{ type: "text" as const, text: JSON.stringify(updatedTask) // Return the full updated task details }] }; } catch (error: unknown) { // Handle potential errors according to systemPatterns.md mapping logger.error(`[${TOOL_NAME}] Error processing request:`, error); if (error instanceof ValidationError) { // Validation error from service (e.g., no fields provided, invalid deps) throw new McpError(ErrorCode.InvalidParams, error.message); } else if (error instanceof NotFoundError) { // Project or task not found - Map to InvalidParams as per SDK limitations/convention throw new McpError(ErrorCode.InvalidParams, error.message); } else { // Generic internal error const message = error instanceof Error ? error.message : 'An unknown error occurred while updating the task.'; throw new McpError(ErrorCode.InternalError, message); } } };
- src/tools/updateTaskParams.ts:17-52 (schema)Zod schemas: UPDATE_TASK_BASE_SCHEMA (base for registration) and TOOL_PARAMS (refined to ensure at least one update field). TOOL_NAME and types defined here.export const UPDATE_TASK_BASE_SCHEMA = z.object({ project_id: z.string() .uuid("The project_id must be a valid UUID.") .describe("The unique identifier (UUID) of the project containing the task to update. This project must exist."), // Required, UUID format task_id: z.string() .uuid("The task_id must be a valid UUID.") // Assuming task IDs are UUIDs for consistency .describe("The unique identifier (UUID) of the task to update. This task must exist within the specified project."), // Required, UUID format description: z.string() .min(1, "Description cannot be empty if provided.") .max(1024, "Description cannot exceed 1024 characters.") .optional() .describe("Optional. The new textual description for the task (1-1024 characters)."), // Optional, string, limits priority: z.enum(priorities) .optional() .describe("Optional. The new priority level for the task ('high', 'medium', or 'low')."), // Optional, enum dependencies: z.array( z.string() .uuid("Each dependency task ID must be a valid UUID.") .describe("A task ID (UUID) that this task should depend on.") ) .max(50, "A task cannot have more than 50 dependencies.") .optional() .describe("Optional. The complete list of task IDs (UUIDs) that this task depends on. Replaces the existing list entirely. Max 50 dependencies."), // Optional, array of UUID strings, limit }); // Refined schema for validation and type inference export const TOOL_PARAMS = UPDATE_TASK_BASE_SCHEMA.refine( data => data.description !== undefined || data.priority !== undefined || data.dependencies !== undefined, { message: "At least one field to update (description, priority, or dependencies) must be provided.", // path: [], // No specific path, applies to the object } );
- src/tools/updateTaskTool.ts:15-60 (registration)updateTaskTool function registers the MCP tool via server.tool() call, defining handler inline.export const updateTaskTool = (server: McpServer, taskService: TaskService): void => { const processRequest = async (args: UpdateTaskArgs): Promise<{ content: { type: 'text', text: string }[] }> => { logger.info(`[${TOOL_NAME}] Received request with args:`, { ...args, dependencies: args.dependencies ? `[${args.dependencies.length} items]` : undefined }); // Avoid logging potentially large arrays try { // Call the service method to update the task // The service method now returns FullTaskData const updatedTask: FullTaskData = await taskService.updateTask({ project_id: args.project_id, task_id: args.task_id, description: args.description, priority: args.priority, dependencies: args.dependencies, }); // Format the successful response logger.info(`[${TOOL_NAME}] Successfully updated task ${args.task_id} in project ${args.project_id}`); return { content: [{ type: "text" as const, text: JSON.stringify(updatedTask) // Return the full updated task details }] }; } catch (error: unknown) { // Handle potential errors according to systemPatterns.md mapping logger.error(`[${TOOL_NAME}] Error processing request:`, error); if (error instanceof ValidationError) { // Validation error from service (e.g., no fields provided, invalid deps) throw new McpError(ErrorCode.InvalidParams, error.message); } else if (error instanceof NotFoundError) { // Project or task not found - Map to InvalidParams as per SDK limitations/convention throw new McpError(ErrorCode.InvalidParams, error.message); } else { // Generic internal error const message = error instanceof Error ? error.message : 'An unknown error occurred while updating the task.'; throw new McpError(ErrorCode.InternalError, message); } } }; // Register the tool with the server using the base schema's shape server.tool(TOOL_NAME, TOOL_DESCRIPTION, UPDATE_TASK_BASE_SCHEMA.shape, processRequest); logger.info(`[${TOOL_NAME}] Tool registered successfully.`); };
- src/tools/index.ts:62-62 (registration)Central registration in tools/index.ts invokes updateTaskTool during server setup.updateTaskTool(server, taskService); // Register the new updateTask tool
- src/services/TaskService.ts:337-418 (helper)TaskService.updateTask: core business logic for updating task fields, validating deps, calling repository, assembling full task data with subtasks/dependencies.public async updateTask(input: { project_id: string; task_id: string; description?: string; priority?: TaskData['priority']; dependencies?: string[]; }): Promise<FullTaskData> { const { project_id, task_id } = input; logger.info(`[TaskService] Attempting to update task ${task_id} in project ${project_id}`); // 1. Validate that at least one field is being updated if (input.description === undefined && input.priority === undefined && input.dependencies === undefined) { throw new ValidationError("At least one field (description, priority, or dependencies) must be provided for update."); } // 2. Validate Project Existence (using repo method) const projectExists = this.projectRepository.findById(project_id); if (!projectExists) { logger.warn(`[TaskService] Project not found: ${project_id}`); throw new NotFoundError(`Project with ID ${project_id} not found.`); } // 3. Validate Task Existence (using repo method - findById also implicitly checks project scope) // We need the task data anyway if dependencies are involved, so fetch it now. const existingTask = this.taskRepository.findById(project_id, task_id); if (!existingTask) { logger.warn(`[TaskService] Task ${task_id} not found in project ${project_id}`); throw new NotFoundError(`Task with ID ${task_id} not found in project ${project_id}.`); } // 4. Validate Dependency Existence if provided if (input.dependencies !== undefined) { if (input.dependencies.length > 0) { const depCheck = this.taskRepository.checkTasksExist(project_id, input.dependencies); if (!depCheck.allExist) { logger.warn(`[TaskService] Invalid dependencies provided for task ${task_id}:`, depCheck.missingIds); throw new ValidationError(`One or more dependency tasks not found in project ${project_id}: ${depCheck.missingIds.join(', ')}`); } // Also check for self-dependency if (input.dependencies.includes(task_id)) { throw new ValidationError(`Task ${task_id} cannot depend on itself.`); } } // If input.dependencies is an empty array, it means "remove all dependencies" } // 5. Prepare payload for repository const updatePayload: { description?: string; priority?: TaskData['priority']; dependencies?: string[] } = {}; if (input.description !== undefined) updatePayload.description = input.description; if (input.priority !== undefined) updatePayload.priority = input.priority; if (input.dependencies !== undefined) updatePayload.dependencies = input.dependencies; // 6. Call Repository update method try { const now = new Date().toISOString(); // The repo method handles the transaction for task update + dependency replacement const updatedTaskData = this.taskRepository.updateTask(project_id, task_id, updatePayload, now); // 7. Fetch full details (including potentially updated dependencies and existing subtasks) // Re-use logic similar to getTaskById const finalDependencies = this.taskRepository.findDependencies(task_id); const finalSubtasks = this.taskRepository.findSubtasks(task_id); const fullUpdatedTask: FullTaskData = { ...updatedTaskData, // Use the data returned by the update method dependencies: finalDependencies, subtasks: finalSubtasks, }; logger.info(`[TaskService] Successfully updated task ${task_id} in project ${project_id}`); return fullUpdatedTask; } catch (error) { logger.error(`[TaskService] Error updating task ${task_id} in project ${project_id}:`, error); // Re-throw specific errors if needed, otherwise let the generic error propagate if (error instanceof Error && error.message.includes('not found')) { // Map repo's generic error for not found back to specific NotFoundError throw new NotFoundError(error.message); } throw error; // Re-throw other errors (like DB constraint errors or unexpected ones) } }