Skip to main content
Glama

list_tasks

Retrieve tasks filtered by assignee, status, dartboard, priority, tags, date range, and parent status. Supports pagination and multiple detail levels.

Instructions

Query tasks with filters (assignee, status, dartboard, priority, tags, dates, has_parent), pagination, and detail levels. Parent filter uses client-side filtering.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
assigneeNoFilter by assignee (dart_id, name, or email)
statusNoFilter by status (dart_id or name)
dartboardNoFilter by dartboard (dart_id or name)
priorityNoFilter by priority (1-5)
tagsNoFilter by tags (dart_ids or names)
due_beforeNoFilter tasks due before date (ISO8601)
due_afterNoFilter tasks due after date (ISO8601)
has_parentNoFilter tasks with parent (true) or without parent (false). Client-side filter.
limitNoMax tasks to return (default: 50, max: 500)
offsetNoPagination offset (default: 0)
detail_levelNominimal=id+title+parent+blockers, standard=+description+status+assignee+priority, full=all fields including relationships

Implementation Reference

  • Main handler function for list_tasks tool. Validates input, resolves filters, calls API, applies client-side filtering and detail_level pruning, then returns paginated results with metadata.
    export async function handleListTasks(input: ListTasksInput): Promise<ListTasksOutput> {
      // Defensive input handling
      const safeInput = input || {};
    
      const DART_TOKEN = process.env.DART_TOKEN;
    
      if (!DART_TOKEN) {
        throw new DartAPIError(
          'DART_TOKEN environment variable is required. Get your token from: https://app.dartai.com/?settings=account',
          401
        );
      }
    
      // Initialize Dart API client
      const client = new DartClient({ token: DART_TOKEN });
    
      // Validate and normalize pagination parameters
      const limit = validateLimit(safeInput.limit);
      const offset = validateOffset(safeInput.offset);
    
      // Validate detail_level
      const detailLevel = validateDetailLevel(safeInput.detail_level);
    
      // Validate and resolve filters
      const resolvedFilters = await resolveFilters(safeInput, client);
    
      // Build API request with resolved filters
      const apiRequest: ListTasksInput = {
        ...resolvedFilters,
        limit,
        offset,
        detail_level: detailLevel,
      };
    
      // Call DartClient.listTasks()
      let apiResponse: { tasks: DartTask[]; total: number };
      try {
        apiResponse = await client.listTasks(apiRequest);
      } catch (error) {
        // Enhance error messages for authentication issues
        if (error instanceof DartAPIError) {
          if (error.statusCode === 401) {
            throw new DartAPIError(
              'Authentication failed: Invalid DART_TOKEN. Get a valid token from: https://app.dartai.com/?settings=account',
              401,
              error.response
            );
          } else if (error.statusCode === 403) {
            throw new DartAPIError(
              'Access forbidden: Your DART_TOKEN does not have permission to list tasks.',
              403,
              error.response
            );
          }
        }
        // Re-throw other errors
        throw error;
      }
    
      // Extract tasks and total count
      let tasks = apiResponse.tasks || [];
      const totalCount = apiResponse.total || 0;
    
      // Apply client-side filtering fallback if API doesn't support certain filters
      // (Some filters might not be supported by the API, so we filter client-side)
      tasks = applyClientSideFilters(tasks, safeInput);
    
      // Apply detail_level pruning to reduce token usage
      tasks = applyDetailLevel(tasks, detailLevel);
    
      // Calculate pagination metadata
      const returnedCount = tasks.length;
      // Use limit (not returnedCount) to calculate hasMore, as client-side filtering could reduce returnedCount
      const hasMore = offset + limit < totalCount;
      const nextOffset = hasMore ? offset + limit : null;
    
      // Build filters_applied object
      const filtersApplied = buildFiltersApplied(safeInput, resolvedFilters);
    
      return {
        tasks,
        total_count: totalCount,
        returned_count: returnedCount,
        has_more: hasMore,
        next_offset: nextOffset,
        filters_applied: filtersApplied,
      };
    }
  • Helper functions used by the handler: validateLimit, validateOffset, validateDetailLevel, resolveFilters (name-to-ID resolution for assignee/status/dartboard/tags), validateRelationshipFilters, applyClientSideFilters (has_parent filtering), applyDetailLevel (minimal/standard/full pruning), buildFiltersApplied.
    /**
     * Validate and normalize limit parameter
     */
    function validateLimit(limit?: number): number {
      if (limit === undefined || limit === null) {
        return 50; // Default limit
      }
    
      if (typeof limit !== 'number' || !Number.isInteger(limit)) {
        throw new ValidationError('limit must be an integer');
      }
    
      if (limit < 1) {
        throw new ValidationError('limit must be at least 1');
      }
    
      if (limit > 500) {
        throw new ValidationError('limit must not exceed 500 (max allowed)');
      }
    
      return limit;
    }
    
    /**
     * Validate and normalize offset parameter
     */
    function validateOffset(offset?: number): number {
      if (offset === undefined || offset === null) {
        return 0; // Default offset
      }
    
      if (typeof offset !== 'number' || !Number.isInteger(offset)) {
        throw new ValidationError('offset must be an integer');
      }
    
      if (offset < 0) {
        throw new ValidationError('offset must be non-negative');
      }
    
      return offset;
    }
    
    /**
     * Validate detail_level parameter
     */
    function validateDetailLevel(detailLevel?: string): 'minimal' | 'standard' | 'full' {
      if (!detailLevel) {
        return 'standard'; // Default detail level
      }
    
      if (!['minimal', 'standard', 'full'].includes(detailLevel)) {
        throw new ValidationError(
          `detail_level must be one of: minimal, standard, full. Got: "${detailLevel}"`
        );
      }
    
      return detailLevel as 'minimal' | 'standard' | 'full';
    }
    
    /**
     * Resolve filter references (names to IDs) against workspace config
     */
    async function resolveFilters(
      input: ListTasksInput,
      client: DartClient
    ): Promise<ListTasksInput> {
      // If no filters that need resolution, return as-is
      const needsResolution = input.assignee || input.status || input.dartboard || input.tags;
    
      if (!needsResolution) {
        return { ...input };
      }
    
      // Get config to resolve names to IDs
      let config = configCache.get();
      if (!config) {
        config = await client.getConfig();
        configCache.set({
          ...config,
          cached_at: new Date().toISOString(),
          cache_ttl_seconds: configCache.getTTL(),
        });
      }
    
      const resolved: ListTasksInput = { ...input };
    
      // Resolve assignee (dart_id, name, or email)
      if (input.assignee && typeof input.assignee === 'string') {
        const assigneeInput = input.assignee; // Type narrowing
    
        // Handle empty assignees array edge case
        if (!config.assignees || config.assignees.length === 0) {
          throw new ValidationError(
            'No assignees configured in workspace. Cannot filter by assignee.',
            'assignee',
            ['No assignees available']
          );
        }
    
        const assignee = config.assignees.find(
          (a) =>
            a.name?.toLowerCase() === assigneeInput.toLowerCase() ||
            a.email?.toLowerCase() === assigneeInput.toLowerCase()
        );
    
        if (!assignee) {
          throw new ValidationError(
            `Assignee not found: "${assigneeInput}". Use get_config to see available assignees.`,
            'assignee',
            config.assignees.map((a) => a.email ? `${a.name} <${a.email}>` : a.name)
          );
        }
    
        // Use email if available, otherwise name
        resolved.assignee = assignee.email || assignee.name;
      }
    
      // Resolve status (dart_id or name)
      if (input.status && typeof input.status === 'string') {
        const statusInput = input.status; // Type narrowing
    
        // Handle empty statuses array edge case
        if (!config.statuses || config.statuses.length === 0) {
          throw new ValidationError(
            'No statuses configured in workspace. Cannot filter by status.',
            'status',
            ['No statuses available']
          );
        }
    
        const status = findStatus(config.statuses, statusInput);
    
        if (!status) {
          throw new ValidationError(
            `Status not found: "${statusInput}". Use get_config to see available statuses.`,
            'status',
            getStatusNames(config.statuses)
          );
        }
    
        // Return dart_id for API filtering
        resolved.status = typeof status === 'string' ? status : status.dart_id;
      }
    
      // Resolve dartboard (dart_id or name)
      if (input.dartboard && typeof input.dartboard === 'string') {
        const dartboardInput = input.dartboard; // Type narrowing
    
        // Handle empty dartboards array edge case
        if (!config.dartboards || config.dartboards.length === 0) {
          throw new ValidationError(
            'No dartboards configured in workspace. Cannot filter by dartboard.',
            'dartboard',
            ['No dartboards available']
          );
        }
    
        const dartboard = findDartboard(config.dartboards, dartboardInput);
    
        if (!dartboard) {
          throw new ValidationError(
            `Dartboard not found: "${dartboardInput}". Use get_config to see available dartboards.`,
            'dartboard',
            getDartboardNames(config.dartboards).slice(0, 10)
          );
        }
    
        // Return dart_id for API filtering
        resolved.dartboard = typeof dartboard === 'string' ? dartboard : dartboard.dart_id;
      }
    
      // Resolve tags (dart_ids or names)
      if (input.tags && Array.isArray(input.tags) && input.tags.length > 0) {
        // Handle empty tags array edge case
        if (!config.tags || config.tags.length === 0) {
          throw new ValidationError(
            'No tags configured in workspace. Cannot filter by tags.',
            'tags',
            ['No tags available']
          );
        }
    
        const resolvedTags: string[] = [];
    
        for (const tagInput of input.tags) {
          // Validate tagInput is a string
          if (typeof tagInput !== 'string') {
            throw new ValidationError(
              `Invalid tag value: tags must be strings. Got: ${typeof tagInput}`,
              'tags'
            );
          }
    
          const tag = findTag(config.tags, tagInput);
    
          if (!tag) {
            throw new ValidationError(
              `Tag not found: "${tagInput}". Use get_config to see available tags.`,
              'tags',
              getTagNames(config.tags).slice(0, 20)
            );
          }
    
          // Return dart_id for API filtering
          resolvedTags.push(typeof tag === 'string' ? tag : tag.dart_id);
        }
    
        resolved.tags = resolvedTags;
      }
    
      // Validate date formats
      if (input.due_before && !isValidISO8601Date(input.due_before)) {
        throw new ValidationError(
          `due_before must be in ISO8601 format (e.g., "2024-12-31T23:59:59Z"). Got: "${input.due_before}"`,
          'due_before'
        );
      }
    
      if (input.due_after && !isValidISO8601Date(input.due_after)) {
        throw new ValidationError(
          `due_after must be in ISO8601 format (e.g., "2024-01-01T00:00:00Z"). Got: "${input.due_after}"`,
          'due_after'
        );
      }
    
      // Validate priority range
      if (input.priority !== undefined) {
        if (typeof input.priority !== 'number' || !Number.isInteger(input.priority)) {
          throw new ValidationError('priority must be an integer', 'priority');
        }
    
        if (input.priority < 1 || input.priority > 5) {
          throw new ValidationError(
            'priority must be between 1 and 5 (1=lowest, 5=highest)',
            'priority'
          );
        }
      }
    
      // Validate relationship filters
      validateRelationshipFilters(input);
    
      return resolved;
    }
    
    /**
     * Validate relationship filter parameters
     *
     * Note: Only has_parent is supported because the list API returns parent_task.
     * Other relationship filters (has_subtasks, has_blockers, is_blocking) are not
     * available because the list API doesn't return taskRelationships data.
     */
    function validateRelationshipFilters(input: ListTasksInput): void {
      // Validate has_parent boolean filter
      if (input.has_parent !== undefined && typeof input.has_parent !== 'boolean') {
        throw new ValidationError('has_parent must be a boolean', 'has_parent');
      }
    }
    
    /**
     * Validate ISO8601 date format
     */
    function isValidISO8601Date(dateString: string): boolean {
      const iso8601Regex = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d{3})?(Z|[+-]\d{2}:\d{2})?)?$/;
      if (!iso8601Regex.test(dateString)) {
        return false;
      }
    
      const date = new Date(dateString);
      return !isNaN(date.getTime());
    }
    
    /**
     * Check if relationship filters are being used (require client-side filtering)
     *
     * Note: Only has_parent is supported. Other relationship filters were removed
     * because the list API doesn't return taskRelationships data.
     */
    function hasRelationshipFilters(input: ListTasksInput): boolean {
      return input.has_parent !== undefined;
    }
    
    /**
     * Apply client-side filtering for relationship filters.
     *
     * Note: Only has_parent is supported because the list API returns parent_task.
     * Other relationship filters (has_subtasks, has_blockers, is_blocking) were removed
     * because the list API doesn't return taskRelationships data.
     */
    function applyClientSideFilters(tasks: DartTask[], input: ListTasksInput): DartTask[] {
      // If no relationship filters, return as-is (API handles other filters)
      if (!hasRelationshipFilters(input)) {
        return tasks;
      }
    
      return tasks.filter((task) => {
        // has_parent filter: tasks with or without a parent task
        if (input.has_parent !== undefined) {
          const hasParent = task.parent_task !== undefined && task.parent_task !== null && task.parent_task !== '';
          if (input.has_parent !== hasParent) {
            return false;
          }
        }
    
        return true;
      });
    }
    
    /**
     * Apply detail_level pruning to reduce token usage
     */
    function applyDetailLevel(
      tasks: DartTask[],
      detailLevel: 'minimal' | 'standard' | 'full'
    ): DartTask[] {
      if (detailLevel === 'full') {
        return tasks; // Return all fields
      }
    
      return tasks.map((task) => {
        if (detailLevel === 'minimal') {
          // minimal: id + title + parent/blockers
          return {
            dart_id: task.dart_id,
            title: task.title,
            parent_task: task.parent_task,
            blocker_ids: task.blocker_ids,
            created_at: task.created_at,
            updated_at: task.updated_at,
          } as DartTask;
        }
    
        // standard: id + title + description + status + assignee + priority + parent/blockers
        return {
          dart_id: task.dart_id,
          title: task.title,
          description: task.description,
          status: task.status,
          status_id: task.status_id,
          priority: task.priority,
          assignees: task.assignees,
          dartboard: task.dartboard,
          dartboard_id: task.dartboard_id,
          parent_task: task.parent_task,
          blocker_ids: task.blocker_ids,
          created_at: task.created_at,
          updated_at: task.updated_at,
        } as DartTask;
      });
    }
    
    /**
     * Build filters_applied object for response
     */
    function buildFiltersApplied(
      input: ListTasksInput,
      resolved: ListTasksInput
    ): Record<string, unknown> {
      const filtersApplied: Record<string, unknown> = {};
    
      // Echo back all applied filters
      if (resolved.assignee) filtersApplied.assignee = resolved.assignee;
      if (resolved.status) filtersApplied.status = resolved.status;
      if (resolved.dartboard) filtersApplied.dartboard = resolved.dartboard;
      if (resolved.priority !== undefined) filtersApplied.priority = resolved.priority;
      if (resolved.tags && resolved.tags.length > 0) filtersApplied.tags = resolved.tags;
      if (resolved.due_before) filtersApplied.due_before = resolved.due_before;
      if (resolved.due_after) filtersApplied.due_after = resolved.due_after;
    
      // Echo back relationship filters (client-side filters)
      // Note: Only has_parent is supported (list API returns parent_task)
      if (input.has_parent !== undefined) filtersApplied.has_parent = input.has_parent;
    
      // Include pagination info (using actual validated values, not defaults from input)
      filtersApplied.limit = input.limit !== undefined ? input.limit : 50;
      filtersApplied.offset = input.offset !== undefined ? input.offset : 0;
      filtersApplied.detail_level = input.detail_level || 'standard';
    
      // Flag indicating client-side filtering was used
      if (hasRelationshipFilters(input)) {
        filtersApplied.client_side_filtered = true;
      }
    
      return filtersApplied;
    }
  • Type definitions: ListTasksInput (filters, pagination, detail_level) and ListTasksOutput (tasks, pagination metadata, filters_applied).
    export interface ListTasksInput {
      assignee?: string;
      status?: string;
      dartboard?: string;
      priority?: number; // 1-5 (1=lowest, 5=highest)
      tags?: string[];
      due_before?: string;
      due_after?: string;
      limit?: number;
      offset?: number;
      detail_level?: 'minimal' | 'standard' | 'full';
    
      // Relationship filters (client-side filtering)
      /**
       * Filter tasks that have a parent task (true) or no parent task (false).
       * Filters based on parent_task field being set or undefined.
       * Note: Other relationship filters (has_subtasks, has_blockers, is_blocking)
       * are not available because the list API doesn't return taskRelationships data.
       */
      has_parent?: boolean;
    }
    
    export interface ListTasksOutput {
      tasks: DartTask[];
      total_count: number;
      returned_count: number;
      has_more: boolean;
      next_offset: number | null;
      filters_applied: Record<string, unknown>;
    }
  • src/index.ts:286-340 (registration)
    Tool registration with name 'list_tasks', description, and inputSchema with all filter/pagination/detail_level parameters.
    {
      name: 'list_tasks',
      description: 'Query tasks with filters (assignee, status, dartboard, priority, tags, dates, has_parent), pagination, and detail levels. Parent filter uses client-side filtering.',
      inputSchema: {
        type: 'object',
        properties: {
          assignee: {
            type: 'string',
            description: 'Filter by assignee (dart_id, name, or email)',
          },
          status: {
            type: 'string',
            description: 'Filter by status (dart_id or name)',
          },
          dartboard: {
            type: 'string',
            description: 'Filter by dartboard (dart_id or name)',
          },
          priority: {
            type: 'integer',
            description: 'Filter by priority (1-5)',
          },
          tags: {
            type: 'array',
            items: { type: 'string' },
            description: 'Filter by tags (dart_ids or names)',
          },
          due_before: {
            type: 'string',
            description: 'Filter tasks due before date (ISO8601)',
          },
          due_after: {
            type: 'string',
            description: 'Filter tasks due after date (ISO8601)',
          },
          // Relationship filters (client-side)
          has_parent: {
            type: 'boolean',
            description: 'Filter tasks with parent (true) or without parent (false). Client-side filter.',
          },
          // Pagination
          limit: {
            type: 'integer',
            description: 'Max tasks to return (default: 50, max: 500)',
          },
          offset: {
            type: 'integer',
            description: 'Pagination offset (default: 0)',
          },
          detail_level: {
            type: 'string',
            enum: ['minimal', 'standard', 'full'],
            description: 'minimal=id+title+parent+blockers, standard=+description+status+assignee+priority, full=all fields including relationships',
          },
        },
  • src/index.ts:1004-1014 (registration)
    Tool dispatch case in main handler - calls handleListTasks with args and returns JSON-stringified result.
    case 'list_tasks': {
      const result = await handleListTasks((args || {}) as any);
      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify(result, null, 2),
          },
        ],
      };
    }
Behavior3/5

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

No annotations are provided, so description carries full burden. It discloses that the parent filter uses client-side filtering, which implies performance note. However, it does not state read-only nature, authentication requirements, rate limits, or other behavioral traits.

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

Conciseness4/5

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

The description is a single sentence that conveys key functionality concisely. It front-loads the action and lists filters. While compact, it lacks structural breakdown for readability.

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?

Given 11 parameters and no output schema, the description covers the primary purpose and a key behavioral note (client-side filter). However, it omits context on return format, error handling, and pagination behavior beyond what schema provides. Adequate but with gaps.

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

Parameters3/5

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

Schema coverage is 100% with detailed descriptions for each parameter. The description adds no new information beyond listing filter types and noting client-side filtering. Baseline 3 applies as schema does the heavy lifting.

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

Purpose4/5

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

The description clearly states 'Query tasks with filters...', specifying the verb and resource. It lists multiple filter types (assignee, status, dartboard, priority, etc.), making the purpose clear. However, it does not differentiate from sibling tool 'search_tasks', which may also query tasks with filters.

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

Usage Guidelines2/5

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

The description mentions one behavioral note (client-side filtering for has_parent) but provides no explicit guidance on when to use this tool versus siblings like search_tasks. No exclusions or alternative recommendations are given.

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/standardbeagle/dart-query'

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