@taazkareem/clickup-mcp-server
Version:
ClickUp MCP Server - Integrate ClickUp tasks with AI through Model Context Protocol
470 lines (466 loc) • 20 kB
JavaScript
/**
* SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
* SPDX-License-Identifier: MIT
*
* ClickUp MCP Single Task Operations
*
* This module defines tools for single task operations including creating,
* updating, moving, duplicating, and deleting tasks, as well as retrieving
* task details and comments.
*/
import { clickUpServices } from '../../services/shared.js';
// Use shared services instance
const { task: taskService } = clickUpServices;
//=============================================================================
// COMMON VALIDATION UTILITIES
//=============================================================================
// Common validation functions
const validateTaskName = (name) => {
if (!name || typeof name !== 'string') {
throw new Error("A task name is required");
}
const trimmedName = name.trim();
if (trimmedName.length === 0) {
throw new Error("Task name cannot be empty or only whitespace");
}
return trimmedName;
};
const validatePriority = (priority) => {
if (priority !== undefined && (typeof priority !== 'number' || priority < 1 || priority > 4)) {
throw new Error("Priority must be a number between 1 and 4");
}
};
const validateDueDate = (dueDate) => {
if (dueDate && typeof dueDate !== 'string') {
throw new Error("Due date must be a string in timestamp format or natural language");
}
};
// Common error handler
const handleOperationError = (operation, error) => {
console.error(`Error ${operation}:`, error);
throw error;
};
//=============================================================================
// SINGLE TASK OPERATION TOOLS
//=============================================================================
/**
* Tool definition for creating a single task
*/
export const createTaskTool = {
name: "create_task",
description: `Creates a single task in a ClickUp list. Use listId (preferred) or listName. Required: name + list info. For multiple tasks use create_bulk_tasks. Can create subtasks via parent param. Supports custom fields as array of {id, value}. Supports assignees as array of user IDs, emails, or usernames.`,
inputSchema: {
type: "object",
properties: {
name: {
type: "string",
description: "REQUIRED: Name of the task. Put a relevant emoji followed by a blank space before the name."
},
listId: {
type: "string",
description: "REQUIRED (unless listName provided): ID of the list to create the task in. If you have this ID from a previous response, use it directly rather than looking up by name."
},
listName: {
type: "string",
description: "REQUIRED (unless listId provided): Name of the list to create the task in - will automatically find the list by name."
},
description: {
type: "string",
description: "Optional plain text description for the task"
},
markdown_description: {
type: "string",
description: "Optional markdown formatted description for the task. If provided, this takes precedence over description"
},
status: {
type: "string",
description: "Optional: Override the default ClickUp status. In most cases, you should omit this to use ClickUp defaults"
},
priority: {
type: "number",
description: "Optional priority of the task (1-4), where 1 is urgent/highest priority and 4 is lowest priority. Only set this when explicitly requested."
},
dueDate: {
type: "string",
description: "Optional due date. Supports Unix timestamps (ms) or natural language like '1 hour from now', 'tomorrow', 'next week', etc."
},
startDate: {
type: "string",
description: "Optional start date. Supports Unix timestamps (ms) or natural language like 'now', 'start of today', etc."
},
parent: {
type: "string",
description: "Optional ID of the parent task. When specified, this task will be created as a subtask of the specified parent task."
},
tags: {
type: "array",
items: {
type: "string"
},
description: "Optional array of tag names to assign to the task. The tags must already exist in the space."
},
custom_fields: {
type: "array",
items: {
type: "object",
properties: {
id: {
type: "string",
description: "ID of the custom field"
},
value: {
description: "Value for the custom field. Type depends on the field type."
}
},
required: ["id", "value"]
},
description: "Optional array of custom field values to set on the task. Each object must have an 'id' and 'value' property."
},
check_required_custom_fields: {
type: "boolean",
description: "Optional flag to check if all required custom fields are set before saving the task."
},
assignees: {
type: "array",
items: {
oneOf: [
{ type: "number" },
{ type: "string" }
]
},
description: "Optional array of assignee user IDs (numbers), emails, or usernames to assign to the task."
}
}
}
};
/**
* Tool definition for updating a task
*/
export const updateTaskTool = {
name: "update_task",
description: `Updates task properties. Use taskId (preferred) or taskName + optional listName. At least one update field required. Custom fields supported as array of {id, value}. Supports assignees as array of user IDs, emails, or usernames. WARNING: Using taskName without listName may match multiple tasks.`,
inputSchema: {
type: "object",
properties: {
taskId: {
type: "string",
description: "ID of task to update (preferred). Works with both regular task IDs (9 characters) and custom IDs with uppercase prefixes (like 'DEV-1234')."
},
taskName: {
type: "string",
description: "Name of task to update. The tool will search for tasks with this name across all lists unless listName is specified."
},
listName: {
type: "string",
description: "Optional: Name of list containing the task. Providing this narrows the search to a specific list, improving performance and reducing ambiguity."
},
name: {
type: "string",
description: "New name for the task. Include emoji prefix if appropriate."
},
description: {
type: "string",
description: "New plain text description. Will be ignored if markdown_description is provided."
},
markdown_description: {
type: "string",
description: "New markdown description. Takes precedence over plain text description."
},
status: {
type: "string",
description: "New status. Must be valid for the task's current list."
},
priority: {
type: "number",
nullable: true,
enum: [1, 2, 3, 4, null],
description: "New priority: 1 (urgent) to 4 (low). Set null to clear priority."
},
dueDate: {
type: "string",
description: "New due date. Supports both Unix timestamps (in milliseconds) and natural language expressions like '1 hour from now', 'tomorrow', 'next week', or '3 days from now'."
},
startDate: {
type: "string",
description: "New start date. Supports both Unix timestamps (in milliseconds) and natural language expressions."
},
time_estimate: {
type: "string",
description: "Time estimate for the task. For best compatibility with the ClickUp API, use a numeric value in minutes (e.g., '150' for 2h 30m)"
},
custom_fields: {
type: "array",
items: {
type: "object",
properties: {
id: {
type: "string",
description: "ID of the custom field"
},
value: {
description: "Value for the custom field. Type depends on the field type."
}
},
required: ["id", "value"]
},
description: "Optional array of custom field values to set on the task. Each object must have an 'id' and 'value' property."
},
assignees: {
type: "array",
items: {
oneOf: [
{ type: "number" },
{ type: "string" }
]
},
description: "Optional array of assignee user IDs (numbers), emails, or usernames to assign to the task."
}
}
}
};
/**
* Tool definition for moving a task
*/
export const moveTaskTool = {
name: "move_task",
description: `Moves task to different list. Use taskId + (listId/listName) preferred, or taskName + sourceListName + (listId/listName). WARNING: Task statuses may reset if destination list has different status options.`,
inputSchema: {
type: "object",
properties: {
taskId: {
type: "string",
description: "ID of the task to move (preferred). Works with both regular task IDs (9 characters) and custom IDs with uppercase prefixes (like 'DEV-1234')."
},
taskName: {
type: "string",
description: "Name of the task to move. When using this, you MUST also provide sourceListName."
},
sourceListName: {
type: "string",
description: "REQUIRED with taskName: Current list containing the task."
},
listId: {
type: "string",
description: "ID of destination list (preferred). Use this instead of listName if you have it."
},
listName: {
type: "string",
description: "Name of destination list. Only use if you don't have listId."
}
},
required: []
}
};
/**
* Tool definition for duplicating a task
*/
export const duplicateTaskTool = {
name: "duplicate_task",
description: `Creates copy of task in same/different list. Use taskId + optional (listId/listName), or taskName + sourceListName + optional (listId/listName). Preserves original properties. Default: same list as original.`,
inputSchema: {
type: "object",
properties: {
taskId: {
type: "string",
description: "ID of task to duplicate (preferred). Works with both regular task IDs (9 characters) and custom IDs with uppercase prefixes (like 'DEV-1234')."
},
taskName: {
type: "string",
description: "Name of task to duplicate. When using this, you MUST provide sourceListName."
},
sourceListName: {
type: "string",
description: "REQUIRED with taskName: List containing the original task."
},
listId: {
type: "string",
description: "ID of list for the duplicate (optional). Defaults to same list as original."
},
listName: {
type: "string",
description: "Name of list for the duplicate. Only use if you don't have listId."
}
},
required: []
}
};
/**
* Tool definition for retrieving task details
*/
export const getTaskTool = {
name: "get_task",
description: `Gets task details by taskId (works with regular/custom IDs) or taskName. For taskName search, provide listName for faster lookup. Set subtasks=true to include all subtask details.`,
inputSchema: {
type: "object",
properties: {
taskId: {
type: "string",
description: "ID of task to retrieve (preferred). Works with both regular task IDs (9 characters) and custom IDs with uppercase prefixes (like 'DEV-1234'). The system automatically detects the ID format."
},
taskName: {
type: "string",
description: "Name of task to retrieve. Can be used alone for a global search, or with listName for faster lookup."
},
listName: {
type: "string",
description: "Name of list containing the task. Optional but recommended when using taskName."
},
customTaskId: {
type: "string",
description: "Custom task ID (e.g., 'DEV-1234'). Only use this if you want to explicitly force custom ID lookup. In most cases, you can just use taskId which auto-detects ID format."
},
subtasks: {
type: "boolean",
description: "Whether to include subtasks in the response. Set to true to retrieve full details of all subtasks."
}
}
}
};
/**
* Tool definition for retrieving tasks from a list
*/
export const getTasksTool = {
name: "get_tasks",
description: `Purpose: Retrieve tasks from a list with optional filtering.
Valid Usage:
1. Use listId (preferred)
2. Use listName
Requirements:
- EITHER listId OR listName is REQUIRED
Notes:
- Use filters (archived, statuses, etc.) to narrow down results
- Pagination available through page parameter
- Sorting available through order_by and reverse parameters`,
inputSchema: {
type: "object",
properties: {
listId: {
type: "string",
description: "ID of list to get tasks from (preferred). Use this instead of listName if you have it."
},
listName: {
type: "string",
description: "Name of list to get tasks from. Only use if you don't have listId."
},
subtasks: {
type: "boolean",
description: "Include subtasks"
},
statuses: {
type: "array",
items: { type: "string" },
description: "Filter by status names (e.g. ['To Do', 'In Progress'])"
},
archived: {
type: "boolean",
description: "Include archived tasks"
},
page: {
type: "number",
description: "Page number for pagination (starts at 0)"
},
order_by: {
type: "string",
description: "Sort field: due_date, created, updated"
},
reverse: {
type: "boolean",
description: "Reverse sort order (descending)"
}
},
required: []
}
};
/**
* Tool definition for retrieving task comments
*/
export const getTaskCommentsTool = {
name: "get_task_comments",
description: `Gets task comments. Use taskId (preferred) or taskName + optional listName. Use start/startId params for pagination. Task names may not be unique across lists.`,
inputSchema: {
type: "object",
properties: {
taskId: {
type: "string",
description: "ID of task to retrieve comments for (preferred). Works with both regular task IDs (9 characters) and custom IDs with uppercase prefixes (like 'DEV-1234')."
},
taskName: {
type: "string",
description: "Name of task to retrieve comments for. Warning: Task names may not be unique."
},
listName: {
type: "string",
description: "Name of list containing the task. Helps find the right task when using taskName."
},
start: {
type: "number",
description: "Timestamp (in milliseconds) to start retrieving comments from. Used for pagination."
},
startId: {
type: "string",
description: "Comment ID to start from. Used together with start for pagination."
}
}
}
};
/**
* Tool definition for creating a comment on a task
*/
export const createTaskCommentTool = {
name: "create_task_comment",
description: `Creates task comment. Use taskId (preferred) or taskName + listName. Required: commentText. Optional: notifyAll to notify assignees, assignee to assign comment.`,
inputSchema: {
type: "object",
properties: {
taskId: {
type: "string",
description: "ID of task to comment on (preferred). Works with both regular task IDs (9 characters) and custom IDs with uppercase prefixes (like 'DEV-1234')."
},
taskName: {
type: "string",
description: "Name of task to comment on. When using this parameter, you MUST also provide listName."
},
listName: {
type: "string",
description: "Name of list containing the task. REQUIRED when using taskName."
},
commentText: {
type: "string",
description: "REQUIRED: Text content of the comment to create."
},
notifyAll: {
type: "boolean",
description: "Whether to notify all assignees. Default is false."
},
assignee: {
type: "number",
description: "Optional user ID to assign the comment to."
}
},
required: ["commentText"]
}
};
/**
* Tool definition for deleting a task
*/
export const deleteTaskTool = {
name: "delete_task",
description: `PERMANENTLY deletes task. Use taskId (preferred/safest) or taskName + optional listName. WARNING: Cannot be undone. Using taskName without listName may match multiple tasks.`,
inputSchema: {
type: "object",
properties: {
taskId: {
type: "string",
description: "ID of task to delete (preferred). Works with both regular task IDs (9 characters) and custom IDs with uppercase prefixes (like 'DEV-1234')."
},
taskName: {
type: "string",
description: "Name of task to delete. The tool will search for tasks with this name across all lists unless listName is specified."
},
listName: {
type: "string",
description: "Optional: Name of list containing the task. Providing this narrows the search to a specific list, improving performance and reducing ambiguity."
}
}
}
};