Skip to main content
Glama

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

TableJSON Schema
NameRequiredDescriptionDefault
dateYesTarget date in YYYY-MM-DD format

Implementation Reference

  • 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'}`);
        }
      }
    };
  • Registration of the timecard_get_timesheet tool within the exported dataTools array.
    export const dataTools: MCPTool[] = [
      timecardGetProjects,
      timecardGetActivities,
      timecardGetTimesheet,

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/keith-hung/timecard-mcp'

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