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
| Name | Required | Description | Default |
|---|---|---|---|
| task_id | No | ID of the task to get hierarchy for (provide this OR task_name) | |
| task_name | No | Name/content of the task to get hierarchy for (provide this OR task_id) | |
| include_completed | No | Include completed tasks in the hierarchy (default: false) |
Implementation Reference
- src/handlers/subtask-handlers.ts:254-352 (handler)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); } }
- src/tools/subtask-tools.ts:166-190 (schema)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;
- src/tools/index.ts:62-69 (registration)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); } }