Skip to main content
Glama
vitalio-sh

Enhanced Todoist MCP Server Extended

todoist_move_task

Move a Todoist task to a different project, section, or make it a subtask of another task, including all subtasks.

Instructions

Move a single task (and its subtasks, if any) to a different project, section, or make it a subtask of another task. Provide the taskId and exactly one of: projectId, sectionId, or parentId.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
taskIdYesThe ID of the task to move.
projectIdNoThe ID of the destination project. (Optional, use only one of projectId, sectionId, parentId)
sectionIdNoThe ID of the destination section. (Optional, use only one of projectId, sectionId, parentId)
parentIdNoThe ID of the parent task to move this task under. (Optional, use only one of projectId, sectionId, parentId)

Implementation Reference

  • Executes the tool logic: validates args with isMoveTaskArgs, constructs moveArgs, calls todoistClient.moveTasks([taskId], moveArgs), fetches updated task with getTask, formats and returns success response or error.
    if (name === "todoist_move_task") {
      if (!isMoveTaskArgs(args)) {
        return { content: [{ type: "text", text: "Invalid arguments for move_task. Provide taskId and exactly one of: projectId, sectionId, or parentId (must be a non-empty string)." }], isError: true };
      }
      try {
        const moveArgs: { projectId?: string; sectionId?: string; parentId?: string } = {};
        if (args.projectId) moveArgs.projectId = args.projectId;
        else if (args.sectionId) moveArgs.sectionId = args.sectionId;
        else if (args.parentId) moveArgs.parentId = args.parentId;
    
        // Use moveTasks from SDK v4+
        await todoistClient.moveTasks([args.taskId], moveArgs as any); // Cast to any for MoveTaskArgs as it expects RequireExactlyOne
        
        const movedTask = await todoistClient.getTask(args.taskId);
        return {
          content: [{
            type: "text",
            text: `Task ${args.taskId} moved successfully.\nNew details:\n${formatTask(movedTask)}`
          }],
          isError: false
        };
      } catch (error: any) {
        return { content: [{ type: "text", text: `Error moving task: ${error.message}` }], isError: true };
      }
    }
  • JSON schema defining the input parameters for the todoist_move_task tool: taskId (required), and exactly one optional destination: projectId, sectionId, or parentId.
    const MOVE_TASK_TOOL: Tool = {
      name: "todoist_move_task",
      description: "Move a single task (and its subtasks, if any) to a different project, section, or make it a subtask of another task. Provide the taskId and exactly one of: projectId, sectionId, or parentId.",
      inputSchema: {
        type: "object",
        properties: {
          taskId: {
            type: "string",
            description: "The ID of the task to move."
          },
          projectId: {
            type: "string",
            description: "The ID of the destination project. (Optional, use only one of projectId, sectionId, parentId)"
          },
          sectionId: {
            type: "string",
            description: "The ID of the destination section. (Optional, use only one of projectId, sectionId, parentId)"
          },
          parentId: {
            type: "string",
            description: "The ID of the parent task to move this task under. (Optional, use only one of projectId, sectionId, parentId)"
          }
        },
        required: ["taskId"]
        // Note: Validation for providing exactly one of projectId, sectionId, or parentId
        // is handled in the isMoveTaskArgs type guard and the tool handler.
        // A more complex JSON schema with oneOf could enforce this, but client support varies.
      }
    };
  • src/index.ts:1083-1121 (registration)
    Registers the todoist_move_task tool (as MOVE_TASK_TOOL) in the list of available tools returned by the ListToolsRequest handler.
    server.setRequestHandler(ListToolsRequestSchema, async () => ({
      tools: [
        // Task tools
        CREATE_TASK_TOOL,
        QUICK_ADD_TASK_TOOL,
        GET_TASKS_TOOL,
        GET_TASK_TOOL,
        UPDATE_TASK_TOOL,
        DELETE_TASK_TOOL,
        COMPLETE_TASK_TOOL,
        REOPEN_TASK_TOOL,
        SEARCH_TASKS_TOOL,
        MOVE_TASK_TOOL,
        BULK_MOVE_TASKS_TOOL,
        // Project tools
        GET_PROJECTS_TOOL,
        GET_PROJECT_TOOL,
        CREATE_PROJECT_TOOL,
        UPDATE_PROJECT_TOOL,
        DELETE_PROJECT_TOOL,
        // Section tools
        GET_SECTIONS_TOOL,
        CREATE_SECTION_TOOL,
        UPDATE_SECTION_TOOL,
        DELETE_SECTION_TOOL,
        // Label tools
        CREATE_LABEL_TOOL,
        GET_LABEL_TOOL,
        GET_LABELS_TOOL,
        UPDATE_LABEL_TOOL,
        DELETE_LABEL_TOOL,
        // Comment tools
        CREATE_COMMENT_TOOL,
        GET_COMMENT_TOOL,
        GET_COMMENTS_TOOL,
        UPDATE_COMMENT_TOOL,
        DELETE_COMMENT_TOOL,
      ],
    }));
  • Type guard helper that validates the input arguments for todoist_move_task, ensuring taskId is a string and exactly one non-empty destination (projectId, sectionId, or parentId) is provided.
    function isMoveTaskArgs(args: unknown): args is {
      taskId: string;
      projectId?: string;
      sectionId?: string;
      parentId?: string;
    } {
      if (typeof args !== 'object' || args === null || !('taskId' in args) || typeof (args as any).taskId !== 'string') {
        return false;
      }
      const { projectId, sectionId, parentId } = args as any;
      const destinations = [projectId, sectionId, parentId];
      const providedDestinations = destinations.filter(dest => dest !== undefined && dest !== null && String(dest).trim() !== '');
      
      // Exactly one destination must be provided and be a non-empty string
      return providedDestinations.length === 1 && 
             providedDestinations.every(dest => typeof dest === 'string');
    }
Behavior2/5

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

With no annotations provided, the description carries full burden for behavioral disclosure. While it mentions the tool moves tasks (implying mutation) and includes subtasks, it doesn't disclose important behavioral traits like whether this requires specific permissions, if the move is reversible, what happens to task ordering or due dates, or potential rate limits. For a mutation tool with zero annotation coverage, this is a significant gap.

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 perfectly concise with two sentences: the first states the purpose and scope, the second provides critical parameter guidance. Every word earns its place, and the most important information (the exclusive parameter requirement) is front-loaded in the second sentence.

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

Completeness3/5

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

For a mutation tool with no annotations and no output schema, the description provides adequate basic information about what the tool does and parameter constraints. However, it lacks important contextual details about behavioral implications (permissions, reversibility, effects on task properties) and doesn't describe what the tool returns, which would be expected for a tool that modifies data.

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 schema already documents all parameters thoroughly. The description adds valuable semantic context by explaining the exclusive relationship between projectId, sectionId, and parentId ('exactly one of'), which isn't captured in the schema's individual parameter descriptions. This clarification significantly enhances parameter understanding beyond what the schema provides.

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 verb ('move') and resource ('a single task'), specifies it includes subtasks, and identifies the destination options (different project, section, or as a subtask). It distinguishes from sibling 'todoist_bulk_move_tasks' by specifying 'a single task' rather than bulk operations.

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

Usage Guidelines4/5

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

The description provides clear context for when to use this tool (moving a task with its subtasks to specific destinations) and implicitly distinguishes from 'todoist_bulk_move_tasks' by specifying single-task operation. However, it doesn't explicitly state when NOT to use this tool or provide alternative scenarios beyond the sibling distinction.

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/vitalio-sh/todoist-mcp-server-ext'

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