Skip to main content
Glama

update_todo

Modify existing to-do items in Things.app by updating title, notes, scheduling, tags, checklist items, project assignment, and completion status.

Instructions

Update an existing to-do item in Things.app. Modify title, notes, scheduling, tags, checklist items, project/area assignment, and completion status.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
idYesThe unique ID of the to-do to update. This ID can be obtained from the list_todos tool
titleNoUpdate the to-do title with a new clear, actionable description of the task
notesNoReplace existing notes with new content (max 10,000 characters). Supports markdown formatting. This completely replaces existing notes
prependNotesNoAdd text to the beginning of existing notes without replacing them. Useful for adding updates or new information
appendNotesNoAdd text to the end of existing notes without replacing them. Useful for adding follow-up information or status updates
whenNoReschedule the to-do. Use "today" for immediate action, "tomorrow" for next day, "evening" for later today, "anytime" for no specific time, "someday" for future consideration, or ISO date format (YYYY-MM-DD) for specific date
deadlineNoUpdate the deadline in ISO date format (YYYY-MM-DD). Creates or updates deadline reminder in Things.app
tagsNoReplace all current tags with this new set of tag names (max 20 tags). This completely replaces existing tags
addTagsNoAdd these tag names to existing tags without removing current ones (max 20 total tags). Preserves existing tags
checklistItemsNoReplace all current checklist items with this new set (max 100 items). This completely replaces existing checklist items
prependChecklistItemsNoAdd these checklist items to the beginning of the existing checklist without removing current items
appendChecklistItemsNoAdd these checklist items to the end of the existing checklist without removing current items
projectIdNoMove the to-do to a different project by specifying the project ID
projectNameNoMove the to-do to a different project by specifying the project name. Things.app will find the project by name
areaIdNoMove the to-do to a different area by specifying the area ID
areaNameNoMove the to-do to a different area by specifying the area name (e.g., "Work", "Personal", "Health")
headingIdNoMove the to-do under a specific heading within the target project by heading ID
headingNameNoMove the to-do under a specific heading within the target project by heading name (e.g., "Phase 1", "Research")
completedNoMark the to-do as completed (true) or reopen it (false). Completed to-dos are moved to the Logbook
canceledNoMark the to-do as canceled (true) or restore it (false). Canceled to-dos are moved to the Trash
creationDateNoOverride the creation date with a specific ISO8601 datetime (YYYY-MM-DDTHH:MM:SS). Useful for data migration
completionDateNoSet a specific completion date using ISO8601 datetime (YYYY-MM-DDTHH:MM:SS). Only used when marking as completed

Implementation Reference

  • Core handler function that implements the update_todo tool logic. Uses JSON operations for completion/cancellation and Things.app URL scheme for other updates.
    async (params) => {
      try {
        logger.info('Updating to-do', { id: params.id });
        
        const authToken = requireAuthToken();
        
        // Check if we're doing a completion/cancellation operation (use JSON)
        if (params.completed !== undefined || params.canceled !== undefined) {
          const attributes: Record<string, any> = {};
          
          if (params.completed !== undefined) {
            attributes.completed = params.completed;
            if (params.completionDate) {
              attributes['completion-date'] = params.completionDate;
            }
          }
          
          if (params.canceled !== undefined) {
            attributes.canceled = params.canceled;
          }
          
          const operation: JsonOperation = {
            type: 'to-do',
            operation: 'update',
            id: params.id,
            attributes
          };
          
          await executeJsonOperation(operation, authToken);
        } else {
          // Use URL scheme for other updates
          const urlParams: Record<string, any> = {
            id: params.id,
            'auth-token': authToken
          };
    
          // Map schema parameters to Things URL scheme parameters
          if (params.title) urlParams.title = params.title;
          if (params.notes) urlParams.notes = params.notes;
          if (params.prependNotes) urlParams['prepend-notes'] = params.prependNotes;
          if (params.appendNotes) urlParams['append-notes'] = params.appendNotes;
          if (params.when) urlParams.when = params.when;
          if (params.deadline) urlParams.deadline = params.deadline;
          if (params.tags) urlParams.tags = params.tags.join(',');
          if (params.addTags) urlParams['add-tags'] = params.addTags.join(',');
          if (params.checklistItems) urlParams['checklist-items'] = params.checklistItems.join(',');
          if (params.prependChecklistItems) urlParams['prepend-checklist-items'] = params.prependChecklistItems.join(',');
          if (params.appendChecklistItems) urlParams['append-checklist-items'] = params.appendChecklistItems.join(',');
          if (params.projectId) urlParams['list-id'] = params.projectId;
          if (params.projectName) urlParams.list = params.projectName;
          if (params.areaId) urlParams['area-id'] = params.areaId;
          if (params.areaName) urlParams.area = params.areaName;
          if (params.headingId) urlParams['heading-id'] = params.headingId;
          if (params.headingName) urlParams.heading = params.headingName;
          if (params.creationDate) urlParams['creation-date'] = params.creationDate;
          
          const url = buildThingsUrl('update', urlParams);
          logger.debug('Generated URL', { url: url.replace(authToken, '***') });
          
          await openThingsUrl(url);
        }
        
        return {
          content: [{
            type: "text",
            text: `Successfully updated to-do: ${params.id}`
          }]
        };
      } catch (error) {
        logger.error('Failed to update to-do', { error: error instanceof Error ? error.message : error });
        throw error;
      }
    }
  • Zod schema defining the input parameters and validation for the update_todo tool.
    const updateTodoSchema = z.object({
      id: z.string().min(1).describe('The unique ID of the to-do to update. This ID can be obtained from the list_todos tool'),
      title: z.string().min(1).optional().describe('Update the to-do title with a new clear, actionable description of the task'),
      notes: z.string().max(10000).optional().describe('Replace existing notes with new content (max 10,000 characters). Supports markdown formatting. This completely replaces existing notes'),
      prependNotes: z.string().optional().describe('Add text to the beginning of existing notes without replacing them. Useful for adding updates or new information'),
      appendNotes: z.string().optional().describe('Add text to the end of existing notes without replacing them. Useful for adding follow-up information or status updates'),
      when: z.enum(['today', 'tomorrow', 'evening', 'anytime', 'someday'])
        .or(z.string().regex(/^\d{4}-\d{2}-\d{2}$/))
        .optional()
        .describe('Reschedule the to-do. Use "today" for immediate action, "tomorrow" for next day, "evening" for later today, "anytime" for no specific time, "someday" for future consideration, or ISO date format (YYYY-MM-DD) for specific date'),
      deadline: z.string()
        .regex(/^\d{4}-\d{2}-\d{2}$/)
        .optional()
        .describe('Update the deadline in ISO date format (YYYY-MM-DD). Creates or updates deadline reminder in Things.app'),
      tags: z.array(z.string().min(1))
        .max(20)
        .optional()
        .describe('Replace all current tags with this new set of tag names (max 20 tags). This completely replaces existing tags'),
      addTags: z.array(z.string().min(1))
        .max(20)
        .optional()
        .describe('Add these tag names to existing tags without removing current ones (max 20 total tags). Preserves existing tags'),
      checklistItems: z.array(z.string().min(1))
        .max(100)
        .optional()
        .describe('Replace all current checklist items with this new set (max 100 items). This completely replaces existing checklist items'),
      prependChecklistItems: z.array(z.string().min(1))
        .optional()
        .describe('Add these checklist items to the beginning of the existing checklist without removing current items'),
      appendChecklistItems: z.array(z.string().min(1))
        .optional()
        .describe('Add these checklist items to the end of the existing checklist without removing current items'),
      projectId: z.string()
        .optional()
        .describe('Move the to-do to a different project by specifying the project ID'),
      projectName: z.string()
        .optional()
        .describe('Move the to-do to a different project by specifying the project name. Things.app will find the project by name'),
      areaId: z.string()
        .optional()
        .describe('Move the to-do to a different area by specifying the area ID'),
      areaName: z.string()
        .optional()
        .describe('Move the to-do to a different area by specifying the area name (e.g., "Work", "Personal", "Health")'),
      headingId: z.string()
        .optional()
        .describe('Move the to-do under a specific heading within the target project by heading ID'),
      headingName: z.string()
        .optional()
        .describe('Move the to-do under a specific heading within the target project by heading name (e.g., "Phase 1", "Research")'),
      completed: z.boolean()
        .optional()
        .describe('Mark the to-do as completed (true) or reopen it (false). Completed to-dos are moved to the Logbook'),
      canceled: z.boolean()
        .optional()
        .describe('Mark the to-do as canceled (true) or restore it (false). Canceled to-dos are moved to the Trash'),
      creationDate: z.string()
        .regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)
        .optional()
        .describe('Override the creation date with a specific ISO8601 datetime (YYYY-MM-DDTHH:MM:SS). Useful for data migration'),
      completionDate: z.string()
        .regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)
        .optional()
        .describe('Set a specific completion date using ISO8601 datetime (YYYY-MM-DDTHH:MM:SS). Only used when marking as completed')
    });
  • Registration function that registers the 'update_todo' tool with the MCP server, providing name, description, schema, and handler.
    export function registerUpdateTodoTool(server: McpServer): void {
      server.tool(
        'update_todo',
        'Update an existing to-do item in Things.app. Modify title, notes, scheduling, tags, checklist items, project/area assignment, and completion status.',
        updateTodoSchema.shape,
        async (params) => {
          try {
            logger.info('Updating to-do', { id: params.id });
            
            const authToken = requireAuthToken();
            
            // Check if we're doing a completion/cancellation operation (use JSON)
            if (params.completed !== undefined || params.canceled !== undefined) {
              const attributes: Record<string, any> = {};
              
              if (params.completed !== undefined) {
                attributes.completed = params.completed;
                if (params.completionDate) {
                  attributes['completion-date'] = params.completionDate;
                }
              }
              
              if (params.canceled !== undefined) {
                attributes.canceled = params.canceled;
              }
              
              const operation: JsonOperation = {
                type: 'to-do',
                operation: 'update',
                id: params.id,
                attributes
              };
              
              await executeJsonOperation(operation, authToken);
            } else {
              // Use URL scheme for other updates
              const urlParams: Record<string, any> = {
                id: params.id,
                'auth-token': authToken
              };
    
              // Map schema parameters to Things URL scheme parameters
              if (params.title) urlParams.title = params.title;
              if (params.notes) urlParams.notes = params.notes;
              if (params.prependNotes) urlParams['prepend-notes'] = params.prependNotes;
              if (params.appendNotes) urlParams['append-notes'] = params.appendNotes;
              if (params.when) urlParams.when = params.when;
              if (params.deadline) urlParams.deadline = params.deadline;
              if (params.tags) urlParams.tags = params.tags.join(',');
              if (params.addTags) urlParams['add-tags'] = params.addTags.join(',');
              if (params.checklistItems) urlParams['checklist-items'] = params.checklistItems.join(',');
              if (params.prependChecklistItems) urlParams['prepend-checklist-items'] = params.prependChecklistItems.join(',');
              if (params.appendChecklistItems) urlParams['append-checklist-items'] = params.appendChecklistItems.join(',');
              if (params.projectId) urlParams['list-id'] = params.projectId;
              if (params.projectName) urlParams.list = params.projectName;
              if (params.areaId) urlParams['area-id'] = params.areaId;
              if (params.areaName) urlParams.area = params.areaName;
              if (params.headingId) urlParams['heading-id'] = params.headingId;
              if (params.headingName) urlParams.heading = params.headingName;
              if (params.creationDate) urlParams['creation-date'] = params.creationDate;
              
              const url = buildThingsUrl('update', urlParams);
              logger.debug('Generated URL', { url: url.replace(authToken, '***') });
              
              await openThingsUrl(url);
            }
            
            return {
              content: [{
                type: "text",
                text: `Successfully updated to-do: ${params.id}`
              }]
            };
          } catch (error) {
            logger.error('Failed to update to-do', { error: error instanceof Error ? error.message : error });
            throw error;
          }
        }
      );
    }
  • src/index.ts:23-23 (registration)
    Top-level call to register the update_todo tool during server initialization.
    registerUpdateTodoTool(server);
  • TypeScript interface defining the parameters for updating a todo, used for type safety.
    export interface UpdateTodoParams {
      id: string;
      'auth-token': string;
      title?: string;
      notes?: string;
      when?: WhenValue;
      deadline?: string;
      tags?: string;
      'checklist-items'?: string;
      list?: string;
      heading?: string;
      completed?: boolean;
      canceled?: boolean;
      'prepend-notes'?: string;
      'append-notes'?: string;
      'add-tags'?: string;
      'add-checklist-items'?: string;
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It states the tool modifies an existing to-do, implying mutation, but lacks details on permissions required, whether changes are reversible, error handling (e.g., invalid IDs), or response format. For a mutation tool with 22 parameters and no annotation coverage, this is a significant gap in transparency.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence that front-loads the core purpose and enumerates key modifiable aspects. It avoids redundancy and waste, though it could be slightly more structured (e.g., bullet points) given the complexity. Every part earns its place by summarizing the tool's scope.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (22 parameters, mutation operation, no annotations, no output schema), the description is incomplete. It lacks behavioral context (e.g., side effects, error cases), usage guidelines, and output information. While the schema covers parameters well, the description doesn't compensate for other gaps, making it inadequate for such a multifaceted tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all 22 parameters thoroughly. The description lists general categories of modifiable fields (e.g., 'title, notes, scheduling, tags'), but adds minimal semantic value beyond what the schema provides. Baseline 3 is appropriate when the schema does the heavy lifting.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb ('Update') and resource ('an existing to-do item in Things.app'), and lists specific modifiable fields (title, notes, scheduling, tags, checklist items, project/area assignment, and completion status). This distinguishes it from siblings like 'add_todo' (creation) and 'update_project' (different resource).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives. It doesn't mention prerequisites (e.g., needing an existing to-do ID from 'list_todos'), exclusions (e.g., not for creating new items), or comparisons with siblings like 'add_todo' for creation or 'update_project' for different resources. Usage is implied but not explicitly stated.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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

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