@taazkareem/clickup-mcp-server
Version:
ClickUp MCP Server - Integrate ClickUp tasks with AI through Model Context Protocol
429 lines (428 loc) • 15.9 kB
JavaScript
/**
* SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
* SPDX-License-Identifier: MIT
*
* ClickUp MCP List Tools
*
* This module defines list-related tools including creating, updating,
* retrieving, and deleting lists. It supports creating lists both in spaces
* and in folders.
*/
import { listService, workspaceService } from '../services/shared.js';
import config from '../config.js';
import { sponsorService } from '../utils/sponsor-service.js';
/**
* Tool definition for creating a list directly in a space
*/
export const createListTool = {
name: "create_list",
description: `Creates a list in a ClickUp space. Use spaceId (preferred) or spaceName + list name. Name is required. For lists in folders, use create_list_in_folder. Optional: content, dueDate, priority, assignee, status.`,
inputSchema: {
type: "object",
properties: {
name: {
type: "string",
description: "Name of the list"
},
spaceId: {
type: "string",
description: "ID of the space to create the list in. Use this instead of spaceName if you have the ID."
},
spaceName: {
type: "string",
description: "Name of the space to create the list in. Alternative to spaceId - one of them MUST be provided."
},
content: {
type: "string",
description: "Description or content of the list"
},
dueDate: {
type: "string",
description: "Due date for the list (Unix timestamp in milliseconds)"
},
priority: {
type: "number",
description: "Priority level: 1 (urgent), 2 (high), 3 (normal), 4 (low)"
},
assignee: {
type: "number",
description: "User ID to assign the list to"
},
status: {
type: "string",
description: "Status of the list"
}
},
required: ["name"]
}
};
/**
* Tool definition for creating a list within a folder
*/
export const createListInFolderTool = {
name: "create_list_in_folder",
description: `Creates a list in a ClickUp folder. Use folderId (preferred) or folderName + space info + list name. Name is required. When using folderName, spaceId/spaceName required as folder names may not be unique. Optional: content, status.`,
inputSchema: {
type: "object",
properties: {
name: {
type: "string",
description: "Name of the list"
},
folderId: {
type: "string",
description: "ID of the folder to create the list in. If you have this, you don't need folderName or space information."
},
folderName: {
type: "string",
description: "Name of the folder to create the list in. When using this, you MUST also provide either spaceName or spaceId."
},
spaceId: {
type: "string",
description: "ID of the space containing the folder. Required when using folderName instead of folderId."
},
spaceName: {
type: "string",
description: "Name of the space containing the folder. Required when using folderName instead of folderId."
},
content: {
type: "string",
description: "Description or content of the list"
},
status: {
type: "string",
description: "Status of the list (uses folder default if not specified)"
}
},
required: ["name"]
}
};
/**
* Tool definition for retrieving list details
*/
export const getListTool = {
name: "get_list",
description: `Gets details of a ClickUp list. Use listId (preferred) or listName. Returns list details including name, content, and space info. ListId more reliable as names may not be unique.`,
inputSchema: {
type: "object",
properties: {
listId: {
type: "string",
description: "ID of the list to retrieve. Use this instead of listName if you have the ID."
},
listName: {
type: "string",
description: "Name of the list to retrieve. May be ambiguous if multiple lists have the same name."
}
},
required: []
}
};
/**
* Tool definition for updating a list
*/
export const updateListTool = {
name: "update_list",
description: `Updates a ClickUp list. Use listId (preferred) or listName + at least one update field (name/content/status). ListId more reliable as names may not be unique. Only specified fields updated.`,
inputSchema: {
type: "object",
properties: {
listId: {
type: "string",
description: "ID of the list to update. Use this instead of listName if you have the ID."
},
listName: {
type: "string",
description: "Name of the list to update. May be ambiguous if multiple lists have the same name."
},
name: {
type: "string",
description: "New name for the list"
},
content: {
type: "string",
description: "New description or content for the list"
},
status: {
type: "string",
description: "New status for the list"
}
},
required: []
}
};
/**
* Tool definition for deleting a list
*/
export const deleteListTool = {
name: "delete_list",
description: `PERMANENTLY deletes a ClickUp list and all its tasks. Use listId (preferred/safest) or listName. WARNING: Cannot be undone, all tasks will be deleted, listName risky if not unique.`,
inputSchema: {
type: "object",
properties: {
listId: {
type: "string",
description: "ID of the list to delete. Use this instead of listName if you have the ID."
},
listName: {
type: "string",
description: "Name of the list to delete. May be ambiguous if multiple lists have the same name."
}
},
required: []
}
};
/**
* Helper function to find a list ID by name
* Uses the ClickUp service's global list search functionality
*/
export async function findListIDByName(workspaceService, listName) {
// Use workspace service to find the list in the hierarchy
const hierarchy = await workspaceService.getWorkspaceHierarchy();
const listInfo = workspaceService.findIDByNameInHierarchy(hierarchy, listName, 'list');
if (!listInfo)
return null;
return { id: listInfo.id, name: listName };
}
/**
* Handler for the create_list tool
* Creates a new list directly in a space
*/
export async function handleCreateList(parameters) {
const { name, spaceId, spaceName, content, dueDate, priority, assignee, status } = parameters;
// Validate required fields
if (!name) {
throw new Error("List name is required");
}
let targetSpaceId = spaceId;
// If no spaceId but spaceName is provided, look up the space ID
if (!targetSpaceId && spaceName) {
const spaceIdResult = await workspaceService.findSpaceIDByName(spaceName);
if (!spaceIdResult) {
throw new Error(`Space "${spaceName}" not found`);
}
targetSpaceId = spaceIdResult;
}
if (!targetSpaceId) {
throw new Error("Either spaceId or spaceName must be provided");
}
// Prepare list data
const listData = {
name
};
// Add optional fields if provided
if (content)
listData.content = content;
if (dueDate)
listData.due_date = parseInt(dueDate);
if (priority)
listData.priority = priority;
if (assignee)
listData.assignee = assignee;
if (status)
listData.status = status;
try {
// Create the list
const newList = await listService.createList(targetSpaceId, listData);
return sponsorService.createResponse({
id: newList.id,
name: newList.name,
content: newList.content,
space: {
id: newList.space.id,
name: newList.space.name
},
url: `https://app.clickup.com/${config.clickupTeamId}/v/l/${newList.id}`,
message: `List "${name}" created successfully`
}, true);
}
catch (error) {
return sponsorService.createErrorResponse(`Failed to create list: ${error.message}`);
}
}
/**
* Handler for the create_list_in_folder tool
* Creates a new list inside a folder
*/
export async function handleCreateListInFolder(parameters) {
const { name, folderId, folderName, spaceId, spaceName, content, status } = parameters;
// Validate required fields
if (!name) {
throw new Error("List name is required");
}
let targetFolderId = folderId;
// If no folderId but folderName is provided, look up the folder ID
if (!targetFolderId && folderName) {
let targetSpaceId = spaceId;
// If no spaceId provided but spaceName is, look up the space ID first
if (!targetSpaceId && spaceName) {
const spaceIdResult = await workspaceService.findSpaceByName(spaceName);
if (!spaceIdResult) {
throw new Error(`Space "${spaceName}" not found`);
}
targetSpaceId = spaceIdResult.id;
}
if (!targetSpaceId) {
throw new Error("When using folderName to identify a folder, you must also provide either spaceId or spaceName to locate the correct folder. This is because folder names might not be unique across different spaces.");
}
// Find the folder in the workspace hierarchy
const hierarchy = await workspaceService.getWorkspaceHierarchy();
const folderInfo = workspaceService.findIDByNameInHierarchy(hierarchy, folderName, 'folder');
if (!folderInfo) {
throw new Error(`Folder "${folderName}" not found in space`);
}
targetFolderId = folderInfo.id;
}
if (!targetFolderId) {
throw new Error("Either folderId or folderName must be provided");
}
// Prepare list data
const listData = {
name
};
// Add optional fields if provided
if (content)
listData.content = content;
if (status)
listData.status = status;
try {
// Create the list in the folder
const newList = await listService.createListInFolder(targetFolderId, listData);
return sponsorService.createResponse({
id: newList.id,
name: newList.name,
content: newList.content,
folder: {
id: newList.folder.id,
name: newList.folder.name
},
space: {
id: newList.space.id,
name: newList.space.name
},
url: `https://app.clickup.com/${config.clickupTeamId}/v/l/${newList.id}`,
message: `List "${name}" created successfully in folder "${newList.folder.name}"`
}, true);
}
catch (error) {
return sponsorService.createErrorResponse(`Failed to create list in folder: ${error.message}`);
}
}
/**
* Handler for the get_list tool
* Retrieves details about a specific list
*/
export async function handleGetList(parameters) {
const { listId, listName } = parameters;
let targetListId = listId;
// If no listId provided but listName is, look up the list ID
if (!targetListId && listName) {
const listResult = await findListIDByName(workspaceService, listName);
if (!listResult) {
throw new Error(`List "${listName}" not found`);
}
targetListId = listResult.id;
}
if (!targetListId) {
throw new Error("Either listId or listName must be provided");
}
try {
// Get the list
const list = await listService.getList(targetListId);
return sponsorService.createResponse({
id: list.id,
name: list.name,
content: list.content,
space: {
id: list.space.id,
name: list.space.name
},
url: `https://app.clickup.com/${config.clickupTeamId}/v/l/${list.id}`
}, true);
}
catch (error) {
return sponsorService.createErrorResponse(`Failed to retrieve list: ${error.message}`);
}
}
/**
* Handler for the update_list tool
* Updates an existing list's properties
*/
export async function handleUpdateList(parameters) {
const { listId, listName, name, content, status } = parameters;
let targetListId = listId;
// If no listId provided but listName is, look up the list ID
if (!targetListId && listName) {
const listResult = await findListIDByName(workspaceService, listName);
if (!listResult) {
throw new Error(`List "${listName}" not found`);
}
targetListId = listResult.id;
}
if (!targetListId) {
throw new Error("Either listId or listName must be provided");
}
// Ensure at least one update field is provided
if (!name && !content && !status) {
throw new Error("At least one of name, content, or status must be provided for update");
}
// Prepare update data
const updateData = {};
if (name)
updateData.name = name;
if (content)
updateData.content = content;
if (status)
updateData.status = status;
try {
// Update the list
const updatedList = await listService.updateList(targetListId, updateData);
return sponsorService.createResponse({
id: updatedList.id,
name: updatedList.name,
content: updatedList.content,
space: {
id: updatedList.space.id,
name: updatedList.space.name
},
url: `https://app.clickup.com/${config.clickupTeamId}/v/l/${updatedList.id}`,
message: `List "${updatedList.name}" updated successfully`
}, true);
}
catch (error) {
return sponsorService.createErrorResponse(`Failed to update list: ${error.message}`);
}
}
/**
* Handler for the delete_list tool
* Permanently removes a list from the workspace
*/
export async function handleDeleteList(parameters) {
const { listId, listName } = parameters;
let targetListId = listId;
// If no listId provided but listName is, look up the list ID
if (!targetListId && listName) {
const listResult = await findListIDByName(workspaceService, listName);
if (!listResult) {
throw new Error(`List "${listName}" not found`);
}
targetListId = listResult.id;
}
if (!targetListId) {
throw new Error("Either listId or listName must be provided");
}
try {
// Get list details before deletion for confirmation message
const list = await listService.getList(targetListId);
const listName = list.name;
// Delete the list
await listService.deleteList(targetListId);
return sponsorService.createResponse({
success: true,
message: `List "${listName || targetListId}" deleted successfully`
}, true);
}
catch (error) {
return sponsorService.createErrorResponse(`Failed to delete list: ${error.message}`);
}
}