Skip to main content
Glama
austinmoody

Things MCP Server

by austinmoody
validation.js10.3 kB
/** * Validation Utilities - Enhanced validation helpers for Things MCP Server * * This module provides comprehensive validation functions for input data, * environment setup, and system requirements. These utilities help ensure * robust error handling and provide clear feedback to users. * * The validation functions follow a consistent pattern: * - Return true if validation passes * - Throw descriptive errors if validation fails * - Include suggestions for fixing common issues */ /** * Validate that the environment is properly configured for Things operations * * This function checks all the prerequisites for running Things MCP operations: * - Operating system (macOS required) * - Things app availability * - Authentication token (for write operations) * * @param {boolean} requiresAuth - Whether the operation requires authentication * @returns {boolean} - True if environment is valid * @throws {Error} - Descriptive error with troubleshooting suggestions */ export function validateEnvironment(requiresAuth = false) { // Import platform check here to avoid circular dependencies import { platform } from 'os'; // Check operating system if (platform() !== 'darwin') { throw new Error( 'Things app is only available on macOS. ' + 'Please run this MCP server on a Mac with Things installed.' ); } // Check authentication token if required if (requiresAuth && !process.env.THINGS_AUTHENTICATION_TOKEN) { throw new Error( 'THINGS_AUTHENTICATION_TOKEN environment variable is required for this operation. ' + 'Please set this variable with your Things authentication token. ' + 'You can find your token in Things > Preferences > General > Enable Things URLs.' ); } return true; } /** * Validate todo data for creation or update operations * * This function ensures that todo data meets all requirements: * - Required fields are present and non-empty * - Optional fields have valid values * - Field lengths are within reasonable limits * * @param {Object} todoData - The todo data to validate * @param {boolean} isUpdate - Whether this is for an update operation (allows missing title) * @returns {Object} - Cleaned and validated todo data * @throws {Error} - Validation error with specific field information */ export function validateTodoData(todoData, isUpdate = false) { if (!todoData || typeof todoData !== 'object') { throw new Error('Todo data must be an object'); } const cleaned = {}; // Validate title (required for creation, optional for updates) if (!isUpdate) { if (!todoData.title || typeof todoData.title !== 'string' || todoData.title.trim() === '') { throw new Error('Todo title is required and must be a non-empty string'); } cleaned.title = todoData.title.trim(); } else if (todoData.title !== undefined) { if (typeof todoData.title !== 'string') { throw new Error('Todo title must be a string'); } cleaned.title = todoData.title.trim(); } // Validate optional fields if (todoData.notes !== undefined) { if (typeof todoData.notes !== 'string') { throw new Error('Todo notes must be a string'); } cleaned.notes = todoData.notes; // Don't trim notes to preserve formatting } if (todoData.when !== undefined) { const validWhenValues = ['today', 'tomorrow', 'evening', 'anytime', 'someday']; if (!validWhenValues.includes(todoData.when)) { throw new Error(`Todo 'when' must be one of: ${validWhenValues.join(', ')}`); } cleaned.when = todoData.when; } if (todoData.deadline !== undefined) { if (typeof todoData.deadline !== 'string') { throw new Error('Todo deadline must be a string (date format)'); } cleaned.deadline = todoData.deadline.trim(); } if (todoData.tags !== undefined) { if (typeof todoData.tags !== 'string') { throw new Error('Todo tags must be a string (comma-separated)'); } cleaned.tags = todoData.tags.trim(); } if (todoData.checklist !== undefined) { if (typeof todoData.checklist !== 'string') { throw new Error('Todo checklist must be a string (line-separated)'); } cleaned.checklist = todoData.checklist; // Don't trim to preserve formatting } if (todoData.list !== undefined) { if (typeof todoData.list !== 'string') { throw new Error('Todo list must be a string'); } cleaned.list = todoData.list.trim(); } return cleaned; } /** * Validate project data for creation or update operations * * Similar to todo validation but with project-specific fields and requirements. * * @param {Object} projectData - The project data to validate * @param {boolean} isUpdate - Whether this is for an update operation * @returns {Object} - Cleaned and validated project data * @throws {Error} - Validation error with specific field information */ export function validateProjectData(projectData, isUpdate = false) { if (!projectData || typeof projectData !== 'object') { throw new Error('Project data must be an object'); } const cleaned = {}; // Validate title (required for creation, optional for updates) if (!isUpdate) { if (!projectData.title || typeof projectData.title !== 'string' || projectData.title.trim() === '') { throw new Error('Project title is required and must be a non-empty string'); } cleaned.title = projectData.title.trim(); } else if (projectData.title !== undefined) { if (typeof projectData.title !== 'string') { throw new Error('Project title must be a string'); } cleaned.title = projectData.title.trim(); } // Validate optional fields if (projectData.notes !== undefined) { if (typeof projectData.notes !== 'string') { throw new Error('Project notes must be a string'); } cleaned.notes = projectData.notes; } if (projectData.when !== undefined) { const validWhenValues = ['today', 'tomorrow', 'evening', 'anytime', 'someday']; if (!validWhenValues.includes(projectData.when)) { throw new Error(`Project 'when' must be one of: ${validWhenValues.join(', ')}`); } cleaned.when = projectData.when; } if (projectData.deadline !== undefined) { if (typeof projectData.deadline !== 'string') { throw new Error('Project deadline must be a string (date format)'); } cleaned.deadline = projectData.deadline.trim(); } if (projectData.tags !== undefined) { if (typeof projectData.tags !== 'string') { throw new Error('Project tags must be a string (comma-separated)'); } cleaned.tags = projectData.tags.trim(); } if (projectData.area !== undefined) { if (typeof projectData.area !== 'string') { throw new Error('Project area must be a string'); } cleaned.area = projectData.area.trim(); } if (projectData.todos !== undefined) { if (!Array.isArray(projectData.todos)) { throw new Error('Project todos must be an array'); } // Validate each todo in the array cleaned.todos = projectData.todos .filter(todo => todo && typeof todo === 'string' && todo.trim()) .map(todo => todo.trim()); } return cleaned; } /** * Validate ID for update/completion operations * * Ensures that IDs used for identifying existing items are valid. * * @param {string} id - The ID to validate * @param {string} itemType - Type of item (for error messages) * @returns {string} - Cleaned ID * @throws {Error} - Validation error */ export function validateItemId(id, itemType = 'item') { if (!id || typeof id !== 'string' || id.trim() === '') { throw new Error(`${itemType} ID is required and must be a non-empty string`); } const cleanId = id.trim(); // Basic length check (Things IDs are typically reasonable length) if (cleanId.length > 1000) { throw new Error(`${itemType} ID is too long (max 1000 characters)`); } return cleanId; } /** * Validate search query * * Ensures search queries are valid and not empty. * * @param {string} query - The search query to validate * @returns {string} - Cleaned query * @throws {Error} - Validation error */ export function validateSearchQuery(query) { if (!query || typeof query !== 'string' || query.trim() === '') { throw new Error('Search query is required and must be a non-empty string'); } const cleanQuery = query.trim(); // Reasonable length limit for search queries if (cleanQuery.length > 500) { throw new Error('Search query is too long (max 500 characters)'); } return cleanQuery; } /** * Validate batch operation parameters * * Ensures batch operations have reasonable limits and valid data. * * @param {Array} items - Array of items for batch operation * @param {string} operationType - Type of operation (for error messages) * @param {number} maxItems - Maximum allowed items * @returns {Array} - Validated items array * @throws {Error} - Validation error */ export function validateBatchItems(items, operationType = 'batch operation', maxItems = 50) { if (!Array.isArray(items)) { throw new Error(`${operationType} requires an array of items`); } if (items.length === 0) { throw new Error(`${operationType} requires at least one item`); } if (items.length > maxItems) { throw new Error(`${operationType} supports a maximum of ${maxItems} items at once. ` + `Please split your request into smaller batches.`); } return items; } /** * Validate Things list ID for show operations * * Ensures list IDs are valid Things list identifiers. * * @param {string} listId - The list ID to validate * @returns {string} - Validated list ID * @throws {Error} - Validation error */ export function validateListId(listId) { if (!listId || typeof listId !== 'string' || listId.trim() === '') { throw new Error('List ID is required'); } const validListIds = [ 'today', 'inbox', 'upcoming', 'anytime', 'someday', 'projects', 'areas', 'logbook' ]; const cleanListId = listId.trim().toLowerCase(); if (!validListIds.includes(cleanListId)) { throw new Error(`Invalid list ID. Must be one of: ${validListIds.join(', ')}`); } return cleanListId; }

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/austinmoody/things-mcp'

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