Skip to main content
Glama

diagnose_punches

Identifies issues in raw clock punches—duplicates, odd counts, round-number bias, off-shift punches—and reports longest/shortest gaps, returning a recommendation: usable, review, or reject.

Instructions

Triage raw clock punches before trusting them: count, sort, dedup, surface duplicates / odd-punch counts / round-number bias, report longest and shortest gaps, and (when an expected shift is provided) flag punches that fall outside it. Returns a recommendation: 'usable', 'review', or 'reject'.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
dateYesDuty date for the punches, YYYY-MM-DD.
punchesYes
expectedShiftNoOptional shift definition. When provided, the report flags punches outside [start - 4h, end + 4h] as off-shift.
dedupeSecondsNo

Implementation Reference

  • The registerDiagnosePunches function registers the 'diagnose_punches' tool on the McpServer via server.tool(), defining its schema, description, and handler.
    export function registerDiagnosePunches(server: McpServer): void {
      server.tool(
        'diagnose_punches',
        "Triage raw clock punches before trusting them: count, sort, dedup, surface duplicates / odd-punch counts / round-number bias, report longest and shortest gaps, and (when an expected shift is provided) flag punches that fall outside it. Returns a recommendation: 'usable', 'review', or 'reject'.",
        {
          date: z
            .string()
            .regex(/^\d{4}-\d{2}-\d{2}$/)
            .describe('Duty date for the punches, YYYY-MM-DD.'),
          punches: z.array(PunchSchema),
          expectedShift: ShiftConfigSchema.optional().describe(
            'Optional shift definition. When provided, the report flags punches outside [start - 4h, end + 4h] as off-shift.',
          ),
          dedupeSeconds: z.number().int().min(0).optional(),
        },
        async ({ date, punches, expectedShift, dedupeSeconds }) => {
          const shift = expectedShift ?? { start: '00:00', end: '23:59' };
          const result = resolveDay({
            date,
            punches,
            shift,
            policy: { pairing: 'in-out-pairs', dedupeSeconds: dedupeSeconds ?? 60 },
          });
    
          const flagSet = new Set(result.flags);
    
          // Gap analysis from the resolved segments + interstitial gaps.
          const segs = result.segments;
          const gaps: number[] = [];
          for (let i = 0; i < segs.length - 1; i += 1) {
            const a = segs[i]!.out;
            const b = segs[i + 1]!.in;
            const minutes = Math.round((Date.parse(b) - Date.parse(a)) / 60_000);
            if (minutes > 0) gaps.push(minutes);
          }
    
          const longestGapMinutes = gaps.length === 0 ? 0 : Math.max(...gaps);
          const shortestGapMinutes = gaps.length === 0 ? 0 : Math.min(...gaps);
    
          const issues: string[] = [];
          if (flagSet.has('duplicate-punch')) issues.push('Duplicate punches were dropped — review your device dedupe window.');
          if (flagSet.has('odd-punch-count')) issues.push('Odd punch count — at least one in or out is missing.');
          if (flagSet.has('round-number-bias')) issues.push('Every punch lands on a 5-minute boundary with zero seconds — likely manual entry, not a device read.');
          if (flagSet.has('no-punches')) issues.push('No usable punches.');
          if (flagSet.has('missing-out-resolved')) issues.push("Engine synthesised a punch-out at shift end to recover — original record is incomplete.");
    
          const recommendation: 'usable' | 'review' | 'reject' =
            flagSet.has('no-punches') || flagSet.has('odd-punch-count')
              ? 'reject'
              : (flagSet.has('round-number-bias') || flagSet.has('duplicate-punch'))
                ? 'review'
                : 'usable';
    
          return {
            content: [
              jsonText({
                date,
                count: punches.length,
                firstIn: result.firstIn,
                lastOut: result.lastOut,
                segments: result.segments.length,
                longestGapMinutes,
                shortestGapMinutes,
                flags: result.flags,
                issues,
                recommendation,
                workedMinutesEstimate: result.workedMinutes,
                spansMidnight: result.spansMidnight,
              }),
            ],
          };
        },
      );
    }
  • The async handler function that executes the tool logic: calls resolveDay() from the core engine, performs gap analysis, checks flags for issues (duplicate, odd count, round-number bias, etc.), and returns a recommendation (usable/review/reject) with a detailed JSON report.
    async ({ date, punches, expectedShift, dedupeSeconds }) => {
      const shift = expectedShift ?? { start: '00:00', end: '23:59' };
      const result = resolveDay({
        date,
        punches,
        shift,
        policy: { pairing: 'in-out-pairs', dedupeSeconds: dedupeSeconds ?? 60 },
      });
    
      const flagSet = new Set(result.flags);
    
      // Gap analysis from the resolved segments + interstitial gaps.
      const segs = result.segments;
      const gaps: number[] = [];
      for (let i = 0; i < segs.length - 1; i += 1) {
        const a = segs[i]!.out;
        const b = segs[i + 1]!.in;
        const minutes = Math.round((Date.parse(b) - Date.parse(a)) / 60_000);
        if (minutes > 0) gaps.push(minutes);
      }
    
      const longestGapMinutes = gaps.length === 0 ? 0 : Math.max(...gaps);
      const shortestGapMinutes = gaps.length === 0 ? 0 : Math.min(...gaps);
    
      const issues: string[] = [];
      if (flagSet.has('duplicate-punch')) issues.push('Duplicate punches were dropped — review your device dedupe window.');
      if (flagSet.has('odd-punch-count')) issues.push('Odd punch count — at least one in or out is missing.');
      if (flagSet.has('round-number-bias')) issues.push('Every punch lands on a 5-minute boundary with zero seconds — likely manual entry, not a device read.');
      if (flagSet.has('no-punches')) issues.push('No usable punches.');
      if (flagSet.has('missing-out-resolved')) issues.push("Engine synthesised a punch-out at shift end to recover — original record is incomplete.");
    
      const recommendation: 'usable' | 'review' | 'reject' =
        flagSet.has('no-punches') || flagSet.has('odd-punch-count')
          ? 'reject'
          : (flagSet.has('round-number-bias') || flagSet.has('duplicate-punch'))
            ? 'review'
            : 'usable';
    
      return {
        content: [
          jsonText({
            date,
            count: punches.length,
            firstIn: result.firstIn,
            lastOut: result.lastOut,
            segments: result.segments.length,
            longestGapMinutes,
            shortestGapMinutes,
            flags: result.flags,
            issues,
            recommendation,
            workedMinutesEstimate: result.workedMinutes,
            spansMidnight: result.spansMidnight,
          }),
        ],
      };
    },
  • Input schema definition using Zod: date (YYYY-MM-DD), punches (array of PunchSchema), optional expectedShift (ShiftConfigSchema), and optional dedupeSeconds.
    {
      date: z
        .string()
        .regex(/^\d{4}-\d{2}-\d{2}$/)
        .describe('Duty date for the punches, YYYY-MM-DD.'),
      punches: z.array(PunchSchema),
      expectedShift: ShiftConfigSchema.optional().describe(
        'Optional shift definition. When provided, the report flags punches outside [start - 4h, end + 4h] as off-shift.',
      ),
      dedupeSeconds: z.number().int().min(0).optional(),
    },
  • PunchSchema used in the tool's input: at (ISO-8601 instant), optional source (biometric/mobile/manual/web), and optional location string.
    export const PunchSchema = z.object({
      at: z
        .string()
        .describe('ISO-8601 instant with an explicit offset, e.g. "2026-06-01T08:57:00+06:00".'),
      source: z.enum(['biometric', 'mobile', 'manual', 'web']).optional(),
      location: z.string().optional(),
    });
  • ShiftConfigSchema used for the optional expectedShift parameter: defines start, end, breaks, graceIn, graceOut, minHalfDayMinutes, and flexible fields.
    export const ShiftConfigSchema = z.object({
      start: z
        .string()
        .regex(/^\d{2}:\d{2}$/)
        .describe('Scheduled start, HH:MM, worksite local wall-clock.'),
      end: z
        .string()
        .regex(/^\d{2}:\d{2}$/)
        .describe('Scheduled end, HH:MM. If <= start, the shift is overnight and ends on the next day.'),
      breaks: z.array(BreakWindowSchema).optional(),
      graceIn: z.number().int().min(0).optional(),
      graceOut: z.number().int().min(0).optional(),
      minHalfDayMinutes: z.number().int().min(0).optional(),
      flexible: z.boolean().optional(),
    });
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries full burden. It discloses the tool's analytical actions (count, sort, dedup, etc.) and the output recommendation. It does not mention destructive intent or side effects, which is appropriate for a read-only diagnostic. Slightly more detail on non-modification would be ideal.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, well-structured sentence that front-loads the main action and provides a comprehensive overview without unnecessary words. Every clause adds value.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the nested object parameters and no output schema, the description adequately explains the tool's behavior and output (a recommendation). It covers the key analytical aspects but lacks detail on the exact format of the return value or handling of edge cases like missing data.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 50%; the description adds context for expectedShift (flags outside shift) but does not explain dedupeSeconds or the overall usage of punches array beyond the schema. The description compensates partially but could be more explicit about how each parameter influences the analysis.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: triage raw clock punches by counting, sorting, deduping, and surfacing anomalies. It specifies precise actions (duplicates, odd-punch counts, bias, gaps) and outputs a recommendation. This distinctly differentiates it from sibling tools like apply_rounding or audit_period_compliance.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage before trusting punches, providing a clear context. However, it does not explicitly state when not to use this tool or offer alternatives among siblings. The context 'before trusting them' is helpful but lacks explicit exclusions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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/arifur9993/attendance-engine-mcp'

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