timecard_get_timesheet
Retrieve saved weekly timesheet data including daily hours, approval status, and notes for a specific date. Use to view existing entries without making changes.
Instructions
Get timesheet data for a specific week. Read-only, no side effects.
Returns for each entry:
daily_hours: Hours worked each day (monday-saturday)
daily_status: Approval status each day (draft/submitted/approved/rejected)
daily_notes: Notes for each day
Status values:
draft: Saved but not submitted
submitted: Submitted for approval
approved: Approved by manager
rejected: Rejected by manager
This retrieves SAVED data from the server. Use timecard_save to make changes.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| date | Yes | Target date in YYYY-MM-DD format |
Implementation Reference
- src/tools/data-tools.ts:89-257 (handler)Implementation of the timecard_get_timesheet tool handler. It fetches the timesheet page for a given date, parses it, groups entries by activity, and returns the formatted data.
const timecardGetTimesheet: MCPTool = { name: 'timecard_get_timesheet', description: `Get timesheet data for a specific week. Read-only, no side effects. Returns for each entry: - daily_hours: Hours worked each day (monday-saturday) - daily_status: Approval status each day (draft/submitted/approved/rejected) - daily_notes: Notes for each day Status values: - draft: Saved but not submitted - submitted: Submitted for approval - approved: Approved by manager - rejected: Rejected by manager This retrieves SAVED data from the server. Use timecard_save to make changes.`, inputSchema: { type: 'object', properties: { date: { type: 'string', description: 'Target date in YYYY-MM-DD format' } }, required: ['date'] }, handler: async (args, session: TimeCardSession) => { const authResult = await session.ensureAuthenticated(); if (!authResult.success) { throw new Error(authResult.message); } const safeArgs = args || {}; const { date } = safeArgs; try { // Fetch the page (also sets session attributes for save) const html = await session.fetchTimesheetPage(date); // Calculate week boundaries const targetDate = new Date(date); const dayOfWeek = targetDate.getDay(); const monday = new Date(targetDate); monday.setDate(targetDate.getDate() - (dayOfWeek === 0 ? 6 : dayOfWeek - 1)); const saturday = new Date(monday); saturday.setDate(monday.getDate() + 5); const weekStart = monday.toISOString().split('T')[0]; const weekEnd = saturday.toISOString().split('T')[0]; // Parse data from HTML const activities = parseActivityList(html); const timeEntries = parseTimearray(html); const mapping = buildIndexMapping(activities); const days = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday']; // Group time entries by (projectIndex, activityIndex) const entryGroups = new Map<string, { projectIndex: number; activityIndex: number; entries: typeof timeEntries; }>(); for (const entry of timeEntries) { const key = `${entry.projectIndex}_${entry.activityIndex}`; if (!entryGroups.has(key)) { entryGroups.set(key, { projectIndex: entry.projectIndex, activityIndex: entry.activityIndex, entries: [], }); } entryGroups.get(key)!.entries.push(entry); } // Build output entries const entriesWithStatus: Array<{ index: number; project: { id: string; name: string }; activity: { id: string; name: string }; daily_hours: Record<string, number>; daily_status: Record<string, string>; daily_notes: Record<string, string>; }> = []; // Get project names from project options const projectOptions = await session.getProjectOptions(date); const projectNameMap = new Map(projectOptions.map(p => [p.id, p.name])); let entryIndex = 0; for (const [key, group] of entryGroups) { const projectId = mapping.projectIndexToId.get(group.projectIndex) || ''; const actEntry = mapping.activityByIndex.get(key); const projectName = projectNameMap.get(projectId) || `Project ${projectId}`; const activityId = actEntry?.uid || ''; const activityName = actEntry ? actEntry.name.replace(/<<.*?>>/, '').trim() : ''; const dailyHours: Record<string, number> = {}; const dailyStatus: Record<string, string> = {}; const dailyNotes: Record<string, string> = {}; // Initialize all days for (let d = 0; d < 6; d++) { dailyHours[days[d]] = 0; dailyStatus[days[d]] = 'draft'; dailyNotes[days[d]] = ''; } // Fill in data from timearray entries for (const entry of group.entries) { if (entry.dayIndex >= 0 && entry.dayIndex < 6) { const dayName = days[entry.dayIndex]; dailyHours[dayName] = parseFloat(entry.duration) || 0; dailyNotes[dayName] = entry.note; // Map status values if (entry.status === 'approve') { dailyStatus[dayName] = 'approved'; } else if (entry.status === 'submit') { dailyStatus[dayName] = 'submitted'; } else if (entry.status === 'reject') { dailyStatus[dayName] = 'rejected'; } else { dailyStatus[dayName] = 'draft'; } } } entriesWithStatus.push({ index: entryIndex++, project: { id: projectId, name: projectName }, activity: { id: activityId, name: activityName }, daily_hours: dailyHours, daily_status: dailyStatus, daily_notes: dailyNotes, }); } // Determine overall status let overallStatus = 'draft'; const statusPriority: Record<string, number> = { 'draft': 0, 'approved': 1, 'submitted': 2, 'rejected': 3 }; for (const entry of entriesWithStatus) { for (const day of Object.keys(entry.daily_status)) { const dayStatus = entry.daily_status[day]; if (entry.daily_hours[day] > 0 && statusPriority[dayStatus] > statusPriority[overallStatus]) { overallStatus = dayStatus; } } } return { week_start: weekStart, week_end: weekEnd, entries: entriesWithStatus, status: overallStatus }; } catch (error) { throw new Error(`Failed to get timesheet for ${date}: ${error instanceof Error ? error.message : 'Unknown error'}`); } } }; - src/tools/data-tools.ts:259-262 (registration)Registration of the timecard_get_timesheet tool within the exported dataTools array.
export const dataTools: MCPTool[] = [ timecardGetProjects, timecardGetActivities, timecardGetTimesheet,