UNPKG

@taazkareem/clickup-mcp-server

Version:

ClickUp MCP Server - Integrate ClickUp tasks with AI through Model Context Protocol

332 lines (331 loc) 13 kB
/** * SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com> * SPDX-License-Identifier: MIT * * ClickUp MCP Folder Tools * * This module defines folder-related tools for creating, retrieving, * updating, and deleting folders in the ClickUp workspace hierarchy. */ import { clickUpServices } from '../services/shared.js'; import { sponsorService } from '../utils/sponsor-service.js'; // Use shared services instance const { folder: folderService, workspace: workspaceService } = clickUpServices; /** * Tool definition for creating a folder */ export const createFolderTool = { name: "create_folder", description: `Creates folder in ClickUp space. Use spaceId (preferred) or spaceName + folder name. Optional: override_statuses for folder-specific statuses. Use create_list_in_folder to add lists after creation.`, inputSchema: { type: "object", properties: { name: { type: "string", description: "Name of the folder" }, spaceId: { type: "string", description: "ID of the space to create the folder in (preferred). Use this instead of spaceName if you have it." }, spaceName: { type: "string", description: "Name of the space to create the folder in. Only use if you don't have spaceId." }, override_statuses: { type: "boolean", description: "Whether to override space statuses with folder-specific statuses" } }, required: ["name"] } }; /** * Tool definition for retrieving folder details */ export const getFolderTool = { name: "get_folder", description: `Gets folder details. Use folderId (preferred) or folderName + (spaceId/spaceName). Helps understand folder structure before creating/updating lists.`, inputSchema: { type: "object", properties: { folderId: { type: "string", description: "ID of folder to retrieve (preferred). Use this instead of folderName if you have it." }, folderName: { type: "string", description: "Name of folder to retrieve. When using this, you MUST also provide spaceId or spaceName." }, spaceId: { type: "string", description: "ID of space containing the folder (required with folderName). Use this instead of spaceName if you have it." }, spaceName: { type: "string", description: "Name of space containing the folder (required with folderName). Only use if you don't have spaceId." } }, required: [] } }; /** * Tool definition for updating a folder */ export const updateFolderTool = { name: "update_folder", description: `Updates folder properties. Use folderId (preferred) or folderName + (spaceId/spaceName). At least one update field (name/override_statuses) required. Changes apply to all lists in folder.`, inputSchema: { type: "object", properties: { folderId: { type: "string", description: "ID of folder to update (preferred). Use this instead of folderName if you have it." }, folderName: { type: "string", description: "Name of folder to update. When using this, you MUST also provide spaceId or spaceName." }, spaceId: { type: "string", description: "ID of space containing the folder (required with folderName). Use this instead of spaceName if you have it." }, spaceName: { type: "string", description: "Name of space containing the folder (required with folderName). Only use if you don't have spaceId." }, name: { type: "string", description: "New name for the folder" }, override_statuses: { type: "boolean", description: "Whether to override space statuses with folder-specific statuses" } }, required: [] } }; /** * Tool definition for deleting a folder */ export const deleteFolderTool = { name: "delete_folder", description: `PERMANENTLY deletes folder and all contents. Use folderId (preferred/safest) or folderName + (spaceId/spaceName). WARNING: Cannot be undone, all lists/tasks deleted, folderName risky if not unique.`, inputSchema: { type: "object", properties: { folderId: { type: "string", description: "ID of folder to delete (preferred). Use this instead of folderName for safety." }, folderName: { type: "string", description: "Name of folder to delete. When using this, you MUST also provide spaceId or spaceName." }, spaceId: { type: "string", description: "ID of space containing the folder (required with folderName). Use this instead of spaceName if you have it." }, spaceName: { type: "string", description: "Name of space containing the folder (required with folderName). Only use if you don't have spaceId." } }, required: [] } }; /** * Handler for the create_folder tool * Creates a new folder in a space */ export async function handleCreateFolder(parameters) { const { name, spaceId, spaceName, override_statuses } = parameters; // Validate required fields if (!name) { throw new Error("Folder 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.findSpaceByName(spaceName); if (!spaceIdResult) { throw new Error(`Space "${spaceName}" not found`); } targetSpaceId = spaceIdResult.id; } if (!targetSpaceId) { throw new Error("Either spaceId or spaceName must be provided"); } // Prepare folder data const folderData = { name }; // Add optional fields if provided if (override_statuses !== undefined) folderData.override_statuses = override_statuses; try { // Create the folder const newFolder = await folderService.createFolder(targetSpaceId, folderData); return sponsorService.createResponse({ id: newFolder.id, name: newFolder.name, space: { id: newFolder.space.id, name: newFolder.space.name }, message: `Folder "${newFolder.name}" created successfully` }, true); } catch (error) { return sponsorService.createErrorResponse(`Failed to create folder: ${error.message}`); } } /** * Handler for the get_folder tool * Retrieves details about a specific folder */ export async function handleGetFolder(parameters) { const { folderId, folderName, spaceId, spaceName } = parameters; let targetFolderId = folderId; // If no folderId provided but folderName is, 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("Either spaceId or spaceName must be provided when using folderName"); } const folderResult = await folderService.findFolderByName(targetSpaceId, folderName); if (!folderResult) { throw new Error(`Folder "${folderName}" not found in space`); } targetFolderId = folderResult.id; } if (!targetFolderId) { throw new Error("Either folderId or folderName must be provided"); } try { // Get the folder const folder = await folderService.getFolder(targetFolderId); return sponsorService.createResponse({ id: folder.id, name: folder.name, space: { id: folder.space.id, name: folder.space.name } }, true); } catch (error) { return sponsorService.createErrorResponse(`Failed to retrieve folder: ${error.message}`); } } /** * Handler for the update_folder tool * Updates an existing folder's properties */ export async function handleUpdateFolder(parameters) { const { folderId, folderName, name, override_statuses, spaceId, spaceName } = parameters; let targetFolderId = folderId; // If no folderId provided but folderName is, 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("Either spaceId or spaceName must be provided when using folderName"); } const folderResult = await folderService.findFolderByName(targetSpaceId, folderName); if (!folderResult) { throw new Error(`Folder "${folderName}" not found in space`); } targetFolderId = folderResult.id; } if (!targetFolderId) { throw new Error("Either folderId or folderName must be provided"); } // Ensure at least one update field is provided if (!name && override_statuses === undefined) { throw new Error("At least one of name or override_statuses must be provided for update"); } // Prepare update data const updateData = {}; if (name) updateData.name = name; if (override_statuses !== undefined) updateData.override_statuses = override_statuses; try { // Update the folder const updatedFolder = await folderService.updateFolder(targetFolderId, updateData); return sponsorService.createResponse({ id: updatedFolder.id, name: updatedFolder.name, space: { id: updatedFolder.space.id, name: updatedFolder.space.name }, message: `Folder "${updatedFolder.name}" updated successfully` }, true); } catch (error) { return sponsorService.createErrorResponse(`Failed to update folder: ${error.message}`); } } /** * Handler for the delete_folder tool * Permanently removes a folder from the workspace */ export async function handleDeleteFolder(parameters) { const { folderId, folderName, spaceId, spaceName } = parameters; let targetFolderId = folderId; // If no folderId provided but folderName is, 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("Either spaceId or spaceName must be provided when using folderName"); } const folderResult = await folderService.findFolderByName(targetSpaceId, folderName); if (!folderResult) { throw new Error(`Folder "${folderName}" not found in space`); } targetFolderId = folderResult.id; } if (!targetFolderId) { throw new Error("Either folderId or folderName must be provided"); } try { // Get folder details before deletion for confirmation message const folder = await folderService.getFolder(targetFolderId); const folderName = folder.name; // Delete the folder await folderService.deleteFolder(targetFolderId); return sponsorService.createResponse({ success: true, message: `Folder "${folderName || targetFolderId}" deleted successfully` }, true); } catch (error) { return sponsorService.createErrorResponse(`Failed to delete folder: ${error.message}`); } }