Skip to main content
Glama
QUICK_REFERENCE.md5.38 kB
# Timer Tools - Quick Reference ## At a Glance | Tool | Purpose | Key Input | Key Output | |------|---------|-----------|------------| | `timer_start` | Start new timer | projectId, note | timeEntry.id (save this!) | | `timer_stop` | Stop & log time | timeEntryId | duration (auto-calculated) | | `timer_current` | Get running timer | accountId only | activeTimers array | | `timer_discard` | Delete timer | timeEntryId | success confirmation | ## Quick Examples ### Start Timer ```typescript const timer = await timerStartHandler({ accountId: "abc123", projectId: 12345, note: "Bug fixes" }, context); // Save timer.id for later! ``` ### Check Timer ```typescript const { activeTimers, count } = await timerCurrentHandler({ accountId: "abc123" }, context); if (count > 0) { console.log(`Timer ${activeTimers[0].id} is running`); } ``` ### Stop Timer ```typescript await timerStopHandler({ accountId: "abc123", timeEntryId: 67890, note: "Completed bug fixes" }, context); ``` ### Discard Timer ```typescript await timerDiscardHandler({ accountId: "abc123", timeEntryId: 67890 }, context); ``` ## Key Concepts ### Timer = Active TimeEntry - Timer is NOT a separate resource - Starting timer → Creates TimeEntry with `active=true` - Stopping timer → Updates TimeEntry with `active=false` - Current timer → Lists TimeEntry where `active=true` ### Duration Auto-Calculation - Start: `duration=0`, `startedAt=now` - Stop: FreshBooks calculates `duration = now - startedAt` - You never manually set duration when stopping ### One Timer Rule - FreshBooks typically allows ONE active timer per user - Always check current timer before starting new one - Attempting to start when one exists may cause conflict ## Common Patterns ### Safe Start ```typescript const current = await timerCurrentHandler({ accountId }); if (current.count === 0) { await timerStartHandler({ accountId, note: "..." }); } else { // Handle existing timer } ``` ### Get ID from Current ```typescript const current = await timerCurrentHandler({ accountId }); if (current.count > 0) { const timerId = current.activeTimers[0].id; await timerStopHandler({ accountId, timeEntryId: timerId }); } ``` ### Elapsed Time ```typescript const timer = activeTimers[0]; const elapsed = Math.floor( (Date.now() - new Date(timer.startedAt).getTime()) / 1000 ); console.log(`${elapsed} seconds elapsed`); ``` ## Critical Differences | Operation | Result | When to Use | |-----------|--------|-------------| | `timer_stop` | Saves time entry with duration | Normal workflow - log worked time | | `timer_discard` | **DELETES** time entry | Started by mistake, no work done | ## Error Quick Reference | Error Code | Meaning | Solution | |------------|---------|----------| | -32005 | Timer not found | Check ID with timer_current | | -32007 | Timer already exists | Stop current timer first | | -32001 | Not authenticated | Re-authenticate | | -32602 | Invalid params | Check required fields | ## Field Reference ### Input Fields (timer_start) - `accountId` - REQUIRED - `projectId` - Recommended for billing - `note` - Recommended for clarity - `billable` - Default: true - `clientId`, `serviceId`, `taskId` - Optional associations ### Output Fields - `id` - Timer ID (SAVE THIS!) - `active` - true=running, false=stopped - `duration` - Seconds elapsed - `startedAt` - ISO 8601 timestamp - `isLogged` - false=timer, true=logged time ## Integration Points ### With TimeEntry Tools Timer tools create/modify TimeEntry records: - Use `timeentry_list` to see all time entries (logged + timers) - Use `timeentry_update` for advanced modifications - Use `timeentry_single` to get full details ### With Project Tools Associate timers with projects: ```typescript // Start timer for specific project await timerStartHandler({ accountId: "abc123", projectId: 12345, // Links to project note: "Development" }); ``` ### With Auth Tools Always get accountId from auth: ```typescript const auth = await authStatusHandler(); const accountId = auth.accounts[0].id; await timerStartHandler({ accountId, ... }); ``` ## Testing Checklist - [ ] Start timer without timer running - [ ] Start timer when one already exists (should error) - [ ] Stop timer with valid ID - [ ] Stop timer with invalid ID (should error) - [ ] Get current timer when running - [ ] Get current timer when not running - [ ] Discard timer with confirmation - [ ] Duration auto-calculation works - [ ] All optional fields accepted - [ ] Error normalization works ## File Locations ``` src/tools/timer/ ├── schemas.ts # Zod schemas ├── timer-start.ts # Start timer tool ├── timer-stop.ts # Stop timer tool ├── timer-current.ts # Get current timer tool ├── timer-discard.ts # Discard timer tool ├── index.ts # Exports ├── README.md # Full documentation └── QUICK_REFERENCE.md # This file ``` ## Next Steps After implementing timer tools: 1. Run `npm run typecheck` to validate TypeScript 2. Run `npm test -- timer` to run tests (once tests are written) 3. Test with real FreshBooks account 4. Update server.ts to register tools with MCP server ## Resources - FreshBooks SDK: `@freshbooks/api` - SDK Method: `client.timeEntries.*` - Error Handler: `ErrorHandler.wrapHandler()` - Full docs: `./README.md`

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