Skip to main content
Glama
timeentry-update.ts6 kB
/** * timeentry_update Tool * * Update an existing time entry in FreshBooks. */ import { z } from 'zod'; import { TimeEntryUpdateInputSchema, TimeEntrySingleOutputSchema } from './schemas.js'; import { FreshBooksClientWrapper } from '../../client/index.js'; import { ErrorHandler } from '../../errors/error-handler.js'; import { ToolContext } from '../../errors/types.js'; import { logger } from '../../utils/logger.js'; /** * Tool definition for timeentry_update */ export const timeentryUpdateTool = { name: 'timeentry_update', description: `Update an existing time entry in FreshBooks. WHEN TO USE: - User wants to modify a time entry - User says "update time entry X", "change the duration", "edit my time log" - User needs to correct duration, note, or project association - User wants to stop a running timer (set active=false) - User asks "update entry 123 to 3 hours", "change project on entry 456" REQUIRED: - accountId: FreshBooks account ID (get from auth_status if not specified) - timeEntryId: ID of the time entry to update OPTIONAL (provide only fields to change): - duration: New duration in seconds - note: New or updated description - projectId: Change project association (null to remove) - clientId: Change client association (null to remove) - serviceId: Change service association (null to remove) - taskId: Change task association (null to remove) - billable: Change billable status - active: Stop/start timer (false to stop, true to restart) - isLogged: Change logged status - startedAt: Change start time - internal: Change internal work flag - retainerId: Change retainer association COMMON USE CASES: 1. Stop a running timer: - Set active=false (duration auto-calculated from startedAt) 2. Change duration: - Set new duration in seconds 3. Update note: - Set new note text 4. Change project: - Set new projectId 5. Make non-billable: - Set billable=false RETURNS: Updated time entry with all fields (including unchanged ones) EXAMPLES: User says: "Update time entry 123 to 3 hours" → timeEntryId: 123 → duration: 10800 User says: "Stop timer 456" → timeEntryId: 456 → active: false User says: "Change the note on entry 789 to 'code review'" → timeEntryId: 789 → note: "code review" User says: "Make entry 321 non-billable" → timeEntryId: 321 → billable: false`, inputSchema: TimeEntryUpdateInputSchema, outputSchema: TimeEntrySingleOutputSchema, /** * Execute the tool */ async execute( input: z.infer<typeof TimeEntryUpdateInputSchema>, client: FreshBooksClientWrapper ): Promise<z.infer<typeof TimeEntrySingleOutputSchema>> { const handler = ErrorHandler.wrapHandler( 'timeentry_update', async ( input: z.infer<typeof TimeEntryUpdateInputSchema>, _context: ToolContext ) => { logger.debug('Updating time entry', { accountId: input.accountId, timeEntryId: input.timeEntryId, fields: Object.keys(input).filter( (k) => k !== 'accountId' && k !== 'timeEntryId' ), }); const result = await client.executeWithRetry( 'timeentry_update', async (fbClient) => { // Build update data object (only include provided fields) const updateData: any = {}; if (input.duration !== undefined) { updateData.duration = input.duration; } if (input.isLogged !== undefined) { updateData.isLogged = input.isLogged; } if (input.startedAt !== undefined) { updateData.startedAt = new Date(input.startedAt); } if (input.note !== undefined) { updateData.note = input.note; } if (input.projectId !== undefined) { updateData.projectId = input.projectId; } if (input.clientId !== undefined) { updateData.clientId = input.clientId; } if (input.serviceId !== undefined) { updateData.serviceId = input.serviceId; } if (input.taskId !== undefined) { updateData.taskId = input.taskId; } if (input.billable !== undefined) { updateData.billable = input.billable; } if (input.active !== undefined) { updateData.active = input.active; } if (input.internal !== undefined) { updateData.internal = input.internal; } if (input.retainerId !== undefined) { updateData.retainerId = input.retainerId; } // Validate that at least one field is being updated if (Object.keys(updateData).length === 0) { throw ErrorHandler.createValidationError( 'No fields provided to update. Please specify at least one field to change.' ); } const response = await fbClient.timeEntries.update( updateData, parseInt(input.accountId), input.timeEntryId ); if (!response.ok) { throw response.error; } if (!response.data) { throw ErrorHandler.createNotFoundError( 'TimeEntry', input.timeEntryId, { accountId: input.accountId, } ); } return response.data; } ); logger.info('Time entry updated successfully', { timeEntryId: input.timeEntryId, updatedFields: Object.keys(input).filter( (k) => k !== 'accountId' && k !== 'timeEntryId' && input[k as keyof typeof input] !== undefined ), }); return result as any; } ); return handler(input, { accountId: input.accountId, entityId: input.timeEntryId }) as any; }, };

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/Good-Samaritan-Software-LLC/freshbooks-mcp'

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