Skip to main content
Glama
pshempel

MCP Time Server Node

by pshempel
validation.ts7.19 kB
import { parseISO, isValid } from 'date-fns'; import { getTimezoneOffset } from 'date-fns-tz'; import { ValidationError } from '../adapters/mcp-sdk/errors'; import type { TimeUnit, RecurrencePattern, TimeServerError } from '../types'; // eslint-disable-next-line @typescript-eslint/consistent-type-imports import { TimeServerErrorCodes } from '../types'; import { debug } from './debug'; // Security limits for input validation export const LIMITS = { MAX_STRING_LENGTH: 1000, // General string inputs MAX_TIMEZONE_LENGTH: 100, // IANA timezones are typically < 30 chars MAX_DATE_STRING_LENGTH: 100, // ISO dates are ~25 chars max MAX_FORMAT_LENGTH: 200, // Format strings rarely exceed 50 chars MAX_ARRAY_LENGTH: 365, // One year of daily entries }; /** * Validates a timezone string using date-fns-tz * @param timezone - The timezone to validate * @param allowEmpty - Whether to allow empty string (defaults to UTC) * @returns true if valid timezone, false otherwise */ export function validateTimezone(timezone: string | undefined | null, allowEmpty = false): boolean { if (!timezone) { return allowEmpty && timezone === ''; } // Check length first for security validateStringLength(timezone, LIMITS.MAX_TIMEZONE_LENGTH, 'timezone'); // Use getTimezoneOffset - it returns NaN for invalid timezones const offset = getTimezoneOffset(timezone, new Date()); return !isNaN(offset); } /** * Validates a date string or Unix timestamp * @param date - The date to validate (string, number, or undefined) * @returns true if valid date format, false otherwise */ export function validateDateFormat(date: string | number | undefined | null): boolean { if (date === null || date === undefined || date === '') { return false; } // Strict type checking - reject objects even if they have toString() if (typeof date !== 'string' && typeof date !== 'number') { return false; } // Handle Unix timestamps (number or string) if (typeof date === 'number') { return isValid(new Date(date * 1000)); } if (typeof date === 'string') { // Check length first for security validateStringLength(date, LIMITS.MAX_DATE_STRING_LENGTH, 'date'); if (/^\d+$/.test(date)) { const timestamp = parseInt(date, 10); return isValid(new Date(timestamp * 1000)); } // Try to parse as ISO format const parsed = parseISO(date); return isValid(parsed); } return false; } /** * Validates a date string with length check * @param dateStr - The date string to validate * @param fieldName - Field name for error messages * @returns void (throws on error) */ export function validateDateString(dateStr: string | undefined | null, fieldName = 'date'): void { if (dateStr !== undefined && dateStr !== null) { validateStringLength(dateStr, LIMITS.MAX_DATE_STRING_LENGTH, fieldName); } } /** * Validates date input with strict type checking * @param dateInput - The date input to validate * @param fieldName - Field name for error messages * @returns void (throws on error) */ export function validateDateInput(dateInput: unknown, fieldName = 'date'): void { // Strict type checking - only allow string or number if (typeof dateInput !== 'string' && typeof dateInput !== 'number') { debug.error('%s must be a string or number, got: %s', fieldName, typeof dateInput); throw new ValidationError(`${fieldName} must be a string or number`, { fieldName, type: typeof dateInput, }); } // Additional validation for strings if (typeof dateInput === 'string') { validateStringLength(dateInput, LIMITS.MAX_DATE_STRING_LENGTH, fieldName); } } /** * Validates a time unit * @param unit - The time unit to validate * @returns true if valid unit, false otherwise */ export function validateTimeUnit(unit: string): boolean { const validUnits: TimeUnit[] = ['years', 'months', 'days', 'hours', 'minutes', 'seconds']; return validUnits.includes(unit as TimeUnit); } /** * Validates a recurrence pattern * @param pattern - The pattern to validate * @returns true if valid pattern, false otherwise */ export function validateRecurrencePattern(pattern: string): boolean { const validPatterns: RecurrencePattern[] = ['daily', 'weekly', 'monthly', 'yearly']; return validPatterns.includes(pattern as RecurrencePattern); } /** * Validates day of week (0-6, where 0 is Sunday) * @param day - The day to validate * @returns true if valid day, false otherwise */ export function validateDayOfWeek(day: number): boolean { return Number.isInteger(day) && day >= 0 && day <= 6; } /** * Validates day of month (1-31) * @param day - The day to validate * @returns true if valid day, false otherwise */ export function validateDayOfMonth(day: number): boolean { return Number.isInteger(day) && day >= 1 && day <= 31; } /** * Validates string length * @param str - The string to validate * @param maxLength - Maximum allowed length * @param fieldName - Name of the field for error messages * @returns true if valid length, false otherwise */ export function validateStringLength( str: string | undefined | null, maxLength: number, fieldName: string ): boolean { if (!str) return true; // undefined/null are handled elsewhere if (str.length > maxLength) { debug.error( '%s exceeds maximum length of %d characters (got %d)', fieldName, maxLength, str.length ); throw new ValidationError(`${fieldName} exceeds maximum length of ${maxLength} characters`, { fieldName, length: str.length, maxLength, }); } return true; } /** * Validates array length * @param arr - The array to validate * @param maxLength - Maximum allowed length * @param fieldName - Name of the field for error messages * @returns true if valid length, false otherwise */ export function validateArrayLength<T>( arr: T[] | undefined | null, maxLength: number, fieldName: string ): boolean { if (!arr) return true; // undefined/null are handled elsewhere if (arr.length > maxLength) { debug.error( '%s exceeds maximum array length of %d items (got %d)', fieldName, maxLength, arr.length ); throw new ValidationError(`${fieldName} exceeds maximum array length of ${maxLength} items`, { fieldName, length: arr.length, maxLength, }); } // Also validate each string in the array if it's a string array if (arr.length > 0 && typeof arr[0] === 'string') { (arr as unknown as string[]).forEach((item, index) => { if (typeof item === 'string') { validateStringLength(item, LIMITS.MAX_DATE_STRING_LENGTH, `${fieldName}[${index}]`); } }); } return true; } /** * Creates a standardized error object * @param code - The error code * @param message - The error message * @param details - Optional additional details * @returns TimeServerError object */ export function createError( code: TimeServerErrorCodes, message: string, details?: unknown ): TimeServerError { const error: TimeServerError = { code, message }; if (details !== undefined) { error.details = details; } return error; }

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/pshempel/mcp-time-server-node'

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