Skip to main content
Glama

create_time_entry

Log work hours in Harvest by specifying project, task, date, and either duration or start/end times to track billable time.

Instructions

Create a new time entry. Requires project_id, task_id, and spent_date. Must provide either hours OR both started_time and ended_time.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
project_idYesThe project ID to log time against
task_idYesThe task ID to log time against
spent_dateYesThe date the time was spent (YYYY-MM-DD)
started_timeNoStart time in HH:MM format (24-hour)
ended_timeNoEnd time in HH:MM format (24-hour)
hoursNoDecimal hours (e.g., 0.5 = 30min, 1.25 = 1h15m)
notesNoNotes for the time entry
external_referenceNo

Implementation Reference

  • Handler class that executes the create_time_entry tool logic.
    class CreateTimeEntryHandler implements ToolHandler {
      constructor(private readonly config: BaseToolConfig) {}
    
      async execute(args: Record<string, any>): Promise<CallToolResult> {
        try {
          const validatedArgs = validateInput(CreateTimeEntrySchema, args, 'create time entry');
          logger.info('Creating time entry via Harvest API');
          const timeEntry = await this.config.harvestClient.createTimeEntry(validatedArgs);
          
          return {
            content: [{ type: 'text', text: JSON.stringify(timeEntry, null, 2) }],
          };
        } catch (error) {
          return handleMCPToolError(error, 'create_time_entry');
        }
      }
    }
  • Zod schema definition for validating inputs to the create_time_entry tool.
    export const CreateTimeEntrySchema = z.object({
      project_id: z.number().int().positive(),
      task_id: z.number().int().positive(),
      spent_date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'Date must be in YYYY-MM-DD format'),
      started_time: z.string().regex(/^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/).optional(), // HH:MM
      ended_time: z.string().regex(/^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/).optional(), // HH:MM
      hours: z.number().min(0).max(24).optional(), // Either hours or start/end time
      notes: z.string().max(2000).optional(), // Harvest has a notes limit
      external_reference: z.object({
        id: z.string().optional(),
        group_id: z.string().optional(),
        account_id: z.string().optional(),
        permalink: z.string().url().optional(),
      }).optional(),
    }).refine((data) => {
      // Must have either hours or both start/end times
      const hasHours = data.hours !== undefined && data.hours > 0;
      const hasTimes = data.started_time && data.ended_time;
      return hasHours || hasTimes;
    }, {
      message: "Must provide either 'hours' or both 'started_time' and 'ended_time'",
    });
    
    // For updates, we need to work around the refined schema
  • Registration of the create_time_entry tool.
      tool: {
        name: 'create_time_entry',
        description: 'Create a new time entry. Requires project_id, task_id, and spent_date. Must provide either hours OR both started_time and ended_time.',
        inputSchema: {
          type: 'object',
          properties: {
            project_id: { type: 'number', description: 'The project ID to log time against' },
            task_id: { type: 'number', description: 'The task ID to log time against' },
            spent_date: { type: 'string', format: 'date', description: 'The date the time was spent (YYYY-MM-DD)' },
            started_time: { type: 'string', pattern: '^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$', description: 'Start time in HH:MM format (24-hour)' },
            ended_time: { type: 'string', pattern: '^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$', description: 'End time in HH:MM format (24-hour)' },
            hours: { type: 'number', minimum: 0, maximum: 24, description: 'Decimal hours (e.g., 0.5 = 30min, 1.25 = 1h15m)' },
            notes: { type: 'string', maxLength: 2000, description: 'Notes for the time entry' },
            external_reference: {
              type: 'object',
              properties: {
                id: { type: 'string', description: 'External reference ID' },
                group_id: { type: 'string', description: 'External group ID' },
                account_id: { type: 'string', description: 'External account ID' },
                permalink: { type: 'string', format: 'uri', description: 'External permalink URL' },
              },
              additionalProperties: false,
            },
          },
          required: ['project_id', 'task_id', 'spent_date'],
          additionalProperties: false,
        },
      },
      handler: new CreateTimeEntryHandler(config),
    },

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/ianaleck/harvest-mcp-server'

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