Skip to main content
Glama

build_cron

Convert natural language schedule descriptions into cron expressions for automated task scheduling. Provides expression, human-readable confirmation, and next run times.

Instructions

Convert natural language schedule descriptions into cron expressions. Examples: 'every weekday at 9am', 'first Monday of each month at noon', 'every 5 minutes'. Returns the expression, human-readable confirmation, and next 5 run times.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
descriptionYesNatural language schedule description
timezoneNoTimezone for contextUTC

Implementation Reference

  • The buildCron function takes a natural language description and parses it into a cron expression and associated metadata.
    function buildCron(desc: string): CronResult {
      const lower = desc.toLowerCase().trim();
      const warnings: string[] = [];
    
      let minute = "*";
      let hour = "*";
      let dayOfMonth = "*";
      let month = "*";
      let dayOfWeek = "*";
    
      // ---- Every N minutes/hours ----
      const everyMinMatch = lower.match(/every\s+(\d+)\s*min/);
      if (everyMinMatch) {
        const n = parseInt(everyMinMatch[1], 10);
        minute = n === 1 ? "*" : `*/${n}`;
        const expr = `${minute} * * * *`;
        return formatResult(expr, `Every ${n} minute(s)`, desc, warnings);
      }
    
      const everyHourMatch = lower.match(/every\s+(\d+)\s*hour/);
      if (everyHourMatch) {
        const n = parseInt(everyHourMatch[1], 10);
        hour = n === 1 ? "*" : `*/${n}`;
        const expr = `0 ${hour} * * *`;
        return formatResult(expr, `Every ${n} hour(s), on the hour`, desc, warnings);
      }
    
      if (lower.match(/every\s*(minute|min$)/)) {
        return formatResult("* * * * *", "Every minute", desc, warnings);
      }
    
      if (lower.match(/every\s*hour|hourly/)) {
        return formatResult("0 * * * *", "Every hour, on the hour", desc, warnings);
      }
    
      // ---- Daily / every day ----
      if (lower.match(/every\s*day|daily/) && !lower.includes("weekday")) {
        const time = parseTime(lower);
        const expr = `${time.minute} ${time.hour} * * *`;
        return formatResult(expr, `Every day at ${formatTimeHuman(time.hour, time.minute)}`, desc, warnings);
      }
    
      // ---- Weekly ----
      if (lower.includes("weekly") || lower.includes("every week")) {
        const time = parseTime(lower);
        const days = parseDays(lower);
        const dow = days.length > 0 ? days.join(",") : "1"; // default Monday
        const expr = `${time.minute} ${time.hour} * * ${dow}`;
        return formatResult(expr, `Weekly on ${daysToHuman(days.length > 0 ? days : [1])} at ${formatTimeHuman(time.hour, time.minute)}`, desc, warnings);
      }
    
      // ---- Monthly ----
      const monthlyMatch = lower.match(/(?:every month|monthly)/);
      if (monthlyMatch) {
        const time = parseTime(lower);
        const domMatch = lower.match(/(\d{1,2})(?:st|nd|rd|th)/);
        const dom = domMatch ? domMatch[1] : "1";
        const expr = `${time.minute} ${time.hour} ${dom} * *`;
        return formatResult(expr, `Monthly on the ${ordinal(parseInt(dom, 10))} at ${formatTimeHuman(time.hour, time.minute)}`, desc, warnings);
      }
    
      // ---- "first/last Monday of each month" pattern ----
      const nthDayMatch = lower.match(/(first|second|third|fourth|last)\s+(sunday|monday|tuesday|wednesday|thursday|friday|saturday|sun|mon|tue|wed|thu|fri|sat)/);
      if (nthDayMatch) {
        const posMap: Record<string, string> = { first: "1", second: "2", third: "3", fourth: "4", last: "L" };
        const pos = posMap[nthDayMatch[1]] || "1";
        const dayName = nthDayMatch[2];
        const dayNum = DAYS[dayName] ?? 1;
        const time = parseTime(lower);
    
        // Standard cron doesn't support nth weekday; use the closest approximation
        if (pos === "L") {
          warnings.push("Standard cron does not support 'last weekday of month'. This approximates using day-of-week only. Consider using a cron library that supports extended syntax like '0L' for last occurrence.");
          const expr = `${time.minute} ${time.hour} * * ${dayNum}`;
          return formatResult(expr, `Every ${dayName} at ${formatTimeHuman(time.hour, time.minute)} (approximate — see warnings)`, desc, warnings);
        }
    
        // Approximate: first Monday ≈ days 1-7 on that weekday
        const dayRanges: Record<string, string> = { "1": "1-7", "2": "8-14", "3": "15-21", "4": "22-28" };
        const domRange = dayRanges[pos] || "1-7";
        const expr = `${time.minute} ${time.hour} ${domRange} * ${dayNum}`;
        return formatResult(expr, `${nthDayMatch[1]} ${dayName} of each month at ${formatTimeHuman(time.hour, time.minute)}`, desc, warnings);
      }
    
      // ---- Specific days of the week ----
      const days = parseDays(lower);
      if (days.length > 0) {
        const time = parseTime(lower);
        const dow = days.join(",");
        const expr = `${time.minute} ${time.hour} * * ${dow}`;
        return formatResult(expr, `${daysToHuman(days)} at ${formatTimeHuman(time.hour, time.minute)}`, desc, warnings);
      }
    
      // ---- Specific months ----
      for (const [name, num] of Object.entries(MONTH_NAMES)) {
        if (lower.includes(name)) {
          const time = parseTime(lower);
          const domMatch = lower.match(/(\d{1,2})(?:st|nd|rd|th)?/);
          const dom = domMatch ? domMatch[1] : "1";
          const expr = `${time.minute} ${time.hour} ${dom} ${num} *`;
          return formatResult(expr, `${name.charAt(0).toUpperCase() + name.slice(1)} ${ordinal(parseInt(dom, 10))} at ${formatTimeHuman(time.hour, time.minute)}`, desc, warnings);
        }
      }
    
      // ---- Yearly / annually ----
      if (lower.includes("yearly") || lower.includes("annually") || lower.includes("every year")) {
        const time = parseTime(lower);
        const expr = `${time.minute} ${time.hour} 1 1 *`;
        return formatResult(expr, `Yearly on January 1st at ${formatTimeHuman(time.hour, time.minute)}`, desc, warnings);
      }
    
      // ---- Fallback: try to find just a time ----
      const timeFallback = lower.match(/\d{1,2}(:\d{2})?\s*(am|pm)/);
      if (timeFallback) {
        const time = parseTime(lower);
        const expr = `${time.minute} ${time.hour} * * *`;
        warnings.push("Could not determine a specific schedule pattern — defaulting to daily at the specified time.");
        return formatResult(expr, `Daily at ${formatTimeHuman(time.hour, time.minute)}`, desc, warnings);
      }
    
      // ---- Couldn't parse ----
      warnings.push("Could not parse the schedule description. Returning a midnight daily default. Please try being more specific, e.g., 'every weekday at 9am' or 'every 5 minutes'.");
      return formatResult("0 0 * * *", "Daily at midnight (default — could not parse input)", desc, warnings);
    }
  • The tool handler which executes the buildCron function.
    // ----- Handler -----
    async function handler(input: Input): Promise<CronResult> {
      const result = buildCron(input.description);
      result.timezone = input.timezone;
      return result;
    }

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/marras0914/agent-toolbelt'

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