Skip to main content
Glama
austinmoody

Things MCP Server

by austinmoody
batch-tools.js16.7 kB
/** * Batch Tools - MCP Tools for Things App Batch Operations * * This module defines MCP tools that handle batch operations in the Things app. * These tools allow performing operations on multiple items at once, which is * more efficient than executing individual commands for each item. * * Batch operations require authentication tokens for security and need to identify * multiple items to operate on. The tools validate input thoroughly and provide * detailed progress reporting for each operation. */ /** * Create and return all batch-related MCP tools * * This factory function creates tool objects configured with a specific * ThingsClient instance. Each tool handles different types of batch * operations in the Things app. * * @param {ThingsClient} thingsClient - Configured Things client instance * @returns {Array} - Array of MCP tool objects */ export function createBatchTools(thingsClient) { return [ { name: "batch_add_todos", description: "Create multiple to-do items in Things at once. This is more efficient than adding items one by one.", // JSON Schema defining the input parameters for batch adding to-dos inputSchema: { type: "object", properties: { todos: { type: "array", description: "Array of to-do items to create", minItems: 1, maxItems: 20, // Reasonable limit to prevent overwhelming the system items: { type: "object", properties: { title: { type: "string", description: "The title/name of the to-do item (required)", minLength: 1 }, notes: { type: "string", description: "Optional notes or description for the to-do" }, when: { type: "string", description: "When to schedule the to-do", enum: ["today", "tomorrow", "evening", "anytime", "someday"] }, deadline: { type: "string", description: "Due date for the to-do" }, tags: { type: "string", description: "Comma-separated list of tags" }, list: { type: "string", description: "Which list to add the to-do to" } }, required: ["title"] } } }, required: ["todos"], }, /** * Handler function for the batch_add_todos tool * * This function validates the input and creates multiple to-dos sequentially. * It provides detailed feedback on the success/failure of each operation. * * @param {Object} args - Arguments passed from the MCP client * @returns {Promise<string>} - Detailed success or error message */ handler: async (args) => { try { // Validate that Things is available if (!thingsClient.isThingsAvailable()) { throw new Error("Things app is not available. Make sure you're running on macOS and Things is installed."); } // Validate required fields if (!args.todos || !Array.isArray(args.todos) || args.todos.length === 0) { throw new Error("Todos array is required and must contain at least one item"); } // Check for authentication token if (!process.env.THINGS_AUTHENTICATION_TOKEN) { throw new Error("THINGS_AUTHENTICATION_TOKEN is required for batch add operations. Please set this environment variable."); } const results = []; let successCount = 0; let failureCount = 0; // Process each to-do sequentially to avoid overwhelming the system for (let i = 0; i < args.todos.length; i++) { const todo = args.todos[i]; try { // Validate individual todo if (!todo.title || todo.title.trim() === '') { throw new Error(`Todo ${i + 1}: Title is required`); } // Clean up the todo data const todoData = { title: todo.title.trim(), }; // Add optional fields if provided if (todo.notes && todo.notes.trim()) todoData.notes = todo.notes.trim(); if (todo.when) todoData.when = todo.when; if (todo.deadline) todoData.deadline = todo.deadline; if (todo.tags && todo.tags.trim()) todoData.tags = todo.tags.trim(); if (todo.list && todo.list.trim()) todoData.list = todo.list.trim(); // Execute the add todo command const success = await thingsClient.addTodo(todoData); if (success) { results.push(`✅ Todo ${i + 1}: "${todoData.title}" - Created successfully`); successCount++; } else { results.push(`❌ Todo ${i + 1}: "${todoData.title}" - Failed to create`); failureCount++; } // Small delay between operations to be respectful to the system if (i < args.todos.length - 1) { await new Promise(resolve => setTimeout(resolve, 100)); } } catch (error) { results.push(`❌ Todo ${i + 1}: "${todo.title || 'Unknown'}" - Error: ${error.message}`); failureCount++; } } // Compile final report const summary = `Batch add todos completed: ${successCount} successful, ${failureCount} failed`; const detailReport = results.join('\n'); return `${summary}\n\nDetailed Results:\n${detailReport}`; } catch (error) { const errorMessage = `Failed to batch add todos: ${error.message}`; console.error(errorMessage); throw new Error(errorMessage); } }, }, { name: "batch_complete_todos", description: "Mark multiple to-do items as completed in Things at once. Requires the IDs or titles of the to-dos to complete.", // JSON Schema defining the input parameters for batch completing to-dos inputSchema: { type: "object", properties: { ids: { type: "array", description: "Array of to-do IDs or titles to mark as completed", minItems: 1, maxItems: 50, // Higher limit since this is just marking items complete items: { type: "string", description: "The unique identifier or title of the to-do to complete", minLength: 1 } } }, required: ["ids"], }, /** * Handler function for the batch_complete_todos tool * * This function validates the input and marks multiple to-dos as completed. * It provides detailed feedback on the success/failure of each operation. * * @param {Object} args - Arguments passed from the MCP client * @returns {Promise<string>} - Detailed success or error message */ handler: async (args) => { try { // Validate that Things is available if (!thingsClient.isThingsAvailable()) { throw new Error("Things app is not available. Make sure you're running on macOS and Things is installed."); } // Validate required fields if (!args.ids || !Array.isArray(args.ids) || args.ids.length === 0) { throw new Error("IDs array is required and must contain at least one item"); } // Check for authentication token if (!process.env.THINGS_AUTHENTICATION_TOKEN) { throw new Error("THINGS_AUTHENTICATION_TOKEN is required for batch completion operations. Please set this environment variable."); } const results = []; let successCount = 0; let failureCount = 0; // Process each to-do sequentially for (let i = 0; i < args.ids.length; i++) { const id = args.ids[i]; try { // Validate individual ID if (!id || id.trim() === '') { throw new Error(`ID ${i + 1}: ID cannot be empty`); } const completeData = { id: id.trim(), }; // Execute the complete todo command const success = await thingsClient.completeTodo(completeData); if (success) { results.push(`✅ Todo ${i + 1}: "${completeData.id}" - Marked as completed`); successCount++; } else { results.push(`❌ Todo ${i + 1}: "${completeData.id}" - Failed to complete`); failureCount++; } // Small delay between operations if (i < args.ids.length - 1) { await new Promise(resolve => setTimeout(resolve, 100)); } } catch (error) { results.push(`❌ Todo ${i + 1}: "${id || 'Unknown'}" - Error: ${error.message}`); failureCount++; } } // Compile final report const summary = `Batch complete todos finished: ${successCount} successful, ${failureCount} failed`; const detailReport = results.join('\n'); return `${summary}\n\nDetailed Results:\n${detailReport}`; } catch (error) { const errorMessage = `Failed to batch complete todos: ${error.message}`; console.error(errorMessage); throw new Error(errorMessage); } }, }, { name: "batch_update_todos", description: "Update multiple to-do items in Things at once. Allows applying the same changes to multiple items efficiently.", // JSON Schema defining the input parameters for batch updating to-dos inputSchema: { type: "object", properties: { ids: { type: "array", description: "Array of to-do IDs or titles to update", minItems: 1, maxItems: 20, items: { type: "string", description: "The unique identifier or title of the to-do to update", minLength: 1 } }, updates: { type: "object", description: "The updates to apply to all specified to-dos", properties: { title: { type: "string", description: "New title/name for the to-dos" }, notes: { type: "string", description: "New notes or description for the to-dos" }, when: { type: "string", description: "When to reschedule the to-dos", enum: ["today", "tomorrow", "evening", "anytime", "someday"] }, deadline: { type: "string", description: "New due date for the to-dos" }, tags: { type: "string", description: "New comma-separated list of tags" }, list: { type: "string", description: "Move the to-dos to a different list" } }, minProperties: 1 // At least one field must be provided for updates } }, required: ["ids", "updates"], }, /** * Handler function for the batch_update_todos tool * * This function validates the input and updates multiple to-dos with the same changes. * It provides detailed feedback on the success/failure of each operation. * * @param {Object} args - Arguments passed from the MCP client * @returns {Promise<string>} - Detailed success or error message */ handler: async (args) => { try { // Validate that Things is available if (!thingsClient.isThingsAvailable()) { throw new Error("Things app is not available. Make sure you're running on macOS and Things is installed."); } // Validate required fields if (!args.ids || !Array.isArray(args.ids) || args.ids.length === 0) { throw new Error("IDs array is required and must contain at least one item"); } if (!args.updates || typeof args.updates !== 'object') { throw new Error("Updates object is required"); } // Check that at least one update field is provided const updateKeys = Object.keys(args.updates).filter(key => args.updates[key] !== undefined); if (updateKeys.length === 0) { throw new Error("At least one update field must be provided"); } // Check for authentication token if (!process.env.THINGS_AUTHENTICATION_TOKEN) { throw new Error("THINGS_AUTHENTICATION_TOKEN is required for batch update operations. Please set this environment variable."); } const results = []; let successCount = 0; let failureCount = 0; // Process each to-do sequentially for (let i = 0; i < args.ids.length; i++) { const id = args.ids[i]; try { // Validate individual ID if (!id || id.trim() === '') { throw new Error(`ID ${i + 1}: ID cannot be empty`); } // Build update data for this specific to-do const updateData = { id: id.trim(), }; // Add the update fields if (args.updates.title !== undefined) updateData.title = args.updates.title; if (args.updates.notes !== undefined) updateData.notes = args.updates.notes; if (args.updates.when !== undefined) updateData.when = args.updates.when; if (args.updates.deadline !== undefined) updateData.deadline = args.updates.deadline; if (args.updates.tags !== undefined) updateData.tags = args.updates.tags; if (args.updates.list !== undefined) updateData.list = args.updates.list; // Execute the update todo command const success = await thingsClient.updateTodo(updateData); if (success) { results.push(`✅ Todo ${i + 1}: "${updateData.id}" - Updated successfully`); successCount++; } else { results.push(`❌ Todo ${i + 1}: "${updateData.id}" - Failed to update`); failureCount++; } // Small delay between operations if (i < args.ids.length - 1) { await new Promise(resolve => setTimeout(resolve, 100)); } } catch (error) { results.push(`❌ Todo ${i + 1}: "${id || 'Unknown'}" - Error: ${error.message}`); failureCount++; } } // Compile final report const summary = `Batch update todos completed: ${successCount} successful, ${failureCount} failed`; const detailReport = results.join('\n'); // Show what updates were applied const appliedUpdates = Object.entries(args.updates) .filter(([key, value]) => value !== undefined) .map(([key, value]) => `${key}: ${value}`) .join(', '); return `${summary}\n\nApplied Updates: ${appliedUpdates}\n\nDetailed Results:\n${detailReport}`; } catch (error) { const errorMessage = `Failed to batch update todos: ${error.message}`; console.error(errorMessage); throw new Error(errorMessage); } }, }, ]; }

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