Skip to main content
Glama

add_time_tracking

Add a time tracking entry to a task by providing start time and either end time or duration in minutes.

Instructions

Add a time tracking entry to a task. Supports started_at/finished_at or duration_minutes.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
dart_idYesTask dart_id (also accepts "id" or "task_id")
started_atYesStart time in ISO8601 format (e.g., 2026-01-25T10:00:00Z)
finished_atNoEnd time in ISO8601 format (optional if duration_minutes provided)
duration_minutesNoDuration in minutes (optional if finished_at provided)
noteNoOptional note about the time entry

Implementation Reference

  • Main handler function handleAddTimeTracking that validates input (ISO8601 dates, duration_minutes), resolves dart_id aliases, and calls DartClient.addTimeTracking to POST time entry data to the API.
    /**
     * add_time_tracking Tool Handler
     *
     * Adds a time tracking entry to a task.
     * Supports started_at/finished_at timestamps or duration_minutes.
     */
    
    import { DartClient } from '../api/dartClient.js';
    import {
      AddTimeTrackingInput,
      AddTimeTrackingOutput,
      DartAPIError,
      ValidationError,
      resolveDartId,
    } from '../types/index.js';
    
    // ISO8601 date pattern
    const ISO8601_PATTERN = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?)?$/;
    
    /**
     * Handle add_time_tracking tool calls
     *
     * @param input - AddTimeTrackingInput with task_id and time entry details
     * @returns AddTimeTrackingOutput with created entry
     */
    export async function handleAddTimeTracking(input: AddTimeTrackingInput): Promise<AddTimeTrackingOutput> {
      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
        );
      }
    
      // Validate input
      if (!input || typeof input !== 'object') {
        throw new ValidationError('Input must be an object', 'input');
      }
    
      // Accept id, task_id, or taskId as aliases for dart_id
      input.dart_id = resolveDartId(input as unknown as Record<string, unknown>);
    
      if (!input.started_at || typeof input.started_at !== 'string') {
        throw new ValidationError('started_at is required and must be an ISO8601 string', 'started_at');
      }
    
      if (!ISO8601_PATTERN.test(input.started_at)) {
        throw new ValidationError(
          'started_at must be a valid ISO8601 date (e.g., 2026-01-25T10:00:00Z)',
          'started_at'
        );
      }
    
      if (input.finished_at !== undefined) {
        if (typeof input.finished_at !== 'string' || !ISO8601_PATTERN.test(input.finished_at)) {
          throw new ValidationError(
            'finished_at must be a valid ISO8601 date if provided',
            'finished_at'
          );
        }
      }
    
      if (input.duration_minutes !== undefined) {
        if (typeof input.duration_minutes !== 'number' || input.duration_minutes < 0) {
          throw new ValidationError(
            'duration_minutes must be a non-negative number if provided',
            'duration_minutes'
          );
        }
      }
    
      const client = new DartClient({ token: DART_TOKEN });
    
      const result = await client.addTimeTracking({
        dart_id: input.dart_id.trim(),
        started_at: input.started_at,
        finished_at: input.finished_at,
        duration_minutes: input.duration_minutes,
        note: input.note,
      });
    
      return {
        entry: {
          entry_id: result.entry_id,
          dart_id: result.dart_id,
          started_at: result.started_at,
          finished_at: result.finished_at,
          duration_minutes: result.duration_minutes,
          note: result.note,
        },
        task_id: input.dart_id.trim(),
        url: `https://app.dartai.com/task/${input.dart_id.trim()}`,
      };
    }
  • src/index.ts:877-906 (registration)
    Tool registration with name 'add_time_tracking', description, and inputSchema defining dart_id, started_at, finished_at, duration_minutes, and note properties.
    {
      name: 'add_time_tracking',
      description: 'Add a time tracking entry to a task. Supports started_at/finished_at or duration_minutes.',
      inputSchema: {
        type: 'object',
        properties: {
          dart_id: {
            type: 'string',
            description: 'Task dart_id (also accepts "id" or "task_id")',
          },
          started_at: {
            type: 'string',
            description: 'Start time in ISO8601 format (e.g., 2026-01-25T10:00:00Z)',
          },
          finished_at: {
            type: 'string',
            description: 'End time in ISO8601 format (optional if duration_minutes provided)',
          },
          duration_minutes: {
            type: 'integer',
            description: 'Duration in minutes (optional if finished_at provided)',
          },
          note: {
            type: 'string',
            description: 'Optional note about the time entry',
          },
        },
        required: ['dart_id', 'started_at'],
      },
    },
  • src/index.ts:1220-1230 (registration)
    Case handler in the main tool dispatcher that calls handleAddTimeTracking and returns the result as JSON text content.
    case 'add_time_tracking': {
      const result = await handleAddTimeTracking((args || {}) as any);
      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify(result, null, 2),
          },
        ],
      };
    }
  • TypeScript interfaces: AddTimeTrackingInput (dart_id, started_at, finished_at?, duration_minutes?, note?), TimeTrackingEntry, and AddTimeTrackingOutput (entry, task_id, url).
    export interface AddTimeTrackingInput {
      dart_id: string;
      started_at: string;
      finished_at?: string;
      duration_minutes?: number;
      note?: string;
    }
    
    export interface TimeTrackingEntry {
      entry_id: string;
      dart_id: string;
      started_at: string;
      finished_at?: string;
      duration_minutes: number;
      note?: string;
    }
    
    export interface AddTimeTrackingOutput {
      entry: TimeTrackingEntry;
      task_id: string;
      url: string;
    }
  • API client method addTimeTracking that computes finishedAt from duration if needed, resolves current user via config, and POSTs to /tasks/{dart_id}/time-tracking endpoint.
    /**
     * Add time tracking entry to a task
     */
    async addTimeTracking(input: {
      dart_id: string;
      started_at: string;
      finished_at?: string;
      duration_minutes?: number;
      note?: string;
    }): Promise<{
      entry_id: string;
      dart_id: string;
      started_at: string;
      finished_at?: string;
      duration_minutes: number;
      note?: string;
    }> {
      if (!input.dart_id || typeof input.dart_id !== 'string' || input.dart_id.trim() === '') {
        throw new DartAPIError('dart_id is required and must be a non-empty string', 400);
      }
      if (!input.started_at || typeof input.started_at !== 'string') {
        throw new DartAPIError('started_at is required and must be an ISO8601 string', 400);
      }
    
      // Compute finishedAt from duration if not provided
      let finishedAt = input.finished_at;
      if (!finishedAt && input.duration_minutes) {
        const start = new Date(input.started_at);
        finishedAt = new Date(start.getTime() + input.duration_minutes * 60000).toISOString();
      }
      if (!finishedAt) {
        throw new DartAPIError('Either finished_at or duration_minutes is required', 400);
      }
    
      // Get current user from config for the required user field
      const config = await this.getConfig();
      const currentUser = config.user || config.assignees?.find(a => a.email);
      const userId = currentUser?.dart_id || currentUser?.email || currentUser?.name;
      if (!userId) {
        throw new DartAPIError('Cannot determine current user for time tracking. Check workspace config.', 400);
      }
    
      const apiInput: Record<string, unknown> = {
        user: userId,
        startedAt: input.started_at,
        finishedAt: finishedAt,
      };
      if (input.note !== undefined) apiInput.note = input.note;
    
      const response = await this.request<{ item: any }>(
        'POST',
        `/tasks/${encodeURIComponent(input.dart_id.trim())}/time-tracking`,
        apiInput
      );
    
      return {
        entry_id: response.item?.id || '',
        dart_id: input.dart_id,
        started_at: response.item?.startedAt || input.started_at,
        finished_at: response.item?.finishedAt,
        duration_minutes: response.item?.durationMinutes || input.duration_minutes || 0,
        note: response.item?.note,
      };
    }
Behavior2/5

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

With no annotations, the description must disclose behavioral traits. It only states that the tool 'supports' two time input methods, but does not reveal side effects (e.g., whether the task is modified), error handling, or return values. Behavior is largely inferred from the name and schema.

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 very concise, using two sentences that front-load the core purpose and key usage pattern. Every sentence is necessary and contributes to understanding.

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

Completeness2/5

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

The description lacks information about return values, error cases, and confirmation of successful creation. Given the tool has no output schema, the description should cover such aspects, but it does not, leaving the agent guessing about post-invocation behavior.

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?

The input schema has 100% description coverage, so baseline is 3. The description summarizes the two input modes, which is already in the schema, so it adds minimal additional meaning.

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 action ('Add') and the resource ('time tracking entry to a task'), making the tool's purpose unambiguous. It also mentions the two supported input patterns (started_at/finished_at or duration_minutes), which further clarifies functionality.

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 provides no guidance on when to use this tool versus other sibling tools (e.g., add_task_comment, update_task). It does not mention prerequisites, alternatives, or context for choosing this tool over others.

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