Skip to main content
Glama

log_time

Record completed work sessions with task details, duration, and optional date/tags to maintain accurate time tracking records.

Instructions

Log a completed time entry. This tool records work you've done.

Natural language examples Claude should parse:

  • "2h on security review" → task: "security review", duration: "2h"

  • "Client meeting yesterday 90 minutes" → task: "Client meeting", duration: "90m", date: "yesterday"

  • "Just finished 1.5h on code review" → task: "code review", duration: "1.5h"

Claude should extract:

  • task: What was worked on

  • duration: How long (2h, 90m, 1.5h, etc.)

  • time: When (optional, defaults to now)

  • date: Which day (optional, defaults to today)

  • tags: Inferred or explicit tags

  • company: Which company (optional, uses default)

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
companyNoCompany to log time for (optional, uses default if omitted)
dateNoDate of work (e.g., "today", "yesterday", "2025-10-17", omit for today)
durationYesDuration (e.g., "2h", "90m", "1.5h")
tagsNoTags to categorize work (e.g., ["development", "security"])
taskYesTask description (e.g., "Conduit MCP: Security review")
timeNoTime when work was done (e.g., "14:30", "2 hours ago", omit for now)

Implementation Reference

  • The core handler function for the 'log_time' tool. It processes LogTimeInput arguments: resolves company, sanitizes task, parses duration/date/time/tags, creates a TimeEntry, saves it to the company's markdown file via MarkdownManager, loads weekly summary, checks parse issues, and constructs a detailed markdown response with totals, commitment progress, and warnings.
    handler: withErrorHandler('logging time', async (args: LogTimeInput) => {
        // Resolve company (single-company mode auto-selects, multi-company requires explicit)
        const userInput = `${args.task || ''} ${args.duration || ''}`.trim();
        const company = getCompanyForOperation(args.company, userInput);
    
        // Sanitize and validate task description
        const sanitizedTask = sanitizeTaskDescription(args.task);
    
        // Parse inputs
        const duration = parseDuration(args.duration);
        const date = parseDate(args.date);
        const time = parseTime(args.time, date);
        const tags = args.tags || [];
    
        // Create time entry
        const entry: TimeEntry = {
            time: formatTime(time),
            task: sanitizedTask,
            duration: duration.hours,
            tags,
            date: formatDate(date)
        };
    
        // Save to markdown
        await markdownManager.addEntry(company, entry);
    
        // Load weekly summary for confirmation
        const { year, week } = getISOWeek(date);
        const summary = await markdownManager.getWeeklySummary(company, year, week);
    
        // Check for parse issues
        const parseIssues = markdownManager.getParseIssues();
    
        // Build response
        let response = `✓ Logged ${duration.formatted} for "${sanitizedTask}" at ${entry.time}`;
    
        if (args.date && args.date !== 'today') {
            response += ` on ${entry.date}`;
        }
    
        if (tags.length > 0) {
            response += ` [${tags.map(t => '#' + t).join(' ')}]`;
        }
    
        response += '\n\n';
    
        if (summary) {
            response += `**Week ${week} Status:**\n`;
            response += `• Total: ${summary.totalHours.toFixed(1)}h`;
    
            const config = await markdownManager.loadConfig(company);
            if (config.commitments.total) {
                const limit = config.commitments.total.limit;
                const stats = summaryCalculator.getCommitmentStats(summary.totalHours, limit);
                response += ` / ${limit}h (${stats.percentage}%)`;
    
                if (stats.status === 'over') {
                    response += ` ⚠️ OVER LIMIT`;
                } else if (stats.status === 'approaching') {
                    response += ` ⚠️ Close to limit`;
                }
            }
    
            response += '\n';
    
            // Show commitment breakdown
            for (const [commitment, hours] of Object.entries(summary.byCommitment)) {
                const limit = config.commitments[commitment]?.limit;
                if (limit) {
                    const stats = summaryCalculator.getCommitmentStats(hours, limit);
                    const warning = stats.status !== 'within' ? ` ${stats.indicator}` : '';
                    response += `• ${capitalizeName(commitment)}: ${hours.toFixed(1)}h / ${limit}h (${stats.percentage}%)${warning}\n`;
                }
            }
        }
    
        // Add parse warnings if any
        if (parseIssues.warnings.length > 0) {
            response += '\n\n⚠️ **Parse Warnings:**\n';
            for (const warning of parseIssues.warnings) {
                response += `• ${warning}\n`;
            }
            if (parseIssues.unparsedLines.length > 0 && parseIssues.unparsedLines.length <= 3) {
                response += '\nUnparsed lines:\n';
                for (const { lineNumber, content } of parseIssues.unparsedLines) {
                    response += `  Line ${lineNumber}: ${content}\n`;
                }
            }
        }
    
        return createTextResponse(response);
    })
  • JSON Schema definition for LogTimeInput used by the log_time tool, specifying properties for task, duration (required), time, date, tags, and optional company.
    inputSchema: {
        type: 'object',
        properties: {
            task: {
                type: 'string',
                description: 'Task description (e.g., "Conduit MCP: Security review")'
            },
            duration: {
                type: 'string',
                description: 'Duration (e.g., "2h", "90m", "1.5h")'
            },
            time: {
                type: 'string',
                description: 'Time when work was done (e.g., "14:30", "2 hours ago", omit for now)'
            },
            date: {
                type: 'string',
                description: 'Date of work (e.g., "today", "yesterday", "2025-10-17", omit for today)'
            },
            tags: {
                type: 'array',
                items: { type: 'string' },
                description: 'Tags to categorize work (e.g., ["development", "security"])'
            },
            company: {
                type: 'string',
                description: 'Company to log time for (optional, uses default if omitted)'
            }
        },
        required: ['task', 'duration']
    },
  • Registration of the 'log_time' tool using registerTool from ./registry.js. Includes name, detailed description with parsing examples, inputSchema, annotations, and handler.
    registerTool({
        name: 'log_time',
        description: `Log a completed time entry. This tool records work you've done.
    
    Natural language examples Claude should parse:
    - Single company mode: "2h on security review" → task: "security review", duration: "2h"
    - Multi company mode: "hm 2h on security review" → company: "HeliMods", duration: "2h", task: "security review"
    - Multi company mode: "2h debugging for stellantis" → duration: "2h", task: "debugging", company: "Stellantis"
    - "Client meeting yesterday 90 minutes" → task: "Client meeting", duration: "90m", date: "yesterday"
    
    Claude should extract:
    - task: What was worked on
    - duration: How long (2h, 90m, 1.5h, etc.)
    - time: When (optional, defaults to now)
    - date: Which day (optional, defaults to today)
    - tags: Inferred or explicit tags
    - company: CRITICAL - In multi-company mode, MUST extract from user input using:
      * Prefix pattern: "company/abbrev [duration] [task]" (e.g., "hm 2h debugging", "stellantis 1h meeting")
      * Suffix pattern: "[duration] [task] for company/abbrev" (e.g., "2h debugging for hm", "1h meeting for stellantis")
      * Full company names work (case-insensitive): "HeliMods", "Stellantis", etc.
      * Abbreviations work (case-insensitive): "HM", "STLA", etc.
      * In single-company mode, company is automatic (ignore any company in input)`,
        inputSchema: {
            type: 'object',
            properties: {
                task: {
                    type: 'string',
                    description: 'Task description (e.g., "Conduit MCP: Security review")'
                },
                duration: {
                    type: 'string',
                    description: 'Duration (e.g., "2h", "90m", "1.5h")'
                },
                time: {
                    type: 'string',
                    description: 'Time when work was done (e.g., "14:30", "2 hours ago", omit for now)'
                },
                date: {
                    type: 'string',
                    description: 'Date of work (e.g., "today", "yesterday", "2025-10-17", omit for today)'
                },
                tags: {
                    type: 'array',
                    items: { type: 'string' },
                    description: 'Tags to categorize work (e.g., ["development", "security"])'
                },
                company: {
                    type: 'string',
                    description: 'Company to log time for (optional, uses default if omitted)'
                }
            },
            required: ['task', 'duration']
        },
        annotations: {
            title: 'Log Time',
            readOnlyHint: false,
            destructiveHint: false,
            idempotentHint: false,
            openWorldHint: false
        },
        handler: withErrorHandler('logging time', async (args: LogTimeInput) => {
            // Resolve company (single-company mode auto-selects, multi-company requires explicit)
            const userInput = `${args.task || ''} ${args.duration || ''}`.trim();
            const company = getCompanyForOperation(args.company, userInput);
    
            // Sanitize and validate task description
            const sanitizedTask = sanitizeTaskDescription(args.task);
    
            // Parse inputs
            const duration = parseDuration(args.duration);
            const date = parseDate(args.date);
            const time = parseTime(args.time, date);
            const tags = args.tags || [];
    
            // Create time entry
            const entry: TimeEntry = {
                time: formatTime(time),
                task: sanitizedTask,
                duration: duration.hours,
                tags,
                date: formatDate(date)
            };
    
            // Save to markdown
            await markdownManager.addEntry(company, entry);
    
            // Load weekly summary for confirmation
            const { year, week } = getISOWeek(date);
            const summary = await markdownManager.getWeeklySummary(company, year, week);
    
            // Check for parse issues
            const parseIssues = markdownManager.getParseIssues();
    
            // Build response
            let response = `✓ Logged ${duration.formatted} for "${sanitizedTask}" at ${entry.time}`;
    
            if (args.date && args.date !== 'today') {
                response += ` on ${entry.date}`;
            }
    
            if (tags.length > 0) {
                response += ` [${tags.map(t => '#' + t).join(' ')}]`;
            }
    
            response += '\n\n';
    
            if (summary) {
                response += `**Week ${week} Status:**\n`;
                response += `• Total: ${summary.totalHours.toFixed(1)}h`;
    
                const config = await markdownManager.loadConfig(company);
                if (config.commitments.total) {
                    const limit = config.commitments.total.limit;
                    const stats = summaryCalculator.getCommitmentStats(summary.totalHours, limit);
                    response += ` / ${limit}h (${stats.percentage}%)`;
    
                    if (stats.status === 'over') {
                        response += ` ⚠️ OVER LIMIT`;
                    } else if (stats.status === 'approaching') {
                        response += ` ⚠️ Close to limit`;
                    }
                }
    
                response += '\n';
    
                // Show commitment breakdown
                for (const [commitment, hours] of Object.entries(summary.byCommitment)) {
                    const limit = config.commitments[commitment]?.limit;
                    if (limit) {
                        const stats = summaryCalculator.getCommitmentStats(hours, limit);
                        const warning = stats.status !== 'within' ? ` ${stats.indicator}` : '';
                        response += `• ${capitalizeName(commitment)}: ${hours.toFixed(1)}h / ${limit}h (${stats.percentage}%)${warning}\n`;
                    }
                }
            }
    
            // Add parse warnings if any
            if (parseIssues.warnings.length > 0) {
                response += '\n\n⚠️ **Parse Warnings:**\n';
                for (const warning of parseIssues.warnings) {
                    response += `• ${warning}\n`;
                }
                if (parseIssues.unparsedLines.length > 0 && parseIssues.unparsedLines.length <= 3) {
                    response += '\nUnparsed lines:\n';
                    for (const { lineNumber, content } of parseIssues.unparsedLines) {
                        response += `  Line ${lineNumber}: ${content}\n`;
                    }
                }
            }
    
            return createTextResponse(response);
        })
    });
  • TypeScript interface definition for LogTimeInput, matching the tool's input schema and used in the handler signature.
    export interface LogTimeInput {
        task: string;
        duration: string;       // "2h", "90m", "1.5h", etc.
        time?: string;          // "14:30", "2 hours ago", omit for now
        date?: string;          // "today", "yesterday", "2025-10-17"
        tags?: string[];
        company?: string;
    }

Tool Definition Quality

Score is being calculated. Check back soon.

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/markwharton/time-tracking-mcp'

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