Skip to main content
Glama
by cristip73

asana_get_project_hierarchy

Retrieve the complete hierarchical structure of any Asana project including sections, tasks, and subtasks with flexible pagination options for efficient data management.

Instructions

Get the complete hierarchical structure of an Asana project, including its sections, tasks, and subtasks. Supports both manual and automatic pagination.

PAGINATION GUIDE:

  1. Get all data at once: Use auto_paginate=true

  2. Manual pagination: First request with limit=N, then use the returned 'next_offset' tokens in subsequent requests

  3. Tips for large projects: Specify only needed fields, set include_subtasks=false if subtasks aren't needed

EXAMPLES:

  • For all data: {project_id:"123", auto_paginate:true}

  • For first page: {project_id:"123", limit:10}

  • For next page: {project_id:"123", limit:10, offset:"eyJ0a..."}

  • For deep subtasks: {project_id:"123", include_subtasks:true, max_subtask_depth:3} Note: offset must be a token from previous response (section.pagination_info.next_offset)

Input Schema

NameRequiredDescriptionDefault
project_idYesID of the project to get hierarchy for
include_completed_tasksNoInclude completed tasks (default: false)
include_subtasksNoInclude subtasks for each task (default: true)
include_completed_subtasksNoInclude completed subtasks (default: follows include_completed_tasks)
max_subtask_depthNoMaximum depth of subtasks to retrieve (default: 1, meaning only direct subtasks)
opt_fields_tasksNoOptional fields for tasks (e.g. 'name,notes,assignee,due_on,completed')
opt_fields_subtasksNoOptional fields for subtasks (if not specified, uses same as tasks)
opt_fields_sectionsNoOptional fields for sections (e.g. 'name,created_at')
opt_fields_projectNoOptional fields for project (e.g. 'name,created_at,owner')
limitNoMax results per page (1-100). For pagination, set this and don't use auto_paginate
offsetNoPagination token from previous response. MUST be valid token from section.pagination_info.next_offset
auto_paginateNoIf true, automatically gets all pages and combines results (limited by max_pages)
max_pagesNoMaximum pages to fetch when auto_paginate is true (protects against infinite loops)

Input Schema (JSON Schema)

{ "properties": { "auto_paginate": { "default": false, "description": "If true, automatically gets all pages and combines results (limited by max_pages)", "type": "boolean" }, "include_completed_subtasks": { "description": "Include completed subtasks (default: follows include_completed_tasks)", "type": "boolean" }, "include_completed_tasks": { "description": "Include completed tasks (default: false)", "type": "boolean" }, "include_subtasks": { "description": "Include subtasks for each task (default: true)", "type": "boolean" }, "limit": { "description": "Max results per page (1-100). For pagination, set this and don't use auto_paginate", "maximum": 100, "minimum": 1, "type": "number" }, "max_pages": { "default": 10, "description": "Maximum pages to fetch when auto_paginate is true (protects against infinite loops)", "type": "number" }, "max_subtask_depth": { "default": 1, "description": "Maximum depth of subtasks to retrieve (default: 1, meaning only direct subtasks)", "maximum": 10, "minimum": 1, "type": "number" }, "offset": { "description": "Pagination token from previous response. MUST be valid token from section.pagination_info.next_offset", "type": "string" }, "opt_fields_project": { "description": "Optional fields for project (e.g. 'name,created_at,owner')", "type": "string" }, "opt_fields_sections": { "description": "Optional fields for sections (e.g. 'name,created_at')", "type": "string" }, "opt_fields_subtasks": { "description": "Optional fields for subtasks (if not specified, uses same as tasks)", "type": "string" }, "opt_fields_tasks": { "description": "Optional fields for tasks (e.g. 'name,notes,assignee,due_on,completed')", "type": "string" }, "project_id": { "description": "ID of the project to get hierarchy for", "type": "string" } }, "required": [ "project_id" ], "type": "object" }

Implementation Reference

  • MCP tool dispatch handler: extracts project_id and opts, calls AsanaClientWrapper.getProjectHierarchy, returns JSON response.
    case "asana_get_project_hierarchy": { const { project_id, ...opts } = args; const response = await asanaClient.getProjectHierarchy(project_id, opts); return { content: [{ type: "text", text: JSON.stringify(response) }], }; }
  • Input schema definition for the tool, specifying parameters like project_id, pagination (limit, offset, auto_paginate), subtasks options (include_subtasks, max_subtask_depth), opt_fields, etc.
    export const getProjectHierarchyTool: Tool = { name: "asana_get_project_hierarchy", description: "Get the complete hierarchical structure of an Asana project, including its sections, tasks, and subtasks. Supports both manual and automatic pagination.\n\n" + "PAGINATION GUIDE:\n" + "1. Get all data at once: Use auto_paginate=true\n" + "2. Manual pagination: First request with limit=N, then use the returned 'next_offset' tokens in subsequent requests\n" + "3. Tips for large projects: Specify only needed fields, set include_subtasks=false if subtasks aren't needed\n\n" + "EXAMPLES:\n" + "- For all data: {project_id:\"123\", auto_paginate:true}\n" + "- For first page: {project_id:\"123\", limit:10}\n" + "- For next page: {project_id:\"123\", limit:10, offset:\"eyJ0a...\"}\n" + "- For deep subtasks: {project_id:\"123\", include_subtasks:true, max_subtask_depth:3}\n" + "Note: offset must be a token from previous response (section.pagination_info.next_offset)", inputSchema: { type: "object", properties: { project_id: { type: "string", description: "ID of the project to get hierarchy for" }, include_completed_tasks: { type: "boolean", description: "Include completed tasks (default: false)" }, include_subtasks: { type: "boolean", description: "Include subtasks for each task (default: true)" }, include_completed_subtasks: { type: "boolean", description: "Include completed subtasks (default: follows include_completed_tasks)" }, max_subtask_depth: { type: "number", description: "Maximum depth of subtasks to retrieve (default: 1, meaning only direct subtasks)", minimum: 1, maximum: 10, default: 1 }, opt_fields_tasks: { type: "string", description: "Optional fields for tasks (e.g. 'name,notes,assignee,due_on,completed')" }, opt_fields_subtasks: { type: "string", description: "Optional fields for subtasks (if not specified, uses same as tasks)" }, opt_fields_sections: { type: "string", description: "Optional fields for sections (e.g. 'name,created_at')" }, opt_fields_project: { type: "string", description: "Optional fields for project (e.g. 'name,created_at,owner')" }, limit: { type: "number", description: "Max results per page (1-100). For pagination, set this and don't use auto_paginate", minimum: 1, maximum: 100 }, offset: { type: "string", description: "Pagination token from previous response. MUST be valid token from section.pagination_info.next_offset" }, auto_paginate: { type: "boolean", description: "If true, automatically gets all pages and combines results (limited by max_pages)", default: false }, max_pages: { type: "number", description: "Maximum pages to fetch when auto_paginate is true (protects against infinite loops)", default: 10 } }, required: ["project_id"] } };
  • Tool registration: getProjectHierarchyTool is imported from task-tools and included in the exported tools array used for MCP tool registration.
    export const tools: Tool[] = [ listWorkspacesTool, searchProjectsTool, getProjectTool, getProjectTaskCountsTool, getProjectSectionsTool, createSectionForProjectTool, createProjectForWorkspaceTool, updateProjectTool, reorderSectionsTool, getProjectStatusTool, getProjectStatusesForProjectTool, createProjectStatusTool, deleteProjectStatusTool, searchTasksTool, getTaskTool, createTaskTool, updateTaskTool, createSubtaskTool, getMultipleTasksByGidTool, addTaskToSectionTool, getTasksForSectionTool, getProjectHierarchyTool, getSubtasksForTaskTool, getTasksForProjectTool, getTasksForTagTool, getTagsForWorkspaceTool, addTagsToTaskTool, addTaskDependenciesTool, addTaskDependentsTool, setParentForTaskTool, addFollowersToTaskTool, getStoriesForTaskTool, createTaskStoryTool, getTeamsForUserTool, getTeamsForWorkspaceTool, addMembersForProjectTool, addFollowersForProjectTool, getUsersForWorkspaceTool, getAttachmentsForObjectTool, uploadAttachmentForObjectTool, downloadAttachmentTool ];
  • Core implementation: Retrieves full project hierarchy (project -> sections -> tasks -> subtasks) with advanced pagination (manual/auto), filtering (completed tasks/subtasks), optional fields, recursive subtasks up to specified depth, and statistics.
    // Metodă pentru obținerea structurii ierarhice complete a unui proiect async getProjectHierarchy(projectId: string, opts: any = {}) { /** * Get the complete hierarchical structure of an Asana project * Pagination features: * 1. Auto pagination: Set auto_paginate=true to get all results automatically * 2. Manual pagination: * - First request: Set limit=N (without offset) * - Subsequent requests: Use limit=N with offset token from previous response * 3. Pagination metadata is provided at multiple levels: * - Global: result.pagination_info * - Section level: section.pagination_info (contains next_offset token) * - Task level: task.subtasks_pagination_info (for subtasks pagination) * * @param projectId - The project GID * @param opts - Options including pagination params (limit, offset, auto_paginate) */ try { // Extrage opțiunile de paginare const { auto_paginate = false, max_pages = 10, limit, offset, include_subtasks = true, include_completed_subtasks, max_subtask_depth = 1, ...otherOpts } = opts; // Initialize stats object for tracking counts const stats = { total_sections: 0, total_tasks: 0, total_subtasks: 0 }; // Pasul 1: Obține informații despre proiect const projectFields = "name,gid" + (opts.opt_fields_project ? `,${opts.opt_fields_project}` : ""); const project = await this.getProject(projectId, { opt_fields: projectFields }); // Pasul 2: Obține secțiunile proiectului const sectionFields = "name,gid" + (opts.opt_fields_sections ? `,${opts.opt_fields_sections}` : ""); const sections = await this.getProjectSections(projectId, { opt_fields: sectionFields }); // Update stats with section count stats.total_sections = sections ? sections.length : 0; // Verifică dacă avem secțiuni if (!sections || sections.length === 0) { return { project: project, sections: [], stats }; } // Calculăm limita efectivă pentru task-uri, dacă este specificată // Dacă nu este specificată, folosim o valoare implicită care va permite API-ului să decidă const effectiveLimit = limit ? Math.min(Math.max(1, Number(limit)), 100) : undefined; // Pasul 3: Pentru fiecare secțiune, obține task-urile const sectionsWithTasks = await Promise.all(sections.map(async (section: any) => { const taskFields = "name,gid,completed,resource_subtype,num_subtasks" + (opts.opt_fields_tasks ? `,${opts.opt_fields_tasks}` : ""); // Pregătim parametrii pentru task-uri const taskOpts: any = { opt_fields: taskFields }; // Adăugăm limita doar dacă este specificată if (effectiveLimit) { taskOpts.limit = effectiveLimit; } // Adăugăm offset doar dacă este specificat și pare valid (începe cu 'eyJ') if (offset && typeof offset === 'string' && offset.startsWith('eyJ')) { taskOpts.offset = offset; } // Include sau exclude task-urile completate if (opts.include_completed_tasks === false) { taskOpts.completed_since = "now"; } // Obținem task-urile din secțiune cu sau fără paginare let tasks; if (auto_paginate) { // Folosim handlePaginatedResults pentru a obține toate task-urile cu paginare automată tasks = await this.handlePaginatedResults( // Initial fetch function () => this.tasks.getTasksForSection(section.gid, taskOpts), // Next page fetch function (nextOffset) => this.tasks.getTasksForSection(section.gid, { ...taskOpts, offset: nextOffset }), // Pagination options { auto_paginate, max_pages } ); } else { // Obținem doar o pagină de task-uri try { const response = await this.tasks.getTasksForSection(section.gid, taskOpts); tasks = response.data || []; // Includem informații despre paginare în rezultat if (response.next_page) { section.pagination_info = { has_more: true, next_offset: response.next_page.offset }; } else { section.pagination_info = { has_more: false }; } } catch (error) { console.error(`Error fetching tasks for section ${section.gid}:`, error); tasks = []; section.error = "Could not fetch tasks for this section"; } } // Update total tasks count stats.total_tasks += tasks ? tasks.length : 0; // Pasul 4: Pentru fiecare task, obține subtask-urile dacă acestea există și dacă utilizatorul dorește const tasksWithSubtasks = await Promise.all(tasks.map(async (task: any) => { // Verifică dacă avem nevoie de subtask-uri și dacă task-ul are subtask-uri if (include_subtasks && task.num_subtasks && task.num_subtasks > 0) { try { // Pregătim câmpurile pentru subtask-uri const subtaskFields = "name,gid,completed,resource_subtype,num_subtasks" + (opts.opt_fields_subtasks ? `,${opts.opt_fields_subtasks}` : opts.opt_fields_tasks ? `,${opts.opt_fields_tasks}` : ""); // Pregătim parametrii pentru subtask-uri const subtaskOpts: any = { opt_fields: subtaskFields }; // Adăugăm limita doar dacă este specificată if (effectiveLimit) { subtaskOpts.limit = effectiveLimit; } // Aplicăm filtrarea pentru task-uri completate (dacă este specificată) if (include_completed_subtasks === false) { subtaskOpts.completed_since = "now"; } // Folosim metoda corectă pentru a obține subtask-urile let subtasks; if (auto_paginate) { // Cu paginare automată subtasks = await this.handlePaginatedResults( // Initial fetch function () => this.tasks.getSubtasksForTask(task.gid, subtaskOpts), // Next page fetch function (nextOffset) => this.tasks.getSubtasksForTask(task.gid, { ...subtaskOpts, offset: nextOffset }), // Pagination options { auto_paginate, max_pages } ); } else { // Fără paginare automată, doar o singură pagină try { const response = await this.tasks.getSubtasksForTask(task.gid, subtaskOpts); subtasks = response.data || []; // Includem informații despre paginare în rezultat if (response.next_page) { task.subtasks_pagination_info = { has_more: true, next_offset: response.next_page.offset }; } else { task.subtasks_pagination_info = { has_more: false }; } } catch (error) { console.error(`Error fetching subtasks for task ${task.gid}:`, error); subtasks = []; task.subtasks_error = "Could not fetch subtasks for this task"; } } // Update subtasks count stats.total_subtasks += subtasks ? subtasks.length : 0; // If max_subtask_depth > 1, recursively fetch deeper subtasks if (max_subtask_depth > 1 && subtasks && subtasks.length > 0) { await this.fetchSubtasksRecursively( subtasks, max_subtask_depth, 1, // Current depth subtaskOpts, auto_paginate, max_pages, stats ); } return { ...task, subtasks }; } catch (error) { console.error(`Error fetching subtasks for task ${task.gid}:`, error); return { ...task, subtasks: [], subtasks_error: "Error fetching subtasks" }; } } return { ...task, subtasks: [] }; })); return { ...section, tasks: tasksWithSubtasks }; })); // Adăugăm informații despre paginare la nivelul rezultatului const result = { project: project, sections: sectionsWithTasks, stats, // Include the statistics in the result pagination_info: { auto_paginate_used: auto_paginate, effective_limit: effectiveLimit, offset_provided: offset ? true : false } }; // Returnează structura ierarhică completă return result; } catch (error: any) { // Oferim un mesaj de eroare mai util pentru probleme comune if (error.message && error.message.includes('offset')) { console.error("Error in getProjectHierarchy with pagination:", error); throw new Error(`Invalid pagination parameters: ${error.message}. Asana requires offset tokens to be obtained from previous responses.`); } else { console.error("Error in getProjectHierarchy:", error); throw error; } } }
  • Private recursive helper for fetching multi-level subtasks with pagination support.
    // Helper method to recursively fetch subtasks to a specified depth private async fetchSubtasksRecursively( tasks: any[], maxDepth: number, currentDepth: number, opts: any, auto_paginate: boolean, max_pages: number, stats: { total_subtasks: number } ) { // If we've reached the maximum depth, stop recursion if (currentDepth >= maxDepth) { return; } // For each task at the current depth for (const task of tasks) { // Skip if task has no subtasks if (!task.num_subtasks || task.num_subtasks <= 0) { continue; } try { // Fetch subtasks for this task let subtasks; if (auto_paginate) { // With auto pagination subtasks = await this.handlePaginatedResults( // Initial fetch function () => this.tasks.getSubtasksForTask(task.gid, opts), // Next page fetch function (nextOffset) => this.tasks.getSubtasksForTask(task.gid, { ...opts, offset: nextOffset }), // Pagination options { auto_paginate, max_pages } ); } else { // Without auto pagination, just get one page const response = await this.tasks.getSubtasksForTask(task.gid, opts); subtasks = response.data || []; // Add pagination info to the task if (response.next_page) { task.subtasks_pagination_info = { has_more: true, next_offset: response.next_page.offset }; } else { task.subtasks_pagination_info = { has_more: false }; } } // Update subtasks count stats.total_subtasks += subtasks ? subtasks.length : 0; // Add subtasks to the current task task.subtasks = subtasks; // Recursively fetch the next level of subtasks if (subtasks && subtasks.length > 0) { await this.fetchSubtasksRecursively( subtasks, maxDepth, currentDepth + 1, opts, auto_paginate, max_pages, stats ); } } catch (error) { console.error(`Error recursively fetching subtasks for task ${task.gid}:`, error); task.subtasks = []; task.subtasks_error = "Error fetching subtasks recursively"; } } }

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/cristip73/mcp-server-asana'

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