Skip to main content
Glama

Time Tracking MCP

by markwharton
company-resolver.ts6.1 kB
// src/utils/company-resolver.ts import { TimeTrackingEnvironment } from '../config/environment.js'; /** * Resolve company name from input string (case-insensitive) * Supports both full company names and abbreviations * * @param input - Company name or abbreviation to resolve * @returns Canonical company name * @throws Error if company not found */ export function resolveCompany(input: string): string { const normalized = input.toLowerCase().trim(); const companies = TimeTrackingEnvironment.companies; const abbreviations = TimeTrackingEnvironment.getCompanyAbbreviations(); // 1. Check if it matches full company name (case-insensitive) for (const company of companies) { if (company.toLowerCase() === normalized) { return company; // Return canonical name } } // 2. Check if it matches any abbreviation (case-insensitive) for (const [company, abbrevs] of Object.entries(abbreviations)) { if (abbrevs.some(abbrev => abbrev.toLowerCase() === normalized)) { return company; } } // Not found - provide helpful error message const validOptions: string[] = []; for (const company of companies) { const abbrevs = abbreviations[company] || []; if (abbrevs.length > 0) { validOptions.push(`${company} (${abbrevs.join('/')})`); } else { validOptions.push(company); } } throw new Error(`Unknown company: "${input}". Valid options: ${validOptions.join(', ')}`); } /** * Extract company from natural language input * Supports prefix pattern: "hm 2h debugging" or "helimods 2h debugging" * Supports suffix pattern: "2h debugging for hm" or "2h debugging for helimods" * * @param input - Natural language input * @returns Company name if found, null otherwise */ export function extractCompanyFromInput(input: string): string | null { const normalized = input.toLowerCase().trim(); const companies = TimeTrackingEnvironment.companies; const abbreviations = TimeTrackingEnvironment.getCompanyAbbreviations(); // Build list of all possible company identifiers (case-insensitive) const allIdentifiers: Array<{ identifier: string; company: string }> = []; for (const company of companies) { // Add full company name allIdentifiers.push({ identifier: company.toLowerCase(), company }); // Add abbreviations const abbrevs = abbreviations[company] || []; for (const abbrev of abbrevs) { allIdentifiers.push({ identifier: abbrev.toLowerCase(), company }); } } // Sort by length (longest first) to match longest identifiers first allIdentifiers.sort((a, b) => b.identifier.length - a.identifier.length); // 1. Check prefix pattern: "company ..." or "abbrev ..." for (const { identifier, company } of allIdentifiers) { // Match "identifier " (with space after) if (normalized.startsWith(identifier + ' ')) { return company; } } // 2. Check suffix pattern: "... for company" or "... for abbrev" for (const { identifier, company } of allIdentifiers) { // Match " for identifier" (at end or followed by space) const pattern = ' for ' + identifier; if (normalized.endsWith(pattern) || normalized.includes(pattern + ' ')) { return company; } } return null; } /** * Validate that company is in the allowed list * @param company - Company name to validate * @throws Error if company not in configured list */ export function validateCompany(company: string): void { const companies = TimeTrackingEnvironment.companies; if (!companies.includes(company)) { throw new Error(`Company "${company}" is not configured. Configured companies: ${companies.join(', ')}`); } } /** * Check if system is in single-company mode * @returns true if only one company is configured */ export function isSingleCompanyMode(): boolean { return TimeTrackingEnvironment.companies.length === 1; } /** * Get the company to use for an operation * - Single company mode: Always return the single company (ignore input) * - Multi company mode: Require explicit company specification * * @param explicitCompany - Company explicitly specified by user/tool * @param naturalLanguageInput - Original user input for pattern matching * @returns Company name to use * @throws Error if multi-company mode and no company specified */ export function getCompanyForOperation( explicitCompany: string | undefined, naturalLanguageInput?: string ): string { // Single company mode: always use the single company if (isSingleCompanyMode()) { return TimeTrackingEnvironment.companies[0]; } // Multi-company mode: require explicit specification let company: string | undefined = explicitCompany; // Try to extract from natural language if not explicitly provided if (!company && naturalLanguageInput) { const extracted = extractCompanyFromInput(naturalLanguageInput); if (extracted) { company = extracted; } } if (!company) { const companies = TimeTrackingEnvironment.companies; const abbreviations = TimeTrackingEnvironment.getCompanyAbbreviations(); // Build helpful error message const examples: string[] = []; for (const comp of companies) { const abbrevs = abbreviations[comp] || []; if (abbrevs.length > 0) { examples.push(`'${comp}/${abbrevs[0]}'`); } else { examples.push(`'${comp}'`); } } throw new Error( `Company required when multiple companies configured. ` + `Use prefix pattern (${examples.join(' or ')} 2h task) ` + `or suffix pattern (2h task for ${examples.join(' or ')})` ); } // Resolve abbreviation to canonical name and validate const resolved = resolveCompany(company); validateCompany(resolved); return resolved; }

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