Skip to main content
Glama
greirson

Todoist MCP Server

todoist_task_hierarchy_get

Retrieve a Todoist task and its subtasks in a hierarchical structure to view relationships and dependencies between tasks.

Instructions

Get a task with all its subtasks in a hierarchical structure

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
task_idNoID of the task to get hierarchy for (provide this OR task_name)
task_nameNoName/content of the task to get hierarchy for (provide this OR task_id)
include_completedNoInclude completed tasks in the hierarchy (default: false)

Implementation Reference

  • Core handler function that implements todoist_task_hierarchy_get: locates the target task (by ID or name), fetches all tasks, traverses to topmost parent, builds recursive TaskNode tree filtering by include_completed, computes completion stats.
    export async function handleGetTaskHierarchy(
      todoistClient: TodoistApi,
      args: GetTaskHierarchyArgs
    ): Promise<TaskHierarchy> {
      try {
        // Find the requested task
        const requestedTask = await findTask(todoistClient, {
          task_id: args.task_id,
          task_name: args.task_name,
        });
    
        // Get all tasks for hierarchy building
        const response = (await todoistClient.getTasks()) as TasksResponse;
        const allTasks = extractArrayFromResponse(response) as TodoistTask[];
    
        // Find the topmost parent by traversing upward
        let topmostParent = requestedTask;
        const visitedIds = new Set<string>(); // Prevent infinite loops
    
        while (topmostParent.parentId && !visitedIds.has(topmostParent.id)) {
          visitedIds.add(topmostParent.id);
          const parent = allTasks.find((t: unknown) => {
            const todoTask = t as TodoistTask;
            return todoTask.id === topmostParent.parentId;
          });
    
          if (parent) {
            topmostParent = parent as TodoistTask;
          } else {
            // Parent not found, stop traversal
            break;
          }
        }
    
        // Build task tree recursively from the topmost parent
        async function buildTaskTree(
          task: TodoistTask,
          depth: number = 0,
          originalTaskId: string = ""
        ): Promise<ExtendedTaskNode> {
          // Find direct children
          const children = allTasks.filter((t: unknown) => {
            const todoTask = t as TodoistTask;
            return (
              todoTask.parentId === task.id &&
              (args.include_completed || !todoTask.isCompleted)
            );
          });
    
          // Recursively build child nodes
          const childNodes = await Promise.all(
            children.map((child) =>
              buildTaskTree(child as TodoistTask, depth + 1, originalTaskId)
            )
          );
    
          // Calculate completion percentage
          const totalSubtasks = childNodes.reduce(
            (sum, node) => sum + node.totalTasks,
            childNodes.length
          );
          const completedSubtasks = childNodes.reduce(
            (sum, node) => sum + node.completedTasks,
            childNodes.filter((n) => n.task.isCompleted).length
          );
    
          const completionPercentage =
            totalSubtasks > 0
              ? Math.round((completedSubtasks / totalSubtasks) * 100)
              : task.isCompleted
                ? 100
                : 0;
    
          return {
            task,
            children: childNodes,
            depth,
            completionPercentage,
            totalTasks: 1 + totalSubtasks,
            completedTasks: (task.isCompleted ? 1 : 0) + completedSubtasks,
            isOriginalTask: task.id === originalTaskId, // Mark the originally requested task
          };
        }
    
        const rootNode = await buildTaskTree(topmostParent, 0, requestedTask.id);
    
        return {
          root: rootNode,
          totalTasks: rootNode.totalTasks,
          completedTasks: rootNode.completedTasks,
          overallCompletion: Math.round(
            (rootNode.completedTasks / rootNode.totalTasks) * 100
          ),
          originalTaskId: requestedTask.id, // Include the originally requested task ID
        };
      } catch (error) {
        throw ErrorHandler.handleAPIError("getTaskHierarchy", error);
      }
    }
  • Tool schema definition including name, description, and inputSchema for validating arguments: task_id or task_name required, optional include_completed.
    export const GET_TASK_HIERARCHY_TOOL: Tool = {
      name: "todoist_task_hierarchy_get",
      description: "Get a task with all its subtasks in a hierarchical structure",
      inputSchema: {
        type: "object",
        properties: {
          task_id: {
            type: "string",
            description:
              "ID of the task to get hierarchy for (provide this OR task_name)",
          },
          task_name: {
            type: "string",
            description:
              "Name/content of the task to get hierarchy for (provide this OR task_id)",
          },
          include_completed: {
            type: "boolean",
            description:
              "Include completed tasks in the hierarchy (default: false)",
            default: false,
          },
        },
      },
    };
  • src/index.ts:342-348 (registration)
    Main server request handler switch case that routes calls to 'todoist_task_hierarchy_get', validates args, invokes the handler, and formats the hierarchy output for response.
    case "todoist_task_hierarchy_get":
      if (!isGetTaskHierarchyArgs(args)) {
        throw new Error("Invalid arguments for todoist_task_hierarchy_get");
      }
      const hierarchy = await handleGetTaskHierarchy(apiClient, args);
      result = formatTaskHierarchy(hierarchy);
      break;
  • Aggregates all tool definitions including SUBTASK_TOOLS (containing todoist_task_hierarchy_get) into ALL_TOOLS array served via ListToolsRequest.
    export const ALL_TOOLS = [
      ...TASK_TOOLS,
      ...PROJECT_TOOLS,
      ...COMMENT_TOOLS,
      ...LABEL_TOOLS,
      ...SUBTASK_TOOLS,
      ...TEST_TOOLS,
    ];
  • Helper function used by the handler to resolve task by ID (direct fetch) or name (cached search across all tasks).
    async function findTask(
      todoistClient: TodoistApi,
      args: { task_id?: string; task_name?: string }
    ): Promise<TodoistTask> {
      if (!args.task_id && !args.task_name) {
        throw new ValidationError("Either task_id or task_name is required");
      }
    
      try {
        let task: TodoistTask | undefined;
    
        if (args.task_id) {
          const response = await todoistClient.getTask(args.task_id);
          task = response as TodoistTask;
        } else if (args.task_name) {
          const cachedTasks = taskCache.get("todoist_tasks");
          let tasks: TodoistTask[];
    
          if (cachedTasks) {
            tasks = cachedTasks;
          } else {
            const response = (await todoistClient.getTasks()) as TasksResponse;
            tasks = extractArrayFromResponse(response);
            taskCache.set("todoist_tasks", tasks);
          }
    
          const searchTerm = args.task_name.toLowerCase();
          task = tasks.find((t) => t.content.toLowerCase().includes(searchTerm));
        }
    
        if (!task) {
          throw new TaskNotFoundError(
            `Task not found: ${args.task_id || args.task_name}`
          );
        }
    
        return task;
      } catch (error) {
        if (error instanceof TaskNotFoundError) {
          throw error;
        }
        throw ErrorHandler.handleAPIError("findTask", error);
      }
    }

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/greirson/mcp-todoist'

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