Skip to main content
Glama
windalfin

ClickUp MCP Server

by windalfin

move_task

Move a ClickUp task to a different list using task ID or name. Specify destination by list ID or name. Note: Task status may reset if destination list has different status options.

Instructions

Move a task to a different list. Valid parameter combinations:

  1. Use taskId + (listId or listName) - preferred

  2. Use taskName + sourceListName + (listId or listName)

WARNING: Task statuses may reset if destination list has different status options.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
taskIdNoID of the task to move (preferred). Use this instead of taskName if you have it.
taskNameNoName of the task to move. When using this, you MUST also provide sourceListName.
sourceListNameNoREQUIRED with taskName: Current list containing the task.
listIdNoID of destination list (preferred). Use this instead of listName if you have it.
listNameNoName of destination list. Only use if you don't have listId.

Implementation Reference

  • The handleMoveTask function is the primary handler executed by the MCP server for 'move_task' tool calls. It handles parameter resolution (IDs from names using workspace hierarchy and task lists), invokes the task service's moveTask method, and returns a formatted MCP content response.
    export async function handleMoveTask(parameters: any) {
      const { taskId, taskName, sourceListName, listId, listName } = parameters;
      
      let targetTaskId = taskId;
      let sourceListId: string | undefined;
      
      // If sourceListName is provided, find the source list ID
      if (sourceListName) {
        const hierarchy = await workspaceService.getWorkspaceHierarchy();
        const listInfo = workspaceService.findIDByNameInHierarchy(hierarchy, sourceListName, 'list');
        
        if (!listInfo) {
          throw new Error(`Source list "${sourceListName}" not found`);
        }
        sourceListId = listInfo.id;
      }
      
      // If no taskId but taskName is provided, look up the task ID
      if (!targetTaskId && taskName) {
        // Find the task in the source list if specified, otherwise search all tasks
        if (sourceListId) {
          const tasks = await taskService.getTasks(sourceListId);
          const foundTask = tasks.find(t => t.name.toLowerCase() === taskName.toLowerCase());
          
          if (!foundTask) {
            throw new Error(`Task "${taskName}" not found in list "${sourceListName}"`);
          }
          targetTaskId = foundTask.id;
        } else {
          // Without a source list, we need to search more broadly
          // This is less efficient but necessary if source list is unknown
          throw new Error("When using taskName, sourceListName must be provided to find the task");
        }
      }
      
      if (!targetTaskId) {
        throw new Error("Either taskId or taskName (with sourceListName) must be provided");
      }
      
      let targetListId = listId;
      
      // If no listId but listName is provided, look up the list ID
      if (!targetListId && listName) {
        const hierarchy = await workspaceService.getWorkspaceHierarchy();
        const listInfo = workspaceService.findIDByNameInHierarchy(hierarchy, listName, 'list');
        
        if (!listInfo) {
          throw new Error(`Target list "${listName}" not found`);
        }
        targetListId = listInfo.id;
      }
      
      if (!targetListId) {
        throw new Error("Either listId or listName must be provided for the target list");
      }
    
      // Move the task
      const task = await taskService.moveTask(targetTaskId, targetListId);
    
      // Format response
      return {
        content: [{
          type: "text",
          text: JSON.stringify({
            id: task.id,
            name: task.name,
            url: task.url,
            moved: true,
            due_date: task.due_date ? formatDueDate(Number(task.due_date)) : undefined,
            list: task.list.name,
            space: task.space.name,
            folder: task.folder?.name
          }, null, 2)
        }]
      };
    }
  • The moveTaskTool object defines the schema, name, and description for the 'move_task' tool, returned by the server's ListTools handler. It also includes an inline handler implementation.
    export const moveTaskTool = {
      name: "move_task",
      description: "Move a task to a different list. Valid parameter combinations:\n1. Use taskId + (listId or listName) - preferred\n2. Use taskName + sourceListName + (listId or listName)\n\nWARNING: 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). Use this instead of taskName if you have it."
          },
          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: []
      },
      async handler({ taskId, taskName, sourceListName, listId, listName }: {
        taskId?: string;
        taskName?: string;
        sourceListName?: string;
        listId?: string;
        listName?: string;
      }) {
        let targetTaskId = taskId;
        let targetListId = listId;
        
        // If no taskId but taskName is provided, look up the task ID
        if (!targetTaskId && taskName) {
          // First find the source list ID if sourceListName is provided
          let sourceListId: string | undefined;
          
          if (sourceListName) {
            const hierarchy = await workspaceService.getWorkspaceHierarchy();
            const listInfo = workspaceService.findIDByNameInHierarchy(hierarchy, sourceListName, 'list');
            
            if (!listInfo) {
              throw new Error(`Source list "${sourceListName}" not found`);
            }
            sourceListId = listInfo.id;
          }
          
          // Now find the task
          const tasks = await taskService.getTasks(sourceListId || '');
          const foundTask = tasks.find(t => t.name.toLowerCase() === taskName.toLowerCase());
          
          if (!foundTask) {
            throw new Error(`Task "${taskName}" not found${sourceListName ? ` in list "${sourceListName}"` : ""}`);
          }
          targetTaskId = foundTask.id;
        }
        
        if (!targetTaskId) {
          throw new Error("Either taskId or taskName must be provided");
        }
        
        // If no listId but listName is provided, look up the list ID
        if (!targetListId && listName) {
          const hierarchy = await workspaceService.getWorkspaceHierarchy();
          const listInfo = workspaceService.findIDByNameInHierarchy(hierarchy, listName, 'list');
          
          if (!listInfo) {
            throw new Error(`List "${listName}" not found`);
          }
          targetListId = listInfo.id;
        }
        
        if (!targetListId) {
          throw new Error("Either listId or listName must be provided");
        }
        
        // Move the task
        const movedTask = await taskService.moveTask(targetTaskId, targetListId);
        
        // Format response
        return {
          content: [{
            type: "text",
            text: JSON.stringify({
              id: movedTask.id,
              name: movedTask.name,
              url: movedTask.url,
              status: movedTask.status?.status || "Unknown",
              due_date: movedTask.due_date ? formatDueDate(Number(movedTask.due_date)) : undefined,
              list: movedTask.list.name,
              moved: true
            }, null, 2)
          }]
        };
      }
    };
  • src/server.ts:106-107 (registration)
    Server routes 'move_task' tool calls to the handleMoveTask function in the CallToolRequestSchema handler.
    case "move_task":
      return handleMoveTask(params);
  • src/server.ts:70-92 (registration)
    moveTaskTool is registered in the list of available tools returned by ListToolsRequestSchema.
        workspaceHierarchyTool,
        createTaskTool,
        getTaskTool,
        getTasksTool,
        updateTaskTool,
        moveTaskTool,
        duplicateTaskTool,
        deleteTaskTool,
        createBulkTasksTool,
        updateBulkTasksTool,
        moveBulkTasksTool,
        deleteBulkTasksTool,
        createListTool,
        createListInFolderTool,
        getListTool,
        updateListTool,
        deleteListTool,
        createFolderTool,
        getFolderTool,
        updateFolderTool,
        deleteFolderTool
      ]
    };
  • Core implementation of task moving in TaskService: fetches original task and destination list, preserves status/priority where possible, creates duplicate in new list, deletes original, returns new task with metadata.
    async moveTask(taskId: string, destinationListId: string): Promise<ClickUpTask> {
      this.logOperation('moveTask', { taskId, destinationListId });
      
      try {
        // First, get both the task and list info in parallel to save time
        const [originalTask, destinationList] = await Promise.all([
          this.getTask(taskId),
          this.listService.getList(destinationListId)
        ]);
    
        const currentStatus = originalTask.status?.status;
        const availableStatuses = destinationList.statuses?.map(s => s.status) || [];
    
        // Determine the appropriate status for the destination list
        let newStatus = availableStatuses.includes(currentStatus || '')
          ? currentStatus // Keep the same status if available in destination list
          : destinationList.statuses?.[0]?.status; // Otherwise use the default (first) status
    
        // Priority mapping: convert string priority to numeric value if needed
        let priorityValue = null;
        if (originalTask.priority) {
          // If priority.id exists and is numeric, use that
          if (originalTask.priority.id) {
            priorityValue = parseInt(originalTask.priority.id);
            // Ensure it's in the valid range 1-4
            if (isNaN(priorityValue) || priorityValue < 1 || priorityValue > 4) {
              priorityValue = null;
            }
          }
        }
    
        // Prepare the task data for the new list
        const taskData: CreateTaskData = {
          name: originalTask.name,
          description: originalTask.description,
          status: newStatus,
          priority: priorityValue,
          due_date: originalTask.due_date ? Number(originalTask.due_date) : undefined,
          assignees: originalTask.assignees?.map(a => a.id) || [],
          // Add any other relevant fields from the original task
        };
    
        // Create new task and delete old one in a single makeRequest call
        return await this.makeRequest(async () => {
          // First create the new task
          const response = await this.client.post<ClickUpTask>(
            `/list/${destinationListId}/task`,
            taskData
          );
    
          // Then delete the original task
          await this.client.delete(`/task/${taskId}`);
    
          // Add a property to indicate the task was moved
          const newTask = {
            ...response.data,
            moved: true,
            originalId: taskId
          };
    
          return newTask;
        });
      } catch (error) {
        throw this.handleError(error, 'Failed to move task');
      }
    }
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It effectively warns about a critical side effect ('Task statuses may reset if destination list has different status options'), which is valuable beyond the basic 'move' action. However, it doesn't cover other potential behaviors like error conditions or permission requirements.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is appropriately sized and front-loaded, with the core purpose stated first, followed by parameter guidelines and a critical warning. Every sentence earns its place by providing essential usage information without redundancy or fluff.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the complexity (5 parameters, no annotations, no output schema), the description is mostly complete. It covers purpose, parameter logic, and a key behavioral warning. However, it lacks details on return values or error handling, which would be helpful for a mutation tool with no output schema.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3. The description adds significant value by explaining parameter combinations and dependencies (e.g., taskName requires sourceListName, preferences for taskId/listId), which clarifies usage beyond the schema's individual parameter descriptions. It doesn't fully detail all semantics but compensates well.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the specific action ('Move a task') and resource ('to a different list'), distinguishing it from siblings like move_bulk_tasks (bulk operations) or update_task (modifying task properties). It precisely defines what the tool does without being vague or tautological.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides explicit guidance on when to use this tool vs. alternatives by detailing two valid parameter combinations with preferences (e.g., 'preferred' for taskId+listId/listName) and warnings about when taskName requires sourceListName. It also implicitly distinguishes from move_bulk_tasks by focusing on single-task movement.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

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/windalfin/clickup-mcp-server'

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