Skip to main content
Glama

Time Tracking MCP

by markwharton
log-time.ts5.93 kB
// src/tools/log-time.ts import { registerTool } from './registry.js'; import { MarkdownManager } from '../services/markdown-manager.js'; import { parseDuration } from '../services/duration-parser.js'; import { parseDate, parseTime, formatDate, formatTime, getISOWeek } from '../utils/date-utils.js'; import { createTextResponse, withErrorHandler } from '../utils/tool-response.js'; import { getCompanyForOperation } from '../utils/company-resolver.js'; import type { LogTimeInput, TimeEntry } from '../types/index.js'; const markdownManager = new MarkdownManager(); 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); // 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: args.task, 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); // Build response let response = `✓ Logged ${duration.formatted} for "${args.task}" 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 percent = Math.round((summary.totalHours / limit) * 100); response += ` / ${limit}h (${percent}%)`; if (percent > 100) { response += ` ⚠️ OVER LIMIT`; } else if (percent > 90) { 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 percent = Math.round((hours / limit) * 100); const warning = percent > 100 ? ' ⚠️' : ''; response += `• ${commitment.charAt(0).toUpperCase() + commitment.slice(1)}: ${hours.toFixed(1)}h / ${limit}h (${percent}%)${warning}\n`; } } } return createTextResponse(response); }) });

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