Skip to main content
Glama
README.md10.9 kB
# Timer Tools Timer tools for FreshBooks time tracking. These tools enable starting, stopping, checking, and discarding timers through the FreshBooks API. ## Overview **Important**: Timer is NOT a standalone resource in FreshBooks. All timer operations are implemented through `TimeEntry` operations: - **Starting a timer** → Creates a `TimeEntry` with `active=true`, `duration=0` - **Stopping a timer** → Updates the `TimeEntry` with `active=false` (duration auto-calculated) - **Getting current timer** → Lists `TimeEntry` records where `active=true` - **Discarding a timer** → Deletes the `TimeEntry` completely ## Tools ### 1. timer_start Start a new timer for time tracking. **When to use:** - User wants to begin tracking time - Starting work on a project/task - Beginning billable hours **Input:** ```typescript { accountId: string; // Required: FreshBooks account ID projectId?: number; // Optional: Associate with project clientId?: number; // Optional: Associate with client serviceId?: number; // Optional: Type of work taskId?: number; // Optional: Specific task note?: string; // Optional: Work description billable?: boolean; // Optional: Is billable (default: true) internal?: boolean; // Optional: Internal work (default: false) } ``` **Output:** ```typescript { id: number; // Time entry ID - use this to stop/discard active: true; // Timer is running duration: 0; // Starts at 0 seconds startedAt: string; // ISO 8601 timestamp isLogged: false; // Not yet logged // ... other fields } ``` **Important notes:** - FreshBooks typically allows only ONE active timer per user - If a timer is already running, stop it first or it may cause an error - Returns the created `TimeEntry` - save the `id` to stop/discard later **Example:** ```typescript // Start timer for a project const timer = await timerStartHandler({ accountId: "abc123", projectId: 12345, note: "Working on bug fixes" }, context); console.log(`Timer started with ID: ${timer.id}`); ``` --- ### 2. timer_stop Stop a running timer and log the time. **When to use:** - User finishes work and wants to log time - Stopping time tracking - Clocking out **Input:** ```typescript { accountId: string; // Required: FreshBooks account ID timeEntryId: number; // Required: ID from timer_start or timer_current note?: string; // Optional: Update work description } ``` **Output:** ```typescript { id: number; // Time entry ID active: false; // Timer has stopped duration: number; // Auto-calculated seconds startedAt: string; // Original start time isLogged: true; // Time is now logged // ... other fields } ``` **How duration is calculated:** FreshBooks automatically calculates duration: - `duration = now - startedAt` (in seconds) - You don't need to provide duration when stopping **Important notes:** - Duration is calculated server-side based on elapsed time - Updates `active=false` and `isLogged=true` - Can optionally update the note before stopping **Example:** ```typescript // Stop timer and add final note const stoppedTimer = await timerStopHandler({ accountId: "abc123", timeEntryId: 67890, note: "Completed bug fixes and testing" }, context); console.log(`Logged ${stoppedTimer.duration} seconds of work`); ``` --- ### 3. timer_current Get currently running timer(s). **When to use:** - User asks "what timer is running?" - Need to find timer ID before stopping - Check if a timer is active - See current timer details **Input:** ```typescript { accountId: string; // Required: FreshBooks account ID } ``` **Output:** ```typescript { activeTimers: TimeEntry[]; // Array of active timers (typically 0 or 1) count: number; // Number of active timers } ``` **Typical results:** - `count: 0` → No timer is running - `count: 1` → One active timer (normal case) - `count: 2+` → Multiple timers (rare, depends on FreshBooks config) **Important notes:** - Returns ALL active timers for the user - Each timer includes full `TimeEntry` details - Use `activeTimers[0].id` to get the timer ID for stopping/discarding **Example:** ```typescript // Check current timer const current = await timerCurrentHandler({ accountId: "abc123" }, context); if (current.count === 0) { console.log("No timer is running"); } else { const timer = current.activeTimers[0]; console.log(`Timer ${timer.id} running for ${timer.duration}s`); console.log(`Started at: ${timer.startedAt}`); console.log(`Working on: ${timer.note || "No description"}`); } ``` **Calculating elapsed time:** ```typescript const timer = current.activeTimers[0]; const startTime = new Date(timer.startedAt); const now = new Date(); const elapsedSeconds = Math.floor((now.getTime() - startTime.getTime()) / 1000); console.log(`Elapsed: ${elapsedSeconds} seconds`); ``` --- ### 4. timer_discard Discard a timer without logging the time. **When to use:** - Timer was started by mistake - Work was not actually performed - Want to delete tracked time **Input:** ```typescript { accountId: string; // Required: FreshBooks account ID timeEntryId: number; // Required: ID of timer to discard } ``` **Output:** ```typescript { success: boolean; // true if deleted successfully timeEntryId: number; // ID that was deleted message: string; // Confirmation message } ``` **CRITICAL DIFFERENCE: Discard vs Stop** - `timer_stop`: **Keeps** the time entry, logs the duration - `timer_discard`: **Deletes** the time entry, no record remains **WARNING:** - This PERMANENTLY deletes the time entry - Tracked time CANNOT be recovered - Use with caution **Important notes:** - Completely removes the `TimeEntry` from FreshBooks - No duration is logged or saved - Should confirm with user before discarding **Example:** ```typescript // Discard timer (after user confirmation) const result = await timerDiscardHandler({ accountId: "abc123", timeEntryId: 67890 }, context); console.log(result.message); // "Timer 67890 has been discarded successfully..." ``` --- ## Common Workflows ### Start → Check → Stop ```typescript // 1. Start timer const timer = await timerStartHandler({ accountId: "abc123", projectId: 12345, note: "Development work" }, context); // 2. Later, check current timer const current = await timerCurrentHandler({ accountId: "abc123" }, context); console.log(`Timer running for ${current.activeTimers[0].duration}s`); // 3. Stop timer const stopped = await timerStopHandler({ accountId: "abc123", timeEntryId: timer.id, note: "Development work - completed feature X" }, context); console.log(`Logged ${stopped.duration} seconds`); ``` ### Check before starting ```typescript // Always check if timer is already running const current = await timerCurrentHandler({ accountId: "abc123" }, context); if (current.count > 0) { console.log("Timer already running! Stop it first or use the existing one."); } else { // Safe to start new timer const timer = await timerStartHandler({ accountId: "abc123", note: "New task" }, context); } ``` ### Discard with confirmation ```typescript // Get current timer const current = await timerCurrentHandler({ accountId: "abc123" }, context); if (current.count === 0) { console.log("No timer to discard"); } else { const timer = current.activeTimers[0]; // Confirm with user (pseudo-code) if (await confirmWithUser(`Discard timer? This will delete ${timer.duration}s of tracked time.`)) { await timerDiscardHandler({ accountId: "abc123", timeEntryId: timer.id }, context); } } ``` --- ## Error Handling All timer tools use automatic error normalization through `ErrorHandler.wrapHandler()`. ### Common errors: **No timer running:** ```typescript // timer_stop or timer_discard with invalid ID { code: -32005, // RESOURCE_NOT_FOUND message: "TimeEntry with id 12345 was not found", data: { recoverable: false, suggestion: "Verify the timer ID is correct..." } } ``` **Timer already exists:** ```typescript // Trying to start when one is already running { code: -32007, // CONFLICT message: "A timer is already running", data: { recoverable: true, suggestion: "Stop the current timer first..." } } ``` **Authentication error:** ```typescript { code: -32001, // NOT_AUTHENTICATED message: "Not authenticated", data: { recoverable: true, suggestion: "Please re-authenticate using auth_get_url" } } ``` --- ## Implementation Details ### SDK Mapping | Tool | SDK Method | Parameters | |------|------------|------------| | `timer_start` | `client.timeEntries.create()` | `{ active: true, duration: 0, ... }` | | `timer_stop` | `client.timeEntries.update()` | `{ active: false, isLogged: true }` | | `timer_current` | `client.timeEntries.list()` | Search: `active=true` | | `timer_discard` | `client.timeEntries.delete()` | `(accountId, timeEntryId)` | ### Key Fields **When creating (timer_start):** - `active: true` - Makes it a running timer - `duration: 0` - Timer starts at 0 - `isLogged: false` - Not yet logged - `startedAt: new Date()` - Current timestamp **When updating (timer_stop):** - `active: false` - Stops the timer - `isLogged: true` - Marks as logged - Duration is auto-calculated by FreshBooks **When searching (timer_current):** - Use `SearchQueryBuilder` - Filter: `boolean("active", true)` - Returns array of matching `TimeEntry` records --- ## Testing See `tests/tools/timer/` for comprehensive test suites: - `timer-start.test.ts` - Starting timers, validation, conflicts - `timer-stop.test.ts` - Stopping timers, duration calculation - `timer-current.test.ts` - Finding active timers, empty results - `timer-discard.test.ts` - Deleting timers, confirmations Run tests: ```bash npm test -- timer ``` --- ## Claude Usage Examples **Starting a timer:** > "Start a timer for Project Alpha. I'm working on bug fixes." **Checking timer:** > "What timer is running right now?" > "How long has my timer been running?" **Stopping timer:** > "Stop my timer and log the time." > "Clock out and note: completed code review" **Discarding timer:** > "I started a timer by mistake. Can you discard it?" > "Delete my current timer - I didn't actually work on it" --- ## Notes - Always check for existing timers before starting a new one - Timer duration is in seconds (3600 = 1 hour) - `startedAt` is ISO 8601 format: `"2024-12-21T10:30:00Z"` - The `timer` field in responses contains nested timer object with `isRunning` flag - Consider time zones when displaying timer information to users

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